Make some DHCP options and host discovery configurable

- Added broadcast option
- Made host discovery from DHCP configurable
- Some code cleanups

Change-Id: I42191c2fd17ef309c73a5382730d708686b835cd
diff --git a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
index e35f71f..c7453ac 100644
--- a/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
+++ b/apps/dhcp/app/src/main/java/org/onosproject/dhcp/impl/DhcpManager.java
@@ -15,11 +15,13 @@
  */
 package org.onosproject.dhcp.impl;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
@@ -38,6 +40,8 @@
 import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
 import org.onlab.util.Timer;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.dhcp.DhcpService;
@@ -66,19 +70,31 @@
 import org.onosproject.net.packet.PacketService;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_DHCPServerIp;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
+import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_RequestedIP;
+import static org.onlab.packet.DHCPPacketType.DHCPACK;
+import static org.onlab.packet.DHCPPacketType.DHCPNAK;
+import static org.onlab.packet.DHCPPacketType.DHCPOFFER;
 import static org.onlab.packet.MacAddress.valueOf;
+import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
+import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_Requested;
 import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
 
 /**
@@ -89,6 +105,9 @@
 public class DhcpManager implements DhcpService {
 
     private static final ProviderId PID = new ProviderId("of", "org.onosproject.dhcp", true);
+    private static final String ALLOW_HOST_DISCOVERY = "allowHostDiscovery";
+    private static final boolean DEFAULT_ALLOW_HOST_DISCOVERY = false;
+
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     private final InternalConfigListener cfgListener = new InternalConfigListener();
@@ -120,44 +139,36 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected HostProviderRegistry hostProviderRegistry;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService componentConfigService;
+
+    @Property(name = ALLOW_HOST_DISCOVERY, boolValue = DEFAULT_ALLOW_HOST_DISCOVERY,
+            label = "Allow host discovery from DHCP request")
+    private boolean allowHostDiscovery = DEFAULT_ALLOW_HOST_DISCOVERY;
+
     protected HostProviderService hostProviderService;
-
     private final HostProvider hostProvider = new InternalHostProvider();
-
     private ApplicationId appId;
 
     // Hardcoded values are default values.
-
-    private static Ip4Address myIP = Ip4Address.valueOf("10.0.0.2");
-
-    private static MacAddress myMAC = valueOf("4f:4f:4f:4f:4f:4f");
-
     /**
      * leaseTime - 10 mins or 600s.
      * renewalTime - 5 mins or 300s.
      * rebindingTime - 6 mins or 360s.
      */
-
     private static int leaseTime = 600;
-
     private static int renewalTime = 300;
-
     private static int rebindingTime = 360;
-
     private static byte packetTTL = (byte) 127;
-
     private static Ip4Address subnetMask = Ip4Address.valueOf("255.0.0.0");
-
     private static Ip4Address broadcastAddress = Ip4Address.valueOf("10.255.255.255");
-
     private static Ip4Address routerAddress = Ip4Address.valueOf("10.0.0.2");
-
     private static Ip4Address domainServer = Ip4Address.valueOf("10.0.0.2");
-
+    private static Ip4Address myIP = Ip4Address.valueOf("10.0.0.2");
+    private static MacAddress myMAC = valueOf("4f:4f:4f:4f:4f:4f");
     private static final Ip4Address IP_BROADCAST = Ip4Address.valueOf("255.255.255.255");
 
     protected Timeout timeout;
-
     protected static int timerDelay = 2;
 
     @Activate
@@ -165,6 +176,7 @@
         // start the dhcp server
         appId = coreService.registerApplication("org.onosproject.dhcp");
 
+        componentConfigService.registerProperties(getClass());
         cfgService.addListener(cfgListener);
         factories.forEach(cfgService::registerConfigFactory);
         cfgListener.reconfigureNetwork(cfgService.getConfig(appId, DhcpConfig.class));
@@ -187,6 +199,19 @@
         log.info("Stopped");
     }
 
+    @Modified
+    protected void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+
+        String updatedConfig = Tools.get(properties, ALLOW_HOST_DISCOVERY);
+        if (!Strings.isNullOrEmpty(updatedConfig)) {
+            allowHostDiscovery = Boolean.valueOf(updatedConfig);
+            log.info("Host discovery is set to {}", updatedConfig);
+        }
+
+        log.info("Modified");
+    }
+
     /**
      * Request packet in via PacketService.
      */
@@ -241,12 +266,10 @@
     }
 
     @Override
