[CORD-653] Fix host monitor and provide utility functions

Changes:
- fixes host monitor generating correct probe;
- provides and tests utility functions to calculate well known address;
- provides utility functions to craft ndp solicit and ndp adv;
- provides utility functions to craft arp request;
- provides utility functions to craft icmp and icmpv6 response;

Change-Id: I5a4fa89e549fd665a48e51ba3438932849f6627c
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 9206176..66b5fa5 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,12 +19,10 @@
 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.IPv6;
 import org.onlab.packet.IpAddress;
 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.incubator.net.intf.Interface;
@@ -212,14 +210,31 @@
                            IpAddress targetIp,
                            IpAddress sourceIp, MacAddress sourceMac,
                            VlanId vlan) {
-        Ethernet probePacket = null;
+        Ethernet probePacket;
 
         if (targetIp.isIp4()) {
             // IPv4: Use ARP
             probePacket = buildArpRequest(targetIp, sourceIp, sourceMac, vlan);
         } else {
-            // IPv6: Use Neighbor Discovery
-            probePacket = buildNdpRequest(targetIp, sourceIp, sourceMac, vlan);
+            /*
+             * IPv6: Use Neighbor Discovery. According to the NDP protocol,
+             * we should use the solicitation node address as IPv6 destination
+             * and the multicast mac address as Ethernet destination.
+             */
+            byte[] destIp = IPv6.solicitationNodeAddress(targetIp.toOctets());
+            probePacket = NeighborSolicitation.buildNdpSolicit(
+                    targetIp.toOctets(),
+                    sourceIp.toOctets(),
+                    destIp,
+                    sourceMac.toBytes(),
+                    IPv6.multicastMacAddress(destIp),
+                    vlan
+            );
+        }
+
+        if (probePacket == null) {
+            log.warn("Not able to build the probe packet");
+            return;
         }
 
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
@@ -263,43 +278,4 @@
         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 Solicitation 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;
-    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ARP.java b/utils/misc/src/main/java/org/onlab/packet/ARP.java
index 80584ba..798b88c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ARP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ARP.java
@@ -341,6 +341,47 @@
     }
 
     /**
+     * Builds an ARP request using the supplied parameters.
+     *
+     * @param senderMacAddress the mac address of the sender
+     * @param senderIpAddress the ip address of the sender
+     * @param targetAddress the address to resolve
+     * @param vlanId the vlan id
+     * @return the Ethernet frame containing the ARP request
+     */
+    public static Ethernet buildArpRequest(byte[] senderMacAddress,
+                                           byte[] senderIpAddress,
+                                           byte[] targetAddress,
+                                           short vlanId) {
+
+        if (senderMacAddress.length != MacAddress.MAC_ADDRESS_LENGTH ||
+                senderIpAddress.length != Ip4Address.BYTE_LENGTH ||
+                targetAddress.length != Ip4Address.BYTE_LENGTH) {
+            return null;
+        }
+
+        ARP arpRequest = new ARP();
+        arpRequest.setHardwareType(ARP.HW_TYPE_ETHERNET)
+                .setProtocolType(ARP.PROTO_TYPE_IP)
+                .setHardwareAddressLength(
+                        (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+                .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
+                .setOpCode(ARP.OP_REQUEST)
+                .setSenderHardwareAddress(senderMacAddress)
+                .setTargetHardwareAddress(MacAddress.ZERO.toBytes())
+                .setSenderProtocolAddress(senderIpAddress)
+                .setTargetProtocolAddress(targetAddress);
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(MacAddress.BROADCAST.toBytes())
+                .setSourceMACAddress(senderMacAddress)
+                .setEtherType(Ethernet.TYPE_ARP)
+                .setVlanID(vlanId)
+                .setPayload(arpRequest);
+        return eth;
+    }
+
+    /**
      * Builds an ARP reply based on a request.
      *
      * @param srcIp the IP address to use as the reply source
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP.java b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
index 88a18ec..add1c0c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP.java
@@ -225,4 +225,51 @@
                 .add("checksum", Short.toString(checksum))
                 .toString();
     }
+
+    /**
+     * Builds an ICMP reply using the supplied ICMP request.
+     *
+     * @param ethRequest the Ethernet packet containing the ICMP ECHO request
+     * @return the Ethernet packet containing the ICMP ECHO reply
+     */
+    public static Ethernet buildIcmpReply(Ethernet ethRequest) {
+
+        if (ethRequest.getEtherType() != Ethernet.TYPE_IPV4) {
+            return null;
+        }
+
+        IPv4 ipRequest = (IPv4) ethRequest.getPayload();
+
+        if (ipRequest.getProtocol() != IPv4.PROTOCOL_ICMP) {
+            return null;
+        }
+
+        Ethernet ethReply = new Ethernet();
+        IPv4 ipReply = new IPv4();
+
+        int destAddress = ipRequest.getDestinationAddress();
+        ipReply.setDestinationAddress(ipRequest.getSourceAddress());
+        ipReply.setSourceAddress(destAddress);
+        ipReply.setTtl((byte) 64);
+        ipReply.setChecksum((short) 0);
+        ipReply.setProtocol(IPv4.PROTOCOL_ICMP);
+
+        ICMP icmpRequest = (ICMP) ipRequest.getPayload();
+        ICMP icmpReply = new ICMP();
+
+        icmpReply.setPayload(icmpRequest.getPayload());
+        icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY);
+        icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY);
+        icmpReply.setChecksum((short) 0);
+        ipReply.setPayload(icmpReply);
+
+        ethReply.setEtherType(Ethernet.TYPE_IPV4);
+        ethReply.setVlanID(ethRequest.getVlanID());
+        ethReply.setDestinationMACAddress(ethRequest.getSourceMACAddress());
+        ethReply.setSourceMACAddress(ethRequest.getDestinationMACAddress());
+        ethReply.setPayload(ipReply);
+
+
+        return ethReply;
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
index 64552bd..4e6139b 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ICMP6.java
@@ -373,4 +373,48 @@
                 .add("checksum", Short.toString(checksum))
                 .toString();
     }
