Use DHCP ACK to learn the DHCP client

This feature can be turn on/off via component config.
In addition, ARP interception can also be turned on/off now.

Change-Id: Ia3310fa3fb06821051fbf3363e51096d00781dbf
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index d91d437..1119eda 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -23,12 +23,17 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.ARP;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCPPacketType;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv4;
 import org.onlab.packet.IPv6;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
 import org.onlab.packet.ipv6.IExtensionHeader;
 import org.onlab.packet.ndp.NeighborAdvertisement;
@@ -120,10 +125,20 @@
             label = "Enable host removal on port/device down events")
     private boolean hostRemovalEnabled = true;
 
-    @Property(name = "ipv6NeighborDiscovery", boolValue = false,
+    @Property(name = "useArp", boolValue = true,
+            label = "Enable using ARP for neighbor discovery by the " +
+                    "Host Location Provider; default is true")
+    private boolean useArp = true;
+
+    @Property(name = "useIpv6ND", boolValue = false,
             label = "Enable using IPv6 Neighbor Discovery by the " +
                     "Host Location Provider; default is false")
-    private boolean ipv6NeighborDiscovery = false;
+    private boolean useIpv6ND = false;
+
+    @Property(name = "useDhcp", boolValue = false,
+            label = "Enable using DHCP for neighbor discovery by the " +
+                    "Host Location Provider; default is false")
+    private boolean useDhcp = false;
 
     @Property(name = "requestInterceptsEnabled", boolValue = true,
             label = "Enable requesting packet intercepts")
@@ -184,26 +199,52 @@
      * Request packet intercepts.
      */
     private void requestIntercepts() {
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        selector.matchEthType(Ethernet.TYPE_ARP);
-        packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
-
-        // IPv6 Neighbor Solicitation packet.
-        selector.matchEthType(Ethernet.TYPE_IPV6);
-        selector.matchIPProtocol(IPv6.PROTOCOL_ICMP6);
-        selector.matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION);
-        if (ipv6NeighborDiscovery) {
-            packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
+        // Use ARP
+        TrafficSelector arpSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP)
+                .build();
+        if (useArp) {
+            packetService.requestPackets(arpSelector, PacketPriority.CONTROL, appId);
         } else {
-            packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
+            packetService.cancelPackets(arpSelector, PacketPriority.CONTROL, appId);
         }
 
-        // IPv6 Neighbor Advertisement packet.
-        selector.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT);
-        if (ipv6NeighborDiscovery) {
-            packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
+        // Use IPv6 Neighbor Discovery
+        TrafficSelector ipv6NsSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPProtocol(IPv6.PROTOCOL_ICMP6)
+                .matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION)
+                .build();
+        TrafficSelector ipv6NaSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPProtocol(IPv6.PROTOCOL_ICMP6)
+                .matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT)
+                .build();
+        if (useIpv6ND) {
+            packetService.requestPackets(ipv6NsSelector, PacketPriority.CONTROL, appId);
+            packetService.requestPackets(ipv6NaSelector, PacketPriority.CONTROL, appId);
         } else {
-            packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
+            packetService.cancelPackets(ipv6NsSelector, PacketPriority.CONTROL, appId);
+            packetService.cancelPackets(ipv6NaSelector, PacketPriority.CONTROL, appId);
+        }
+
+        // Use DHCP
+        TrafficSelector dhcpServerSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+                .build();
+        TrafficSelector dhcpClientSelector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+                .build();
+        if (useDhcp) {
+            packetService.requestPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
+            packetService.requestPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
+        } else {
+            packetService.cancelPackets(dhcpServerSelector, PacketPriority.CONTROL, appId);
+            packetService.cancelPackets(dhcpClientSelector, PacketPriority.CONTROL, appId);
         }
     }
 
@@ -245,14 +286,34 @@
                      hostRemovalEnabled ? "enabled" : "disabled");
         }
 