-    public boolean setStaticMapping(MacAddress macID, Ip4Address ipAddress, boolean rangeNotEnforced,
-                                    List<Ip4Address> addressList) {
-        log.debug("setStaticMapping is called with Mac: {}, Ip: {} addressList: {}",
-                macID.toString(), ipAddress.toString(), addressList.toString());
-
-        return dhcpStore.assignStaticIP(macID, ipAddress, rangeNotEnforced, addressList);
+    public boolean setStaticMapping(MacAddress macAddress, IpAssignment ipAssignment) {
+        log.debug("setStaticMapping is called with Mac: {} IpAssignment: {}",
+                  macAddress, ipAssignment);
+        return dhcpStore.assignStaticIP(macAddress, ipAssignment);
     }
 
     @Override
@@ -271,24 +294,25 @@
          */
         private Ethernet buildReply(Ethernet packet, Ip4Address ipOffered, byte outgoingMessageType) {
 
-            Ip4Address subnetMaskReply;
-            Ip4Address dhcpServerReply;
-            Ip4Address routerAddressReply;
-            Ip4Address domainServerReply;
-            IpAssignment ipAssignment;
+            // mandatory options
+            // TODO save and get the information below to/from IP assignment
+            Ip4Address dhcpServerReply = myIP;
+            Ip4Address subnetMaskReply = subnetMask;
+            Ip4Address broadcastReply = broadcastAddress;
 
-            ipAssignment = dhcpStore.getIpAssignmentFromAllocationMap(HostId.hostId(packet.getSourceMAC()));
+            // optional options
+            Optional<Ip4Address> routerAddressReply = Optional.of(routerAddress);
+            Optional<Ip4Address> domainServerReply = Optional.of(domainServer);
 
-            if (ipAssignment != null && ipAssignment.rangeNotEnforced()) {
+            IpAssignment ipAssignment = dhcpStore.getIpAssignmentFromAllocationMap(
+                    HostId.hostId(packet.getSourceMAC()));
+
+            if (ipAssignment != null &&
+                    ipAssignment.assignmentStatus().equals(Option_RangeNotEnforced)) {
                 subnetMaskReply = ipAssignment.subnetMask();
-                dhcpServerReply = ipAssignment.dhcpServer();
-                domainServerReply = ipAssignment.domainServer();
-                routerAddressReply = ipAssignment.routerAddress();
-            } else {
-                subnetMaskReply = subnetMask;
-                dhcpServerReply = myIP;
-                routerAddressReply = routerAddress;
-                domainServerReply = domainServer;
+                broadcastReply = ipAssignment.broadcast();
+                routerAddressReply = Optional.ofNullable(ipAssignment.routerAddress());
+                domainServerReply = Optional.ofNullable(ipAssignment.domainServer());
             }
 
             // Ethernet Frame.
@@ -335,7 +359,7 @@
             List<DHCPOption> optionList = new ArrayList<>();
 
             // DHCP Message Type.
-            option.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+            option.setCode(OptionCode_MessageType.getValue());
             option.setLength((byte) 1);
             byte[] optionData = {outgoingMessageType};
             option.setData(optionData);
@@ -343,13 +367,12 @@
 
             // DHCP Server Identifier.
             option = new DHCPOption();
-            option.setCode(DHCP.DHCPOptionCode.OptionCode_DHCPServerIp.getValue());
+            option.setCode(OptionCode_DHCPServerIp.getValue());
             option.setLength((byte) 4);
             option.setData(dhcpServerReply.toOctets());
             optionList.add(option);
 
             if (outgoingMessageType != DHCPPacketType.DHCPNAK.getValue()) {
-
                 // IP Address Lease Time.
                 option = new DHCPOption();
                 option.setCode(DHCP.DHCPOptionCode.OptionCode_LeaseTime.getValue());
@@ -383,22 +406,26 @@
                 option = new DHCPOption();
                 option.setCode(DHCP.DHCPOptionCode.OptionCode_BroadcastAddress.getValue());
                 option.setLength((byte) 4);
-                option.setData(broadcastAddress.toOctets());
+                option.setData(broadcastReply.toOctets());
                 optionList.add(option);
 
                 // Router Address.
-                option = new DHCPOption();
-                option.setCode(DHCP.DHCPOptionCode.OptionCode_RouterAddress.getValue());
-                option.setLength((byte) 4);
-                option.setData(routerAddressReply.toOctets());
-                optionList.add(option);
+                if (routerAddressReply.isPresent()) {
+                    option = new DHCPOption();
+                    option.setCode(DHCP.DHCPOptionCode.OptionCode_RouterAddress.getValue());
+                    option.setLength((byte) 4);
+                    option.setData(routerAddressReply.get().toOctets());
+                    optionList.add(option);
+                }
 
                 // DNS Server Address.
-                option = new DHCPOption();
-                option.setCode(DHCP.DHCPOptionCode.OptionCode_DomainServer.getValue());
-                option.setLength((byte) 4);
-                option.setData(domainServerReply.toOctets());
-                optionList.add(option);
+                if (domainServerReply.isPresent()) {
+                    option = new DHCPOption();
+                    option.setCode(DHCP.DHCPOptionCode.OptionCode_DomainServer.getValue());
+                    option.setLength((byte) 4);
+                    option.setData(domainServerReply.get().toOctets());
+                    optionList.add(option);
+                }
             }
 
             // End Option.
@@ -439,104 +466,94 @@
          * @param dhcpPayload the extracted DHCP payload
          */
         private void processDhcpPacket(PacketContext context, DHCP dhcpPayload) {
+            if (dhcpPayload == null) {
+                log.debug("DHCP packet without payload, do nothing");
+                return;
+            }
+
             Ethernet packet = context.inPacket().parsed();
+            DHCPPacketType incomingPacketType = null;
             boolean flagIfRequestedIP = false;
             boolean flagIfServerIP = false;
             Ip4Address requestedIP = Ip4Address.valueOf("0.0.0.0");
             Ip4Address serverIP = Ip4Address.valueOf("0.0.0.0");
 
-            if (dhcpPayload != null) {
-
-                DHCPPacketType incomingPacketType = DHCPPacketType.getType(0);
-                for (DHCPOption option : dhcpPayload.getOptions()) {
-                    if (option.getCode() == DHCP.DHCPOptionCode.OptionCode_MessageType.getValue()) {
-                        byte[] data = option.getData();
-                        incomingPacketType = DHCPPacketType.getType(data[0]);
-                    }
-                    if (option.getCode() == DHCP.DHCPOptionCode.OptionCode_RequestedIP.getValue()) {
-                        byte[] data = option.getData();
-                        requestedIP = Ip4Address.valueOf(data);
-                        flagIfRequestedIP = true;
-                    }
-                    if (option.getCode() == DHCP.DHCPOptionCode.OptionCode_DHCPServerIp.getValue()) {
-                        byte[] data = option.getData();
-                        serverIP = Ip4Address.valueOf(data);
-                        flagIfServerIP = true;
-                    }
+            for (DHCPOption option : dhcpPayload.getOptions()) {
+                if (option.getCode() == OptionCode_MessageType.getValue()) {
+                    byte[] data = option.getData();
+                    incomingPacketType = DHCPPacketType.getType(data[0]);
                 }
-                DHCPPacketType outgoingPacketType;
-                MacAddress clientMac = new MacAddress(dhcpPayload.getClientHardwareAddress());
-                VlanId vlanId = VlanId.vlanId(packet.getVlanID());
-                HostId hostId = HostId.hostId(clientMac, vlanId);
+                if (option.getCode() == OptionCode_RequestedIP.getValue()) {
+                    byte[] data = option.getData();
+                    requestedIP = Ip4Address.valueOf(data);
+                    flagIfRequestedIP = true;
+                }
+                if (option.getCode() == OptionCode_DHCPServerIp.getValue()) {
+                    byte[] data = option.getData();
+                    serverIP = Ip4Address.valueOf(data);
+                    flagIfServerIP = true;
+                }
+            }
 
-                if (incomingPacketType.getValue() == DHCPPacketType.DHCPDISCOVER.getValue()) {
+            if (incomingPacketType == null) {
+                log.debug("No incoming packet type specified, ignore it");
+                return;
+            }
 
-                    outgoingPacketType = DHCPPacketType.DHCPOFFER;
-                    Ip4Address ipOffered = null;
-                    ipOffered = dhcpStore.suggestIP(hostId, requestedIP);
+            DHCPPacketType outgoingPacketType;
+            MacAddress clientMac = new MacAddress(dhcpPayload.getClientHardwareAddress());
+            VlanId vlanId = VlanId.vlanId(packet.getVlanID());
+            HostId hostId = HostId.hostId(clientMac, vlanId);
 
+            switch (incomingPacketType) {
+                case DHCPDISCOVER:
+                    log.trace("DHCP DISCOVER received from {}", hostId);
+                    Ip4Address ipOffered = dhcpStore.suggestIP(hostId, requestedIP);
                     if (ipOffered != null) {
-                        Ethernet ethReply = buildReply(packet, ipOffered,
-                                (byte) outgoingPacketType.getValue());
+                        Ethernet ethReply = buildReply(
+                                packet,
+                                ipOffered,
+                                (byte) DHCPOFFER.getValue());
                         sendReply(context, ethReply);
                     }
-                } else if (incomingPacketType.getValue() == DHCPPacketType.DHCPREQUEST.getValue()) {
+                    break;
+                case DHCPREQUEST:
+                    log.trace("DHCP REQUEST received from {}", hostId);
+                    if (flagIfServerIP && !myIP.equals(serverIP)) {
+                        return;
+                    }
 
-                    if (flagIfServerIP && flagIfRequestedIP) {
-                        // SELECTING state
+                    if (!flagIfRequestedIP) {
+                        // this is renew or rebinding request
+                        int clientIp = dhcpPayload.getClientIPAddress();
+                        requestedIP = Ip4Address.valueOf(clientIp);
+                    }
 
+                    IpAssignment ipAssignment = IpAssignment.builder()
+                            .ipAddress(requestedIP)
+                            .leasePeriod(leaseTime)
+                            .timestamp(new Date())
+                            .assignmentStatus(Option_Requested).build();
 
-                        if (dhcpStore.getIpAssignmentFromAllocationMap(HostId.hostId(clientMac))
-                                .rangeNotEnforced()) {
-                            outgoingPacketType = DHCPPacketType.DHCPACK;
-                            Ethernet ethReply = buildReply(packet, requestedIP, (byte) outgoingPacketType.getValue());
-                            sendReply(context, ethReply);
-                        } else {
-                            if (myIP.equals(serverIP)) {
-                                if (dhcpStore.assignIP(hostId, requestedIP, leaseTime, false, Lists.newArrayList())) {
-                                    outgoingPacketType = DHCPPacketType.DHCPACK;
-                                    discoverHost(context, requestedIP);
-                                } else {
-                                    outgoingPacketType = DHCPPacketType.DHCPNAK;
-                                }
-                                Ethernet ethReply = buildReply(packet, requestedIP,
-                                        (byte) outgoingPacketType.getValue());
-                                sendReply(context, ethReply);
-                            }
-                        }
-                    } else if (flagIfRequestedIP) {
-                        // INIT-REBOOT state
-                        if (dhcpStore.assignIP(hostId, requestedIP, leaseTime, false, Lists.newArrayList())) {
-                            outgoingPacketType = DHCPPacketType.DHCPACK;
-                            Ethernet ethReply = buildReply(packet, requestedIP, (byte) outgoingPacketType.getValue());
-                            sendReply(context, ethReply);
-                            discoverHost(context, requestedIP);
-                        }
-
+                    if (dhcpStore.assignIP(hostId, ipAssignment)) {
+                        outgoingPacketType = DHCPACK;
+                        discoverHost(context, requestedIP);
                     } else {
-                        // RENEWING and REBINDING state
-                        int ciaadr = dhcpPayload.getClientIPAddress();
-                        if (ciaadr != 0) {
-                            Ip4Address clientIaddr = Ip4Address.valueOf(ciaadr);
-                            if (dhcpStore.assignIP(hostId, clientIaddr, leaseTime, false, Lists.newArrayList())) {
-                                outgoingPacketType = DHCPPacketType.DHCPACK;
-                                discoverHost(context, clientIaddr);
-                            } else if (packet.getEtherType() == Ethernet.TYPE_IPV4 &&
-                                    ((IPv4) packet.getPayload()).getDestinationAddress() == myIP.toInt()) {
-                                outgoingPacketType = DHCPPacketType.DHCPNAK;
-                            } else {
-                                return;
-                            }
-                            Ethernet ethReply = buildReply(packet, clientIaddr, (byte) outgoingPacketType.getValue());
-                            sendReply(context, ethReply);
-                        }
+                        outgoingPacketType = DHCPNAK;
                     }
-                } else if (incomingPacketType.getValue() == DHCPPacketType.DHCPRELEASE.getValue()) {
-                    Ip4Address ip4Address = dhcpStore.releaseIP(hostId);
-                    if (ip4Address != null) {
-                        hostProviderService.removeIpFromHost(hostId, ip4Address);
+
+                    Ethernet ethReply = buildReply(packet, requestedIP, (byte) outgoingPacketType.getValue());
+                    sendReply(context, ethReply);
+                    break;
+                case DHCPRELEASE:
+                    log.trace("DHCP RELEASE received from {}", hostId);
+                    Ip4Address releaseIp = dhcpStore.releaseIP(hostId);
+                    if (releaseIp != null) {
+                        hostProviderService.removeIpFromHost(hostId, releaseIp);
                     }
-                }
+                    break;
+                default:
+                    break;
             }
         }
 
@@ -575,6 +592,11 @@
          * @param ipAssigned IP Address assigned to the host by DHCP Manager
          */
         private void discoverHost(PacketContext context, Ip4Address ipAssigned) {
+            if (!allowHostDiscovery) {
+                // host discovery is not allowed, do nothing
+                return;
+            }
+
             Ethernet packet = context.inPacket().parsed();
             MacAddress mac = packet.getSourceMAC();
             VlanId vlanId = VlanId.vlanId(packet.getVlanID());
@@ -585,6 +607,8 @@
 
             HostId hostId = HostId.hostId(mac, vlanId);
             DefaultHostDescription desc = new DefaultHostDescription(mac, vlanId, hostLocation, ips);
+
+            log.info("Discovered host {}", desc);
             hostProviderService.hostDetected(hostId, desc, false);
         }