+
+    /**
+     * Builds an ICMPv6 reply using the supplied ICMPv6 request.
+     *
+     * @param ethRequest the Ethernet packet containing the ICMPv6 ECHO request
+     * @return the Ethernet packet containing the ICMPv6 ECHO reply
+     */
+    public static Ethernet buildIcmp6Reply(Ethernet ethRequest) {
+
+        if (ethRequest.getEtherType() != Ethernet.TYPE_IPV6) {
+            return null;
+        }
+
+        IPv6 ipv6Request = (IPv6) ethRequest.getPayload();
+
+        if (ipv6Request.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+            return null;
+        }
+
+        Ethernet ethReply = new Ethernet();
+
+
+        IPv6 ipv6Reply = new IPv6();
+
+        byte[] destAddress = ipv6Request.getDestinationAddress();
+        ipv6Reply.setDestinationAddress(ipv6Request.getSourceAddress());
+        ipv6Reply.setSourceAddress(destAddress);
+        ipv6Reply.setHopLimit((byte) 64);
+        ipv6Reply.setNextHeader(IPv6.PROTOCOL_ICMP6);
+
+        ICMP6 icmpv6Reply = new ICMP6();
+        icmpv6Reply.setPayload(ipv6Request.getPayload().getPayload());
+        icmpv6Reply.setIcmpType(ICMP6.ECHO_REPLY);
+        icmpv6Reply.setIcmpCode((byte) 0);
+        ipv6Reply.setPayload(icmpv6Reply);
+
+        ethReply.setEtherType(Ethernet.TYPE_IPV6);
+        ethReply.setVlanID(ethRequest.getVlanID());
+        ethReply.setDestinationMACAddress(ethRequest.getSourceMACAddress());
+        ethReply.setSourceMACAddress(ethRequest.getDestinationMACAddress());
+        ethReply.setPayload(ipv6Reply);
+
+        return ethReply;
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/IPv6.java b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
index ed26268..f466ca4 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IPv6.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IPv6.java
@@ -385,4 +385,52 @@
                 .add("destinationAddress", Arrays.toString(destinationAddress))
                 .toString();
     }
