[CORD-458] Updates ARP handler

Changes:
- SR application uses NRM;
- Adds hooks for ND protocol;
- Updates ARP handler to better leverage on NRM;
- Reworks to take into account IPv4/IPv6 together;

Change-Id: Iab55b8c5ef7d973928d8ad47e2c2a482fb9c5c8a
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
index 8975de1..246a3bb 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -18,19 +18,19 @@
 import org.onlab.packet.ARP;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
-import org.onlab.packet.Ip4Prefix;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
-import org.onosproject.net.packet.InboundPacket;
-import org.onosproject.net.HostId;
-import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
@@ -39,8 +39,10 @@
 
 import java.nio.ByteBuffer;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.incubator.net.neighbour.NeighbourMessageType.REQUEST;
 
 /**
  * Handler of ARP packets that responses or forwards ARP packets that
@@ -81,81 +83,71 @@
      * packet in.
      * We can deal with both cases.
      *
-     * @param pkt incoming packet
+     * @param pkt incoming ARP packet and context information
+     * @param hostService the host service
      */
-    public void processPacketIn(InboundPacket pkt) {
-        Ethernet ethernet = pkt.parsed();
-        ARP arp = (ARP) ethernet.getPayload();
-        ConnectPoint connectPoint = pkt.receivedFrom();
-        DeviceId deviceId = connectPoint.deviceId();
+    public void processPacketIn(NeighbourMessageContext pkt, HostService hostService) {
 
         SegmentRoutingAppConfig appConfig = srManager.cfgService
                 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
-        if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
+        if (appConfig != null && appConfig.suppressSubnet().contains(pkt.inPort())) {
             // Ignore ARP packets come from suppressed ports
             return;
         }
 
-        if (!validateArpSpa(connectPoint, arp)) {
+        if (!validateArpSpa(pkt)) {
             log.debug("Ignore ARP packet discovered on {} with unexpected src protocol address {}.",
-                    connectPoint, Ip4Address.valueOf(arp.getSenderProtocolAddress()));
+                    pkt.inPort(), pkt.sender().getIp4Address());
             return;
         }
 
-        if (arp.getOpCode() == ARP.OP_REQUEST) {
-            handleArpRequest(deviceId, connectPoint, ethernet);
+        if (pkt.type() == REQUEST) {
+            handleArpRequest(pkt, hostService);
         } else {
-            handleArpReply(deviceId, connectPoint, ethernet);
+            handleArpReply(pkt, hostService);
         }
     }
 
-    private void handleArpRequest(DeviceId deviceId, ConnectPoint inPort, Ethernet payload) {
-        ARP arpRequest = (ARP) payload.getPayload();
-        VlanId vlanId = VlanId.vlanId(payload.getVlanID());
-        HostId targetHostId = HostId.hostId(MacAddress.valueOf(
-                                            arpRequest.getTargetHardwareAddress()),
-                                            vlanId);
-
+    private void handleArpRequest(NeighbourMessageContext pkt, HostService hostService) {
         // ARP request for router. Send ARP reply.
-        if (isArpForRouter(deviceId, arpRequest)) {
-            Ip4Address targetAddress = Ip4Address.valueOf(arpRequest.getTargetProtocolAddress());
-            sendArpResponse(arpRequest, config.getRouterMacForAGatewayIp(targetAddress), vlanId);
+        if (isArpForRouter(pkt)) {
+            MacAddress targetMac = config.getRouterMacForAGatewayIp(pkt.target().getIp4Address());
+            sendArpResponse(pkt, targetMac, hostService);
         } else {
-            Host targetHost = srManager.hostService.getHost(targetHostId);
+            Set<Host> hosts = hostService.getHostsByIp(pkt.target());
+            if (hosts.size() > 1) {
+                log.warn("More than one host with the same ip {}", pkt.target());
+            }
+            Host targetHost = hosts.stream().findFirst().orElse(null);
             // ARP request for known hosts. Send proxy ARP reply on behalf of the target.
             if (targetHost != null) {
-                removeVlanAndForward(payload, targetHost.location());
+                pkt.forward(targetHost.location());
             // ARP request for unknown host in the subnet. Flood in the subnet.
             } else {
-                removeVlanAndFlood(payload, inPort);
+                flood(pkt);
             }
         }
     }
 
-    private void handleArpReply(DeviceId deviceId, ConnectPoint inPort, Ethernet payload) {
-        ARP arpReply = (ARP) payload.getPayload();
-        VlanId vlanId = VlanId.vlanId(payload.getVlanID());
-        HostId targetHostId = HostId.hostId(MacAddress.valueOf(
-                                            arpReply.getTargetHardwareAddress()),
-                                            vlanId);
-
+    private void handleArpReply(NeighbourMessageContext pkt, HostService hostService) {
         // ARP reply for router. Process all pending IP packets.
-        if (isArpForRouter(deviceId, arpReply)) {
-            Ip4Address hostIpAddress = Ip4Address.valueOf(arpReply.getSenderProtocolAddress());
-            srManager.ipHandler.forwardPackets(deviceId, hostIpAddress);
+        if (isArpForRouter(pkt)) {
+            Ip4Address hostIpAddress = pkt.sender().getIp4Address();
+            srManager.ipHandler.forwardPackets(pkt.inPort().deviceId(), hostIpAddress);
         } else {
-            Host targetHost = srManager.hostService.getHost(targetHostId);
+            HostId targetHostId = HostId.hostId(pkt.dstMac(), pkt.vlan());
+            Host targetHost = hostService.getHost(targetHostId);
             // ARP reply for known hosts. Forward to the host.
             if (targetHost != null) {
-                removeVlanAndForward(payload, targetHost.location());
+                pkt.forward(targetHost.location());
             // ARP reply for unknown host, Flood in the subnet.
             } else {
                 // Don't flood to non-edge ports
-                if (vlanId.equals(
+                if (pkt.vlan().equals(
                         VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET))) {
                     return;
                 }
-                removeVlanAndFlood(payload, inPort);
+                flood(pkt);
             }
         }
     }
@@ -164,26 +156,27 @@
      * Check if the source protocol address of an ARP packet belongs to the same
      * subnet configured on the port it is seen.
      *
-     * @param connectPoint connect point where the ARP packet is seen
-     * @param arpPacket ARP packet
+     * @param pkt ARP packet and context information
      * @return true if the source protocol address belongs to the configured subnet
      */
-    private boolean validateArpSpa(ConnectPoint connectPoint, ARP arpPacket) {
-        Ip4Address spa = Ip4Address.valueOf(arpPacket.getSenderProtocolAddress());
-        Ip4Prefix subnet = config.getPortSubnet(connectPoint.deviceId(), connectPoint.port());
-        return subnet != null && subnet.contains(spa);
+    private boolean validateArpSpa(NeighbourMessageContext pkt) {
+        Ip4Address spa = pkt.sender().getIp4Address();
+        Set<IpPrefix> subnet = config.getPortSubnets(pkt.inPort().deviceId(), pkt.inPort().port())
+                .stream()
+                .filter(ipPrefix -> ipPrefix.isIp4() && ipPrefix.contains(spa))
+                .collect(Collectors.toSet());
+        return !subnet.isEmpty();
     }
 
 
-    private boolean isArpForRouter(DeviceId deviceId, ARP arpMsg) {
-        Ip4Address targetProtocolAddress = Ip4Address.valueOf(
-                                               arpMsg.getTargetProtocolAddress());
-        Set<Ip4Address> gatewayIpAddresses = null;
+    private boolean isArpForRouter(NeighbourMessageContext pkt) {
+        Ip4Address targetProtocolAddress = pkt.target().getIp4Address();
+        Set<IpAddress> gatewayIpAddresses = null;
         try {
-            if (targetProtocolAddress.equals(config.getRouterIpv4(deviceId))) {
+            if (targetProtocolAddress.equals(config.getRouterIpv4(pkt.inPort().deviceId()))) {
                 return true;
             }
-            gatewayIpAddresses = config.getPortIPs(deviceId);
+            gatewayIpAddresses = config.getPortIPs(pkt.inPort().deviceId());
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage() + " Aborting check for router IP in processing arp");
         }
@@ -207,7 +200,8 @@
 
         try {
             senderMacAddress = config.getDeviceMac(deviceId).toBytes();
-            senderIpAddress = config.getRouterIpv4(deviceId).toOctets();
+            senderIpAddress = config.getRouterIpAddressForASubnetHost(targetAddress.getIp4Address())
+                    .toOctets();
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage() + " Aborting sendArpRequest.");
             return;
@@ -230,41 +224,18 @@
                 .setSourceMACAddress(senderMacAddress)
                 .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest);
 
-        removeVlanAndFlood(eth, inPort);
+        flood(eth, inPort);
     }
 
-    private void sendArpResponse(ARP arpRequest, MacAddress targetMac, VlanId vlanId) {
-        ARP arpReply = new ARP();
-        arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
-                .setProtocolType(ARP.PROTO_TYPE_IP)
-                .setHardwareAddressLength(
-                        (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
-                .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
-                .setOpCode(ARP.OP_REPLY)
-                .setSenderHardwareAddress(targetMac.toBytes())
-                .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
-                .setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
-                .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
-
-        Ethernet eth = new Ethernet();
-        eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
-                .setSourceMACAddress(targetMac.toBytes())
-                .setEtherType(Ethernet.TYPE_ARP).setPayload(arpReply);
-
-        MacAddress hostMac = MacAddress.valueOf(arpReply.getTargetHardwareAddress());
-        HostId dstId = HostId.hostId(hostMac, vlanId);
-        Host dst = srManager.hostService.getHost(dstId);
+    private void sendArpResponse(NeighbourMessageContext pkt, MacAddress targetMac, HostService hostService) {
+        HostId dstId = HostId.hostId(pkt.srcMac(), pkt.vlan());
+        Host dst = hostService.getHost(dstId);
         if (dst == null) {
-            log.warn("Cannot send ARP response to host {}", dstId);
+            log.warn("Cannot send ARP response to host {} - does not exist in the store",
+                     dstId);
             return;
         }
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder().
-                setOutput(dst.location().port()).build();
-        OutboundPacket packet = new DefaultOutboundPacket(dst.location().deviceId(),
-                treatment, ByteBuffer.wrap(eth.serialize()));
-
-        srManager.packetService.emit(packet);
+        pkt.reply(targetMac);
     }
 
     /**
@@ -273,27 +244,54 @@
      * @param packet packet to be flooded
      * @param inPort where the packet comes from
      */
-    private void removeVlanAndFlood(Ethernet packet, ConnectPoint inPort) {
+    private void flood(Ethernet packet, ConnectPoint inPort) {
         Ip4Address targetProtocolAddress = Ip4Address.valueOf(
                 ((ARP) packet.getPayload()).getTargetProtocolAddress()
         );
 
         try {
             srManager.deviceConfiguration
-                 .getSubnetPortsMap(inPort.deviceId()).forEach((subnet, ports) -> {
-                     if (subnet.contains(targetProtocolAddress)) {
+                    .getSubnetPortsMap(inPort.deviceId()).forEach((subnet, ports) -> {
+                if (subnet.contains(targetProtocolAddress)) {
+                    ports.stream()
+                            .filter(port -> port != inPort.port())
+                            .forEach(port -> {
+                                forward(packet, new ConnectPoint(inPort.deviceId(), port));
+                            });
+                }
+            });
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage()
+                             + " Cannot flood in subnet as device config not available"
+                             + " for device: " + inPort.deviceId());
+        }
+    }
+
+    /**
+     * Remove VLAN tag and flood to all ports in the same subnet.
+     *
+     * @param pkt arp packet to be flooded
+     */
+    private void flood(NeighbourMessageContext pkt) {
+        try {
+            srManager.deviceConfiguration
+                 .getSubnetPortsMap(pkt.inPort().deviceId()).forEach((subnet, ports) -> {
+                     if (subnet.contains(pkt.target())) {
                          ports.stream()
-                         .filter(port -> port != inPort.port())
+                         .filter(port -> port != pkt.inPort().port())
                          .forEach(port -> {
-                             removeVlanAndForward(packet,
-                                 new ConnectPoint(inPort.deviceId(), port));
+                             ConnectPoint outPoint = new ConnectPoint(
+                                     pkt.inPort().deviceId(),
+                                     port
+                             );
+                             pkt.forward(outPoint);
                          });
                      }
                  });
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage()
                     + " Cannot flood in subnet as device config not available"
-                    + " for device: " + inPort.deviceId());
+                    + " for device: " + pkt.inPort().deviceId());
         }
     }
 
@@ -312,9 +310,9 @@
      * @param packet packet to be forwarded
      * @param outPort where the packet should be forwarded
      */
-    private void removeVlanAndForward(Ethernet packet, ConnectPoint outPort) {
+    private void forward(Ethernet packet, ConnectPoint outPort) {
         packet.setEtherType(Ethernet.TYPE_ARP);
-        packet.setVlanID(Ethernet.VLAN_UNTAGGED);
+
         ByteBuffer buf = ByteBuffer.wrap(packet.serialize());
 
         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();