[CORD-631] ICMPv6 Echo support

Changes:
- ICMPv6 Echo support;
- Introduces SegmentRoutingNeighbourHandler;
- Simplifies ArpHandler;
- Simplifies IcmpHandler;

Change-Id: I93f04d94ff15f43ca83f96cbab3da5064c215f9c
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
index 5ea5694..ae32bfc 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
@@ -16,51 +16,180 @@
 
 package org.onosproject.segmentrouting;
 
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
-import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
+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.segmentrouting.config.DeviceConfigNotFoundException;
+import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.nio.ByteBuffer;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
- * This handler deals with the ICMPv6 Echo protocol
- * and ICMPv6 ND protocol.
+ * This handler provides provides useful functions to the
+ * neighbour handlers (ARP, NDP).
  */
-public class SegmentRoutingNeighbourHandler implements NeighbourMessageHandler {
+public class SegmentRoutingNeighbourHandler {
 
     private static Logger log = LoggerFactory.getLogger(SegmentRoutingNeighbourHandler.class);
-    private SegmentRoutingManager manager;
+
+    protected SegmentRoutingManager srManager;
+    protected DeviceConfiguration config;
 
     /**
-     * Create a segment routing neighbour handler.
+     * Creates an SegmentRoutingNeighbourHandler object.
      *
-     * @param segmentRoutingManager the segment routing manager
+     * @param srManager SegmentRoutingManager object
      */
-    public SegmentRoutingNeighbourHandler(SegmentRoutingManager segmentRoutingManager) {
-        this.manager = segmentRoutingManager;
+    public SegmentRoutingNeighbourHandler(SegmentRoutingManager srManager) {
+        this.srManager = srManager;
+        this.config = checkNotNull(srManager.deviceConfiguration);
     }
 
-    /*
-     * We use this method to handle the NDP messages
+    /**
+     * Creates an SegmentRoutingNeighbourHandler object.
      */
-    @Override
-    public void handleMessage(NeighbourMessageContext context, HostService hostService) {
-        log.debug("Received a {} packet {}", context.protocol(), context.packet());
-        switch (context.protocol()) {
-            case ARP:
-                if (this.manager.arpHandler != null) {
-                    this.manager.arpHandler.processPacketIn(context, hostService);
+    public SegmentRoutingNeighbourHandler() {
+        this.srManager = null;
+        this.config = null;
+    }
+
+    /**
+     * Retrieve router (device) info.
+     *
+     * @param mac where to copy the mac
+     * @param ip where to copy the ip
+     * @param deviceId the device id
+     * @param targetAddress the target address
+     */
+    protected void getSenderInfo(byte[] mac,
+                                 byte[] ip,
+                                 DeviceId deviceId,
+                                 IpAddress targetAddress) {
+        byte[] senderMacAddress;
+        byte[] senderIpAddress;
+        try {
+            senderMacAddress = config.getDeviceMac(deviceId).toBytes();
+            senderIpAddress = config.getRouterIpAddressForASubnetHost(targetAddress.getIp4Address())
+                    .toOctets();
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting sendArpRequest.");
+            return;
+        }
+        /*
+         * FIXME understand how to manage IPv4/IPv6 together.
+         */
+        System.arraycopy(senderMacAddress, 0, mac, 0, senderMacAddress.length);
+        System.arraycopy(senderIpAddress, 0, ip, 0, senderIpAddress.length);
+    }
+
+    /**
+     * Utility to send a ND reply using the supplied information.
+     *
+     * @param pkt the request
+     * @param targetMac the target mac
+     * @param hostService the host service
+     */
+    protected void sendResponse(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 {} response to host {} - does not exist in the store",
+                     pkt.protocol(), dstId);
+            return;
+        }
+        pkt.reply(targetMac);
+    }
+
+    /**
+     * Flood to all ports in the same subnet.
+     *
+     * @param packet packet to be flooded
+     * @param inPort where the packet comes from
+     * @param targetAddress the target address
+     */
+    protected void flood(Ethernet packet, ConnectPoint inPort, IpAddress targetAddress) {
+        try {
+            srManager.deviceConfiguration
+                    .getSubnetPortsMap(inPort.deviceId()).forEach((subnet, ports) -> {
+                if (subnet.contains(targetAddress)) {
+                    ports.stream()
+                            .filter(port -> port != inPort.port())
+                            .forEach(port -> {
+                                forward(packet, new ConnectPoint(inPort.deviceId(), port));
+                            });
                 }
-                break;
-            case NDP:
-                if (this.manager.icmpHandler != null) {
-                    this.manager.icmpHandler.processPacketIn(context, hostService);
-                }
-                break;
-            default:
-                log.warn("Unknown protocol", context.protocol());
+            });
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage()
+                             + " Cannot flood in subnet as device config not available"
+                             + " for device: " + inPort.deviceId());
         }
     }
 
+    /*
+     * Floods only on the port which have been configured with the subnet
+     * of the target address. The in port is excluded.
+     *
+     * @param pkt the ndp/arp packet and context information
+     */
+    protected void flood(NeighbourMessageContext pkt) {
+        try {
+            srManager.deviceConfiguration
+                    .getSubnetPortsMap(pkt.inPort().deviceId()).forEach((subnet, ports) -> {
+                if (subnet.contains(pkt.target())) {
+                    ports.stream()
+                            .filter(port -> port != pkt.inPort().port())
+                            .forEach(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: " + pkt.inPort().deviceId());
+        }
+    }
+
+    /**
+     * Packet out to given port.
+     *
+     * Note: In current implementation, we expect all communication with
+     * end hosts within a subnet to be untagged.
+     * <p>
+     * For those pipelines that internally assigns a VLAN, the VLAN tag will be
+     * removed before egress.
+     * <p>
+     * For those pipelines that do not assign internal VLAN, the packet remains
+     * untagged.
+     *
+     * @param packet packet to be forwarded
+     * @param outPort where the packet should be forwarded
+     */
+    private void forward(Ethernet packet, ConnectPoint outPort) {
+        ByteBuffer buf = ByteBuffer.wrap(packet.serialize());
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.setOutput(outPort.port());
+        srManager.packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
+                                                               tbuilder.build(), buf));
+    }
 
 }