+
+    /**
+     * According to the RFC 4291, the solicitation node addresses are
+     * formed by taking the low-order 24 bits of an address (unicast or anycast)
+     * and appending those bits to the prefix FF02:0:0:0:0:1:FF00::/104.
+     *
+     * Solicited-Node Address:  FF02:0:0:0:0:1:FFXX:XXXX
+     *
+     * @param targetIp the unicast or anycast address
+     * @return the computed solicitation node address
+     */
+    public static byte[] solicitationNodeAddress(byte[] targetIp) {
+        return targetIp.length != Ip6Address.BYTE_LENGTH ? null : new byte[] {
+                (byte) 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x01, (byte) 0xff,
+                targetIp[targetIp.length - 3],
+                targetIp[targetIp.length - 2],
+                targetIp[targetIp.length - 1]
+        };
+    }
+
+    /**
+     * According to the RFC 2464, an IPv6 packet with a multicast
+     * destination address DST, consisting of the sixteen octets DST[1]
+     * through DST[16], is transmitted to the Ethernet multicast address
+     * whose first two octets are the value 3333 hexadecimal and whose last
+     * four octets are the last four octets of DST.
+     *
+     *                   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     *                   |0 0 1 1 0 0 1 1|0 0 1 1 0 0 1 1|
+     *                   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     *                   |   DST[13]     |   DST[14]     |
+     *                   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     *                   |   DST[15]     |   DST[16]     |
+     *                   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     *
+     * @param targetIp the multicast address.
+     * @return the multicast mac address
+     */
+    public static byte[] multicastMacAddress(byte[] targetIp) {
+        return targetIp.length != Ip6Address.BYTE_LENGTH ? null : new byte[] {
+                0x33, 0x33,
+                targetIp[targetIp.length - 4],
+                targetIp[targetIp.length - 3],
+                targetIp[targetIp.length - 2],
+                targetIp[targetIp.length - 1],
+        };
+    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
index b218ab8..96e2fd7 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborAdvertisement.java
@@ -17,8 +17,12 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -286,4 +290,66 @@
                 .add("targetAddress", Arrays.toString(targetAddress))
                 .toString();
     }
+
+    /**
+     * Builds an NDP reply based on a request.
+     *
+     * @param srcIp   the IP address to use as the reply source
+     * @param srcMac  the MAC address to use as the reply source
+     * @param request the Neighbor Solicitation request we got
+     * @return an Ethernet frame containing the Neighbor Advertisement reply
+     */
+    public static Ethernet buildNdpAdv(byte[] srcIp,
+                                   byte[] srcMac,
+                                   Ethernet request) {
+
+        if (srcIp.length != Ip6Address.BYTE_LENGTH ||
+                srcMac.length != MacAddress.MAC_ADDRESS_LENGTH) {
+            return null;
+        }
+
+        if (request.getEtherType() != Ethernet.TYPE_IPV6) {
+            return null;
+        }
+
+        IPv6 ipv6Request = (IPv6) request.getPayload();
+
+        if (ipv6Request.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+            return null;
+        }
+
+        ICMP6 icmpv6 = (ICMP6) ipv6Request.getPayload();
+
+        if (icmpv6.getIcmpType() != ICMP6.NEIGHBOR_SOLICITATION) {
+            return null;
+        }
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(request.getSourceMAC());
+        eth.setSourceMACAddress(srcMac);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setVlanID(request.getVlanID());
+
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(srcIp);
+        ipv6.setDestinationAddress(ipv6Request.getSourceAddress());
+        ipv6.setHopLimit((byte) 255);
+
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
+        icmp6.setIcmpCode((byte) 0);
+
+        NeighborAdvertisement nadv = new NeighborAdvertisement();
+        nadv.setTargetAddress(srcIp);
+        nadv.setSolicitedFlag((byte) 1);
+        nadv.setOverrideFlag((byte) 1);
+        nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+                       srcMac);
+
+        icmp6.setPayload(nadv);
+        ipv6.setPayload(icmp6);
+        eth.setPayload(ipv6);
+        return eth;
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
index 9eb2376..0df878e 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ndp/NeighborSolicitation.java
@@ -17,8 +17,13 @@
 
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip6Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -198,4 +203,67 @@
                 .toString();
         // TODO: need to handle options
     }
