Added IPv6 support to HostMonitor

This fixes ONOS-635

Change-Id: I49cb135af38d54298ba950d5ce1cc110a6f3184d
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
index b5833cf..8e6d16a 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostMonitor.java
@@ -19,9 +19,14 @@
 import org.jboss.netty.util.TimerTask;
 import org.onlab.packet.ARP;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborDiscoveryOptions;
+import org.onlab.packet.ndp.NeighborSolicitation;
 import org.onlab.util.Timer;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -149,7 +154,7 @@
             Set<Host> hosts = hostManager.getHostsByIp(ip);
 
             if (hosts.isEmpty()) {
-                sendArpRequest(ip);
+                sendArpNdpRequest(ip);
             } else {
                 for (Host host : hosts) {
                     HostProvider provider = hostProviders.get(host.providerId());
@@ -166,12 +171,13 @@
     }
 
     /**
-     * Sends an ARP request for the given IP address.
+     * Sends an ARP or Neighbor Discovery Protocol request for the given IP
+     * address.
      *
-     * @param targetIp IP address to ARP for
+     * @param targetIp IP address to send the request for
      */
-    private void sendArpRequest(IpAddress targetIp) {
-        // Find ports with an IP address in the target's subnet and sent ARP
+    private void sendArpNdpRequest(IpAddress targetIp) {
+        // Find ports with an IP address in the target's subnet and sent ARP/ND
         // probes out those ports.
         for (Device device : deviceService.getDevices()) {
             for (Port port : deviceService.getPorts(device.id())) {
@@ -182,9 +188,10 @@
                 for (PortAddresses portAddresses : portAddressSet) {
                     for (InterfaceIpAddress ia : portAddresses.ipAddresses()) {
                         if (ia.subnetAddress().contains(targetIp)) {
-                            sendProbe(device.id(), port, targetIp,
-                                      ia.ipAddress(), portAddresses.mac(),
-                                      portAddresses.vlan());
+                            sendArpNdpProbe(device.id(), port, targetIp,
+                                            ia.ipAddress(),
+                                            portAddresses.mac(),
+                                            portAddresses.vlan());
                         }
                     }
                 }
@@ -192,26 +199,38 @@
         }
     }
 
-    private void sendProbe(DeviceId deviceId, Port port, IpAddress targetIp,
-            IpAddress sourceIp, MacAddress sourceMac, VlanId vlan) {
-        Ethernet arpPacket = buildArpRequest(targetIp, sourceIp, sourceMac, vlan);
+    private void sendArpNdpProbe(DeviceId deviceId, Port port,
+                                 IpAddress targetIp,
+                                 IpAddress sourceIp, MacAddress sourceMac,
+                                 VlanId vlan) {
+        Ethernet probePacket = null;
+
+        if (targetIp.version() == Ip4Address.VERSION) {
+            // IPv4: Use ARP
+            probePacket = buildArpRequest(targetIp, sourceIp, sourceMac,
+                                          vlan);
+        } else {
+            // IPv6: Use Neighbor Discovery
+            probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac,
+                                          vlan);
+        }
 
         List<Instruction> instructions = new ArrayList<>();
         instructions.add(Instructions.createOutput(port.number()));
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-        .setOutput(port.number())
-        .build();
+            .setOutput(port.number())
+            .build();
 
         OutboundPacket outboundPacket =
-                new DefaultOutboundPacket(deviceId, treatment,
-                        ByteBuffer.wrap(arpPacket.serialize()));
+            new DefaultOutboundPacket(deviceId, treatment,
+                                      ByteBuffer.wrap(probePacket.serialize()));
 
         packetService.emit(outboundPacket);
     }
 
     private Ethernet buildArpRequest(IpAddress targetIp, IpAddress sourceIp,
-            MacAddress sourceMac, VlanId vlan) {
+                                     MacAddress sourceMac, VlanId vlan) {
 
         ARP arp = new ARP();
         arp.setHardwareType(ARP.HW_TYPE_ETHERNET)
@@ -237,4 +256,44 @@
 
         return ethernet;
     }
+
+    private Ethernet buildNdpRequest(IpAddress targetIp, IpAddress sourceIp,
+                                     MacAddress sourceMac, VlanId vlan) {
+
+        // Create the Ethernet packet
+        Ethernet ethernet = new Ethernet();
+        ethernet.setEtherType(Ethernet.TYPE_IPV6)
+                .setDestinationMACAddress(MacAddress.BROADCAST)
+                .setSourceMACAddress(sourceMac);
+        if (!vlan.equals(VlanId.NONE)) {
+            ethernet.setVlanID(vlan.toShort());
+        }
+
+        //
+        // Create the IPv6 packet
+        //
+        // TODO: The destination IP address should be the
+        // solicited-node multicast address
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(sourceIp.toOctets());
+        ipv6.setDestinationAddress(targetIp.toOctets());
+        ipv6.setHopLimit((byte) 255);
+
+        // Create the ICMPv6 packet
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+        icmp6.setIcmpCode((byte) 0);
+
+        // Create the Neighbor Solication packet
+        NeighborSolicitation ns = new NeighborSolicitation();
+        ns.setTargetAddress(targetIp.toOctets());
+        ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
+                     sourceMac.toBytes());
+
+        icmp6.setPayload(ns);
+        ipv6.setPayload(icmp6);
+        ethernet.setPayload(ipv6);
+
+        return ethernet;
+    }
 }