ProxyArpManager - fix IPv6 ND Adv issue with Option fields and ARP/NDP Reply
with first IP address from Host service

This fixes ONOS-1010

Also, add protocol-related constants to class NeighborAdvertisement.

Change-Id: Iacf9e48a8a03a86e1cc4e89e7e2b2b4ccc4a7e3a
diff --git a/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java
index 35e0e31..5369d2c 100644
--- a/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onosproject/net/proxyarp/impl/ProxyArpManager.java
@@ -25,6 +25,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.ARP;
+import org.onlab.packet.Data;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPv6;
@@ -141,22 +142,21 @@
         ARP arp = (ARP) eth.getPayload();
         checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST);
         checkNotNull(inPort);
+        Ip4Address targetAddress = Ip4Address.valueOf(arp.getTargetProtocolAddress());
 
         VlanId vlan = VlanId.vlanId(eth.getVlanID());
 
         // If the request came from outside the network, only reply if it was
         // for one of our external addresses.
         if (isOutsidePort(inPort)) {
-            Ip4Address target =
-                Ip4Address.valueOf(arp.getTargetProtocolAddress());
             Set<PortAddresses> addressSet =
                 hostService.getAddressBindingsForPort(inPort);
 
             for (PortAddresses addresses : addressSet) {
                 for (InterfaceIpAddress ia : addresses.ipAddresses()) {
-                    if (ia.ipAddress().equals(target)) {
+                    if (ia.ipAddress().equals(targetAddress)) {
                         Ethernet arpReply =
-                            buildArpReply(target, addresses.mac(), eth);
+                            buildArpReply(targetAddress, addresses.mac(), eth);
                         sendTo(arpReply, inPort);
                     }
                 }
@@ -187,8 +187,7 @@
 
         // Continue with normal proxy ARP case
 
-        Set<Host> hosts = hostService.getHostsByIp(
-                        Ip4Address.valueOf(arp.getTargetProtocolAddress()));
+        Set<Host> hosts = hostService.getHostsByIp(targetAddress);
 
         Host dst = null;
         Host src = hostService.getHost(HostId.hostId(eth.getSourceMAC(),
@@ -202,23 +201,19 @@
         }
 
         if (src == null || dst == null) {
+            //
+            // The request couldn't be resolved.
+            // Flood the request on all ports except the incoming ports.
+            //
             flood(eth, inPort);
             return;
         }
 
         //
-        // TODO find the correct IP address.
-        // Right now we use the first IPv4 address that is found.
+        // Reply on the port the request was received on
         //
-        for (IpAddress ipAddress : dst.ipAddresses()) {
-            Ip4Address ip4Address = ipAddress.getIp4Address();
-            if (ip4Address != null) {
-                Ethernet arpReply = buildArpReply(ip4Address, dst.mac(), eth);
-                // TODO: check send status with host service.
-                sendTo(arpReply, src.location());
-                break;
-            }
-        }
+        Ethernet arpReply = buildArpReply(targetAddress, dst.mac(), eth);
+        sendTo(arpReply, inPort);
     }
 
     private void replyNdp(Ethernet eth, ConnectPoint inPort) {
@@ -226,22 +221,21 @@
         IPv6 ipv6 = (IPv6) eth.getPayload();
         ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
         NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
+        Ip6Address targetAddress = Ip6Address.valueOf(nsol.getTargetAddress());
 
         VlanId vlan = VlanId.vlanId(eth.getVlanID());
 
         // If the request came from outside the network, only reply if it was
         // for one of our external addresses.
         if (isOutsidePort(inPort)) {
-            Ip6Address target =
-                    Ip6Address.valueOf(nsol.getTargetAddress());
             Set<PortAddresses> addressSet =
                     hostService.getAddressBindingsForPort(inPort);
 
             for (PortAddresses addresses : addressSet) {
                 for (InterfaceIpAddress ia : addresses.ipAddresses()) {
-                    if (ia.ipAddress().equals(target)) {
+                    if (ia.ipAddress().equals(targetAddress)) {
                         Ethernet ndpReply =
-                                buildNdpReply(target, addresses.mac(), eth);
+                                buildNdpReply(targetAddress, addresses.mac(), eth);
                         sendTo(ndpReply, inPort);
                     }
                 }
@@ -272,8 +266,7 @@
 
         // Continue with normal proxy ARP case
 
-        Set<Host> hosts = hostService.getHostsByIp(
-                Ip6Address.valueOf(nsol.getTargetAddress()));
+        Set<Host> hosts = hostService.getHostsByIp(targetAddress);
 
         Host dst = null;
         Host src = hostService.getHost(HostId.hostId(eth.getSourceMAC(),
@@ -287,23 +280,19 @@
         }
 
         if (src == null || dst == null) {
+            //
+            // The request couldn't be resolved.
+            // Flood the request on all ports except the incoming ports.
+            //
             flood(eth, inPort);
             return;
         }
 
         //
-        // TODO find the correct IP address.
-        // Right now we use the first IPv4 address that is found.
+        // Reply on the port the request was received on
         //
-        for (IpAddress ipAddress : dst.ipAddresses()) {
-            Ip6Address ip6Address = ipAddress.getIp6Address();
-            if (ip6Address != null) {
-                Ethernet arpReply = buildNdpReply(ip6Address, dst.mac(), eth);
-                // TODO: check send status with host service.
-                sendTo(arpReply, src.location());
-                break;
-            }
-        }
+        Ethernet ndpReply = buildNdpReply(targetAddress, dst.mac(), eth);
+        sendTo(ndpReply, inPort);
     }
 
 
@@ -429,7 +418,9 @@
 
     /**
      * Flood the arp request at all edges in the network.
-     * @param request the arp request.
+     *
+     * @param request the arp request
+     * @param inPort the connect point the arp request was received on
      */
     private void flood(Ethernet request, ConnectPoint inPort) {
         TrafficTreatment.Builder builder = null;
@@ -519,6 +510,7 @@
         arp.setTargetProtocolAddress(((ARP) request.getPayload())
                 .getSenderProtocolAddress());
         arp.setSenderProtocolAddress(srcIp.toInt());
+
         eth.setPayload(arp);
         return eth;
     }
@@ -545,19 +537,32 @@
         ipv6.setSourceAddress(srcIp.toOctets());
         ipv6.setDestinationAddress(requestIp.getSourceAddress());
         ipv6.setHopLimit((byte) 255);
-        eth.setPayload(ipv6);
 
         ICMP6 icmp6 = new ICMP6();
         icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
         icmp6.setIcmpCode((byte) 0);
-        ipv6.setPayload(icmp6);
 
         NeighborAdvertisement nadv = new NeighborAdvertisement();
-        nadv.setTargetAddress(srcMac.toBytes());
+        nadv.setTargetAddress(srcIp.toOctets());
         nadv.setSolicitedFlag((byte) 1);
         nadv.setOverrideFlag((byte) 1);
+        byte[] nadvData =
+            new byte[NeighborAdvertisement.OPTION_LENGTH_IEEE802_ADDRESS];
+        ByteBuffer bbNadv = ByteBuffer.wrap(nadvData);
+        byte nadvOptionType =
+            NeighborAdvertisement.OPTION_TYPE_TARGET_LL_ADDRESS;
+        // The Option length in 8-octets units
+        byte nadvOptionLength =
+            (NeighborAdvertisement.OPTION_LENGTH_IEEE802_ADDRESS + 7) / 8;
+        bbNadv.put(nadvOptionType);
+        bbNadv.put(nadvOptionLength);
+        bbNadv.put(srcMac.toBytes());
+        Data nadvPayload = new Data();
+        nadv.setPayload(nadvPayload.deserialize(nadvData, 0, nadvData.length));
         icmp6.setPayload(nadv);
 
+        ipv6.setPayload(icmp6);
+        eth.setPayload(ipv6);
         return eth;
     }
 
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 113107a..515dd4e 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2014-2015 Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,9 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.onlab.packet.ndp;
 
 import org.onlab.packet.BasePacket;
@@ -27,10 +24,16 @@
 import java.util.Arrays;
 
 /**
- * Implements ICMPv6 Neighbor Advertisement packet format. (RFC 4861)
+ * Implements ICMPv6 Neighbor Advertisement packet format (RFC 4861).
  */
 public class NeighborAdvertisement extends BasePacket {
     public static final byte HEADER_LENGTH = 20; // bytes
+    public static final byte OPTION_TYPE_SOURCE_LL_ADDRESS = 1;
+    public static final byte OPTION_TYPE_TARGET_LL_ADDRESS = 2;
+    public static final byte OPTION_TYPE_PREFIX_INFORMATION = 3;
+    public static final byte OPTION_TYPE_REDIRECTED_HEADER = 4;
+    public static final byte OPTION_TYPE_MTU = 5;
+    public static final byte OPTION_LENGTH_IEEE802_ADDRESS = 8;
 
     protected byte routerFlag;
     protected byte solicitedFlag;