+
+    /**
+     * Builds a NDP solicitation using the supplied parameters.
+     *
+     * @param targetIp the target ip
+     * @param sourceIp the source ip
+     * @param destinationIp the destination ip
+     * @param sourceMac the source mac address
+     * @param destinationMac the destination mac address
+     * @param vlan the vlan id
+     * @return the ethernet packet containing the ndp solicitation
+     */
+    public static Ethernet buildNdpSolicit(byte[] targetIp,
+                                           byte[] sourceIp,
+                                           byte[] destinationIp,
+                                           byte[] sourceMac,
+                                           byte[] destinationMac,
+                                           VlanId vlan) {
+
+        if (targetIp.length != Ip6Address.BYTE_LENGTH ||
+                sourceIp.length != Ip6Address.BYTE_LENGTH ||
+                destinationIp.length != Ip6Address.BYTE_LENGTH ||
+                sourceMac.length != MacAddress.MAC_ADDRESS_LENGTH ||
+                destinationMac.length != MacAddress.MAC_ADDRESS_LENGTH) {
+            return null;
+        }
+
+        /*
+         * Here we craft the Ethernet packet.
+         */
+        Ethernet ethernet = new Ethernet();
+        ethernet.setEtherType(Ethernet.TYPE_IPV6)
+                .setDestinationMACAddress(destinationMac)
+                .setSourceMACAddress(sourceMac);
+        ethernet.setVlanID(vlan.id());
+        /*
+         * IPv6 packet is created.
+         */
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(sourceIp);
+        ipv6.setDestinationAddress(destinationIp);
+        ipv6.setHopLimit((byte) 255);
+        /*
+         * Create the ICMPv6 packet.
+         */
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_SOLICITATION);
+        icmp6.setIcmpCode((byte) 0);
+        /*
+         * Create the Neighbor Solicitation packet.
+         */
+        NeighborSolicitation ns = new NeighborSolicitation();
+        ns.setTargetAddress(targetIp);
+        ns.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS, sourceMac);
+        /*
+         * Set the payloads
+         */
+        icmp6.setPayload(ns);
+        ipv6.setPayload(icmp6);
+        ethernet.setPayload(ipv6);
+
+        return ethernet;
+    }
 }
diff --git a/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java b/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
index f1d4d38..b1b555d 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IPv6Test.java
@@ -24,12 +24,10 @@
 import org.junit.Test;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * Tests for class {@link IPv6}.
@@ -43,6 +41,13 @@
             (byte) 0x20, (byte) 0x01, (byte) 0x0f, (byte) 0x18, (byte) 0x01, (byte) 0x13, (byte) 0x02, (byte) 0x15,
             (byte) 0xe6, (byte) 0xce, (byte) 0x8f, (byte) 0xff, (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8
     };
+    private static final byte[] SOLICITATION_NODE_ADDRESS = {
+            (byte) 0xff, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0xff, (byte) 0x54, (byte) 0x37, (byte) 0xc8
+    };
+    private static final byte[] MULTICAST_ADDRESS = {
+            (byte) 0x33, (byte) 0x33, (byte) 0xfe, (byte) 0x54, (byte) 0x37, (byte) 0xc8
+    };
     private static Data data;
     private static UDP udp;
     private static byte[] bytePacket;
@@ -168,4 +173,20 @@
         assertTrue(StringUtils.contains(str, "hopLimit=" + (byte) 32));
         // TODO: test IPv6 source and destination address
     }
+
+    /**
+     * Tests the proper operation of the solicitationNodeAddress function.
+     */
+    @Test
+    public void testSolicitationNodeAddress() {
+        assertTrue(Arrays.equals(SOLICITATION_NODE_ADDRESS, IPv6.solicitationNodeAddress(DESTINATION_ADDRESS)));
+    }
+
+    /**
+     * Tests the proper operation of the multicastAddress function.
+     */
+    @Test
+    public void testMulticastAddress() {
+        assertTrue(Arrays.equals(MULTICAST_ADDRESS, IPv6.multicastMacAddress(DESTINATION_ADDRESS)));
+    }
 }