[CORD-1431] Support DHCPv6 by HostLocationProvider

Change-Id: I46d268ae4f61949e2cf48f58afa6bb72e8bf4c22
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 1ead471..445afad 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,17 +23,25 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.ARP;
+import org.onlab.packet.BasePacket;
 import org.onlab.packet.DHCP;
+import org.onlab.packet.DHCP6;
 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.Ip6Address;
 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.dhcp.Dhcp6ClientIdOption;
+import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
+import org.onlab.packet.dhcp.Dhcp6IaNaOption;
+import org.onlab.packet.dhcp.Dhcp6IaTaOption;
+import org.onlab.packet.dhcp.Dhcp6RelayOption;
 import org.onlab.packet.ipv6.IExtensionHeader;
 import org.onlab.packet.ndp.NeighborAdvertisement;
 import org.onlab.packet.ndp.NeighborSolicitation;
@@ -145,6 +153,11 @@
                     "Host Location Provider; default is false")
     private boolean useDhcp = false;
 
+    @Property(name = "useDhcp6", boolValue = false,
+            label = "Use DHCPv6 for neighbor discovery by the " +
+                    "Host Location Provider; default is false")
+    private boolean useDhcp6 = false;
+
     @Property(name = "requestInterceptsEnabled", boolValue = true,
             label = "Enable requesting packet intercepts")
     private boolean requestInterceptsEnabled = true;
@@ -509,10 +522,18 @@
                         pkt.getPayload() instanceof IExtensionHeader) {
                     pkt = pkt.getPayload();
                 }
-
-                // Neighbor Discovery Protocol
                 pkt = pkt.getPayload();
+
+                // DHCPv6 protocol
+                DHCP6 dhcp6 = findDhcp6(pkt).orElse(null);
+                if (dhcp6 != null && useDhcp6) {
+                    createOrUpdateHost(hid, srcMac, vlan, hloc, null);
+                    handleDhcp6(dhcp6, vlan);
+                    return;
+                }
+
                 if (pkt != null && pkt instanceof ICMP6) {
+                    // Neighbor Discovery Protocol
                     pkt = pkt.getPayload();
                     // RouterSolicitation, RouterAdvertisement
                     if (pkt != null && (pkt instanceof RouterAdvertisement ||
@@ -541,6 +562,88 @@
             }
         }
 
+        /**
+         * Handles DHCPv6 packet, if message type is ACK, update IP address
+         * according to DHCPv6 payload (IA Address option).
+         *
+         * @param dhcp6 the DHCPv6 payload
+         * @param vlanId the vlan of this packet
+         */
+        private void handleDhcp6(DHCP6 dhcp6, VlanId vlanId) {
+            // extract the relay message if exist
+            while (dhcp6 != null && DHCP6.RELAY_MSG_TYPES.contains(dhcp6.getMsgType())) {
+                dhcp6 = dhcp6.getOptions().stream()
+                        .filter(opt -> opt instanceof Dhcp6RelayOption)
+                        .map(BasePacket::getPayload)
+                        .map(pld -> (DHCP6) pld)
+                        .findFirst()
+                        .orElse(null);
+            }
+
+            if (dhcp6 == null) {
+                // Can't find dhcp payload
+                log.warn("Can't find dhcp payload from relay message");
+                return;
+            }
+
+            if (dhcp6.getMsgType() != DHCP6.MsgType.REPLY.value()) {
+                // Update IP address only when we received REPLY message
+                return;
+            }
+            Optional<Dhcp6ClientIdOption> clientIdOption = dhcp6.getOptions()
+                    .stream()
+                    .filter(opt -> opt instanceof Dhcp6ClientIdOption)
+                    .map(opt -> (Dhcp6ClientIdOption) opt)
+                    .findFirst();
+
+            if (!clientIdOption.isPresent()) {
+                // invalid DHCPv6 option
+                log.warn("Can't find client ID from DHCPv6 {}", dhcp6);
+                return;
+            }
+
+            byte[] linkLayerAddr = clientIdOption.get().getDuid().getLinkLayerAddress();
+            if (linkLayerAddr == null || linkLayerAddr.length != 6) {
+                // No any mac address found
+                log.warn("Can't find client mac from option {}", clientIdOption);
+                return;
+            }
+            MacAddress clientMac = MacAddress.valueOf(linkLayerAddr);
+
+            // Extract IPv6 address from IA NA ot IA TA option
+            Optional<Dhcp6IaNaOption> iaNaOption = dhcp6.getOptions()
+                    .stream()
+                    .filter(opt -> opt instanceof Dhcp6IaNaOption)
+                    .map(opt -> (Dhcp6IaNaOption) opt)
+                    .findFirst();
+            Optional<Dhcp6IaTaOption> iaTaOption = dhcp6.getOptions()
+                    .stream()
+                    .filter(opt -> opt instanceof Dhcp6IaTaOption)
+                    .map(opt -> (Dhcp6IaTaOption) opt)
+                    .findFirst();
+            Optional<Dhcp6IaAddressOption> iaAddressOption;
+            if (iaNaOption.isPresent()) {
+                iaAddressOption = iaNaOption.get().getOptions().stream()
+                        .filter(opt -> opt instanceof Dhcp6IaAddressOption)
+                        .map(opt -> (Dhcp6IaAddressOption) opt)
+                        .findFirst();
+            } else if (iaTaOption.isPresent()) {
+                iaAddressOption = iaTaOption.get().getOptions().stream()
+                        .filter(opt -> opt instanceof Dhcp6IaAddressOption)
+                        .map(opt -> (Dhcp6IaAddressOption) opt)
+                        .findFirst();
+            } else {
+                iaAddressOption = Optional.empty();
+            }
+            if (iaAddressOption.isPresent()) {
+                Ip6Address ip = iaAddressOption.get().getIp6Address();
+                HostId hostId = HostId.hostId(clientMac, vlanId);
+                updateHostIp(hostId, ip);
+            } else {
+                log.warn("Can't find IPv6 address from DHCPv6 {}", dhcp6);
+            }
+        }
+
         private Optional<DHCP> findDhcp(Ethernet eth) {
             IPacket pkt = eth.getPayload();
             return Stream.of(pkt)
@@ -555,6 +658,17 @@
                     .map(p -> (DHCP) p)
                     .findFirst();
         }
+
+        private Optional<DHCP6> findDhcp6(IPacket pkt) {
+            return Stream.of(pkt)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof UDP)
+                    .map(IPacket::getPayload)
+                    .filter(Objects::nonNull)
+                    .filter(p -> p instanceof DHCP6)
+                    .map(p -> (DHCP6) p)
+                    .findFirst();
+        }
     }
 
     // Auxiliary listener to device events.