-        flag = Tools.isPropertyEnabled(properties, "ipv6NeighborDiscovery");
+        flag = Tools.isPropertyEnabled(properties, "useArp");
+        if (flag == null) {
+            log.info("Using ARP is not configured, " +
+                    "using current value of {}", useArp);
+        } else {
+            useArp = flag;
+            log.info("Configured. Using ARP is {}",
+                    useArp ? "enabled" : "disabled");
+        }
+
+        flag = Tools.isPropertyEnabled(properties, "useIpv6ND");
         if (flag == null) {
             log.info("Using IPv6 Neighbor Discovery is not configured, " +
-                             "using current value of {}", ipv6NeighborDiscovery);
+                             "using current value of {}", useIpv6ND);
         } else {
-            ipv6NeighborDiscovery = flag;
+            useIpv6ND = flag;
             log.info("Configured. Using IPv6 Neighbor Discovery is {}",
-                     ipv6NeighborDiscovery ? "enabled" : "disabled");
+                     useIpv6ND ? "enabled" : "disabled");
+        }
+
+        flag = Tools.isPropertyEnabled(properties, "useDhcp");
+        if (flag == null) {
+            log.info("Using DHCP is not configured, " +
+                    "using current value of {}", useDhcp);
+        } else {
+            useDhcp = flag;
+            log.info("Configured. Using DHCP is {}",
+                    useDhcp ? "enabled" : "disabled");
         }
 
         flag = Tools.isPropertyEnabled(properties, "requestInterceptsEnabled");
@@ -332,7 +393,7 @@
 
     private class InternalHostProvider implements PacketProcessor {
         /**
-         * Update host location only.
+         * Updates host location only.
          *
          * @param hid  host ID
          * @param mac  source Mac address
@@ -350,7 +411,7 @@
         }
 
         /**
-         * Update host location and IP address.
+         * Updates host location and IP address.
          *
          * @param hid  host ID
          * @param mac  source Mac address
@@ -371,6 +432,28 @@
             }
         }
 
+        /**
+         * Updates host IP address for an existing host.
+         *
+         * @param hid host ID
+         * @param ip IP address
+         */
+        private void updateIp(HostId hid, IpAddress ip) {
+            Host host = hostService.getHost(hid);
+            if (host == null) {
+                log.debug("Fail to update IP for {}. Host does not exist");
+                return;
+            }
+
+            HostDescription desc =
+                    new DefaultHostDescription(hid.mac(), hid.vlanId(), host.location(), ip);
+            try {
+                providerService.hostDetected(hid, desc, false);
+            } catch (IllegalStateException e) {
+                log.debug("Host {} suppressed", hid);
+            }
+        }
+
         @Override
         public void process(PacketContext context) {
             if (context == null) {
@@ -412,7 +495,28 @@
                 updateLocationIP(hid, srcMac, vlan, hloc, ip);
 
             // IPv4: update location only
+            // DHCP ACK: additionally update IP of DHCP client
             } else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
+                IPacket pkt = eth.getPayload();
+                if (pkt != null && pkt instanceof IPv4) {
+                    pkt = pkt.getPayload();
+                    if (pkt != null && pkt instanceof UDP) {
+                        pkt = pkt.getPayload();
+                        if (pkt != null && pkt instanceof DHCP) {
+                            DHCP dhcp = (DHCP) pkt;
+                            if (dhcp.getOptions().stream()
+                                    .anyMatch(dhcpOption -> dhcpOption.getCode() ==
+                                            DHCP.DHCPOptionCode.OptionCode_MessageType.getValue() &&
+                                            dhcpOption.getLength() == 1 &&
+                                            dhcpOption.getData()[0] == DHCPPacketType.DHCPACK.getValue())) {
+                                MacAddress hostMac = MacAddress.valueOf(dhcp.getClientHardwareAddress());
+                                VlanId hostVlan = VlanId.vlanId(eth.getVlanID());
+                                HostId hostId = HostId.hostId(hostMac, hostVlan);
+                                updateIp(hostId, IpAddress.valueOf(dhcp.getYourIPAddress()));
+                            }
+                        }
+                    }
+                }
                 updateLocation(hid, srcMac, vlan, hloc);
 
             //