[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/ArpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
index 675e63d..8ef889f 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -27,42 +27,32 @@
 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.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-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
  * are sent to the controller.
  */
-public class ArpHandler {
+public class ArpHandler extends SegmentRoutingNeighbourHandler {
 
     private static Logger log = LoggerFactory.getLogger(ArpHandler.class);
 
-    private SegmentRoutingManager srManager;
-    private DeviceConfiguration config;
-
     /**
      * Creates an ArpHandler object.
      *
      * @param srManager SegmentRoutingManager object
      */
     public ArpHandler(SegmentRoutingManager srManager) {
-        this.srManager = srManager;
-        this.config = checkNotNull(srManager.deviceConfiguration);
+        super(srManager);
     }
 
     /**
@@ -114,7 +104,7 @@
         // ARP request for router. Send ARP reply.
         if (isArpForRouter(pkt)) {
             MacAddress targetMac = config.getRouterMacForAGatewayIp(pkt.target().getIp4Address());
-            sendArpResponse(pkt, targetMac, hostService);
+            sendResponse(pkt, targetMac, hostService);
         } else {
             Set<Host> hosts = hostService.getHostsByIp(pkt.target());
             if (hosts.size() > 1) {
@@ -197,128 +187,22 @@
      * @param inPort in-port
      */
     public void sendArpRequest(DeviceId deviceId, IpAddress targetAddress, ConnectPoint inPort) {
-        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;
-        }
-
-        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.toOctets());
-
-        Ethernet eth = new Ethernet();
-        eth.setDestinationMACAddress(MacAddress.BROADCAST.toBytes())
-                .setSourceMACAddress(senderMacAddress)
-                .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest);
-
-        flood(eth, inPort);
-    }
-
-    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 {} - does not exist in the store",
-                     dstId);
-            return;
-        }
-        pkt.reply(targetMac);
-    }
-
-    /**
-     * Remove VLAN tag and flood to all ports in the same subnet.
-     *
-     * @param packet packet to be flooded
-     * @param inPort where the packet comes from
-     */
-    private void flood(Ethernet packet, ConnectPoint inPort) {
-        Ip4Address targetProtocolAddress = Ip4Address.valueOf(
-                ((ARP) packet.getPayload()).getTargetProtocolAddress()
+        byte[] senderMacAddress = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+        byte[] senderIpAddress = new byte[Ip4Address.BYTE_LENGTH];
+        /*
+         * Retrieves device info.
+         */
+        getSenderInfo(senderMacAddress, senderIpAddress, deviceId, targetAddress);
+        /*
+         * Creates the request.
+         */
+        Ethernet arpRequest = ARP.buildArpRequest(
+                senderMacAddress,
+                senderIpAddress,
+                targetAddress.toOctets(),
+                VlanId.NO_VID
         );
-
-        try {
-            srManager.deviceConfiguration
-                    .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());
-        }
+        flood(arpRequest, inPort, targetAddress);
     }
 
-    /**
-     * 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 != 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());
-        }
-    }
-
-    /**
-     * Remove VLAN tag and 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) {
-        packet.setEtherType(Ethernet.TYPE_ARP);
-        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));
-    }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index 9ac2b26..1594364 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -17,7 +17,9 @@
 
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.ICMP;
+import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip6Address;
 import org.onlab.packet.IpAddress;
@@ -25,6 +27,7 @@
 import org.onlab.packet.MPLS;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageType;
 import org.onosproject.net.ConnectPoint;
@@ -35,10 +38,8 @@
 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.packet.OutboundPacket;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
-import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,17 +47,13 @@
 import java.nio.ByteBuffer;
 import java.util.Set;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 /**
  * Handler of ICMP packets that responses or forwards ICMP packets that
  * are sent to the controller.
  */
-public class IcmpHandler {
+public class IcmpHandler extends SegmentRoutingNeighbourHandler {
 
     private static Logger log = LoggerFactory.getLogger(IcmpHandler.class);
-    private SegmentRoutingManager srManager;
-    private DeviceConfiguration config;
 
     /**
      * Creates an IcmpHandler object.
@@ -64,8 +61,55 @@
      * @param srManager SegmentRoutingManager object
      */
     public IcmpHandler(SegmentRoutingManager srManager) {
-        this.srManager = srManager;
-        this.config = checkNotNull(srManager.deviceConfiguration);
+        super(srManager);
+    }
+
+    /**
+     * Utility function to send packet out.
+     *
+     * @param outport the output port
+     * @param payload the packet to send
+     * @param sid the segment id
+     * @param destIpAddress the destination ip address
+     * @param allowedHops the hop limit/ttl
+     */
+    private void sendPacketOut(ConnectPoint outport,
+                               Ethernet payload,
+                               int sid,
+                               IpAddress destIpAddress,
+                               byte allowedHops) {
+        int destSid;
+        if (destIpAddress.isIp4()) {
+            destSid = config.getIPv4SegmentId(payload.getDestinationMAC());
+        } else {
+            destSid = config.getIPv6SegmentId(payload.getDestinationMAC());
+        }
+
+        if (sid == -1 || destSid == sid ||
+                config.inSameSubnet(outport.deviceId(), destIpAddress)) {
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder().
+                    setOutput(outport.port()).build();
+            OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
+                                                              treatment, ByteBuffer.wrap(payload.serialize()));
+            srManager.packetService.emit(packet);
+        } else {
+            log.debug("Send a MPLS packet as a ICMP response");
+            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                    .setOutput(outport.port())
+                    .build();
+
+            payload.setEtherType(Ethernet.MPLS_UNICAST);
+            MPLS mplsPkt = new MPLS();
+            mplsPkt.setLabel(sid);
+            mplsPkt.setTtl(allowedHops);
+            mplsPkt.setPayload(payload.getPayload());
+            payload.setPayload(mplsPkt);
+
+            OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
+                                                              treatment, ByteBuffer.wrap(payload.serialize()));
+
+            srManager.packetService.emit(packet);
+        }
     }
 
     //////////////////////////////////////
@@ -79,17 +123,13 @@
      * If it is an ICMP packet to unknown host in a subnet, then sends an ARP request
      * to the subnet.
      *
-     * @param pkt inbound packet
+     * @param eth inbound ICMP packet
+     * @param inPort the input port
      */
-    public void processPacketIn(InboundPacket pkt) {
-
-        Ethernet ethernet = pkt.parsed();
-        IPv4 ipv4 = (IPv4) ethernet.getPayload();
-
-        ConnectPoint connectPoint = pkt.receivedFrom();
-        DeviceId deviceId = connectPoint.deviceId();
-        Ip4Address destinationAddress =
-                Ip4Address.valueOf(ipv4.getDestinationAddress());
+    public void processIcmp(Ethernet eth, ConnectPoint inPort) {
+        DeviceId deviceId = inPort.deviceId();
+        IPv4 ipv4Packet = (IPv4) eth.getPayload();
+        Ip4Address destinationAddress = Ip4Address.valueOf(ipv4Packet.getDestinationAddress());
         Set<IpAddress> gatewayIpAddresses = config.getPortIPs(deviceId);
         IpAddress routerIp;
         try {
@@ -98,14 +138,13 @@
             log.warn(e.getMessage() + " Aborting processPacketIn.");
             return;
         }
-        IpPrefix routerIpPrefix = IpPrefix.valueOf(routerIp, IpPrefix.MAX_INET_MASK_LENGTH);
-        Ip4Address routerIpAddress = routerIpPrefix.getIp4Prefix().address();
-
         // ICMP to the router IP or gateway IP
-        if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
-                (destinationAddress.equals(routerIpAddress) ||
+        if (((ICMP) ipv4Packet.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
+                (destinationAddress.equals(routerIp.getIp4Address()) ||
                         gatewayIpAddresses.contains(destinationAddress))) {
-            sendIcmpResponse(ethernet, connectPoint);
+            sendIcmpResponse(eth, inPort);
+            // We remove the packet from the queue
+            srManager.ipHandler.dequeuePacket(ipv4Packet, destinationAddress);
 
         // ICMP for any known host
         } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
@@ -114,12 +153,13 @@
 
         // ICMP for an unknown host in the subnet of the router
         } else if (config.inSameSubnet(deviceId, destinationAddress)) {
-            srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint);
+            srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, inPort);
 
         // ICMP for an unknown host
         } else {
             log.debug("ICMP request for unknown host {} ", destinationAddress);
-            // Do nothing
+            // We remove the packet from the queue
+            srManager.ipHandler.dequeuePacket(ipv4Packet, destinationAddress);
         }
     }
 
@@ -134,71 +174,90 @@
     private void sendIcmpResponse(Ethernet icmpRequest, ConnectPoint outport) {
         // Note: We assume that packets arrive at the edge switches have
         // untagged VLAN.
-        Ethernet icmpReplyEth = new Ethernet();
-
+        Ethernet icmpReplyEth = ICMP.buildIcmpReply(icmpRequest);
         IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload();
-        IPv4 icmpReplyIpv4 = new IPv4();
-
-        int destAddress = icmpRequestIpv4.getDestinationAddress();
-        icmpReplyIpv4.setDestinationAddress(icmpRequestIpv4.getSourceAddress());
-        icmpReplyIpv4.setSourceAddress(destAddress);
-        icmpReplyIpv4.setTtl((byte) 64);
-        icmpReplyIpv4.setChecksum((short) 0);
-
-        ICMP icmpReply = new ICMP();
-        icmpReply.setPayload(((ICMP) icmpRequestIpv4.getPayload()).getPayload());
-        icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY);
-        icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY);
-        icmpReply.setChecksum((short) 0);
-        icmpReplyIpv4.setPayload(icmpReply);
-
-        icmpReplyEth.setPayload(icmpReplyIpv4);
-        icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4);
-        icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress());
-        icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress());
-
-        Ip4Address destIpAddress = Ip4Address.valueOf(icmpReplyIpv4.getDestinationAddress());
+        IPv4 icmpReplyIpv4 = (IPv4) icmpReplyEth.getPayload();
+        Ip4Address destIpAddress = Ip4Address.valueOf(icmpRequestIpv4.getSourceAddress());
         Ip4Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
         int destSid = config.getIPv4SegmentId(destRouterAddress);
         if (destSid < 0) {
-            log.warn("Cannot find the Segment ID for {}", destAddress);
+            log.warn("Cannot find the Segment ID for {}", destIpAddress);
             return;
         }
-
-        sendPacketOut(outport, icmpReplyEth, destSid);
-
+        sendPacketOut(outport, icmpReplyEth, destSid, destIpAddress, icmpReplyIpv4.getTtl());
     }
 
-    private void sendPacketOut(ConnectPoint outport, Ethernet payload, int destSid) {
+    ///////////////////////////////////////////
+    //      ICMPv6 Echo/Reply Protocol       //
+    ///////////////////////////////////////////
 
-        IPv4 ipPacket = (IPv4) payload.getPayload();
-        Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
-
-        if (destSid == -1 || config.getIPv4SegmentId(payload.getDestinationMAC()) == destSid ||
-                config.inSameSubnet(outport.deviceId(), destIpAddress)) {
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder().
-                    setOutput(outport.port()).build();
-            OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
-                    treatment, ByteBuffer.wrap(payload.serialize()));
-            srManager.packetService.emit(packet);
-        } else {
-            log.debug("Send a MPLS packet as a ICMP response");
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                    .setOutput(outport.port())
-                    .build();
-
-            payload.setEtherType(Ethernet.MPLS_UNICAST);
-            MPLS mplsPkt = new MPLS();
-            mplsPkt.setLabel(destSid);
-            mplsPkt.setTtl(((IPv4) payload.getPayload()).getTtl());
-            mplsPkt.setPayload(payload.getPayload());
-            payload.setPayload(mplsPkt);
-
-            OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
-                    treatment, ByteBuffer.wrap(payload.serialize()));
-
-            srManager.packetService.emit(packet);
+    /**
+     * Process incoming ICMPv6 packet.
+     * If it is an ICMP request to router or known host, then sends an ICMP response.
+     * If it is an ICMP packet to known host and forward the packet to the host.
+     * If it is an ICMP packet to unknown host in a subnet, then sends an ARP request
+     * to the subnet.
+     *
+     * @param eth the incoming ICMPv6 packet
+     * @param inPort the input port
+     */
+    public void processIcmpv6(Ethernet eth, ConnectPoint inPort) {
+        DeviceId deviceId = inPort.deviceId();
+        IPv6 ipv6Packet = (IPv6) eth.getPayload();
+        Ip6Address destinationAddress = Ip6Address.valueOf(ipv6Packet.getDestinationAddress());
+        Set<IpAddress> gatewayIpAddresses = config.getPortIPs(deviceId);
+        IpAddress routerIp;
+        try {
+            routerIp = config.getRouterIpv6(deviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting processPacketIn.");
+            return;
         }
+        ICMP6 icmp6 = (ICMP6) ipv6Packet.getPayload();
+        // ICMP to the router IP or gateway IP
+        if (icmp6.getIcmpType()  == ICMP6.ECHO_REQUEST &&
+                (destinationAddress.equals(routerIp.getIp6Address()) ||
+                        gatewayIpAddresses.contains(destinationAddress))) {
+            sendIcmpv6Response(eth, inPort);
+            // We remove the packet from the queue
+            srManager.ipHandler.dequeuePacket(ipv6Packet, destinationAddress);
+            // ICMP for any known host
+        } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
+            // TODO: known host packet should not be coming to controller - resend flows?
+            srManager.ipHandler.forwardPackets(deviceId, destinationAddress);
+            // ICMP for an unknown host in the subnet of the router
+        } else if (config.inSameSubnet(deviceId, destinationAddress)) {
+            sendNdpRequest(deviceId, destinationAddress, inPort);
+            // ICMP for an unknown host or not configured host
+        } else {
+            log.debug("ICMPv6 request for unknown host or not configured host {} ", destinationAddress);
+            // We remove the packet from the queue
+            srManager.ipHandler.dequeuePacket(ipv6Packet, destinationAddress);
+        }
+    }
+
+    /**
+     * Sends an ICMPv6 reply message.
+     *
+     * Note: we assume that packets sending from the edge switches to the hosts
+     * have untagged VLAN.
+     * @param ethRequest the original ICMP request
+     * @param outport the output port where the ICMP reply should be sent to
+     */
+    private void sendIcmpv6Response(Ethernet ethRequest, ConnectPoint outport) {
+        // Note: We assume that packets arrive at the edge switches have
+        // untagged VLAN.
+        Ethernet ethReply = ICMP6.buildIcmp6Reply(ethRequest);
+        IPv6 icmpRequestIpv6 = (IPv6) ethRequest.getPayload();
+        IPv6 icmpReplyIpv6 = (IPv6) ethRequest.getPayload();
+        Ip6Address destIpAddress = Ip6Address.valueOf(icmpRequestIpv6.getSourceAddress());
+        Ip6Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
+        int sid = config.getIPv6SegmentId(destRouterAddress);
+        if (sid < 0) {
+            log.warn("Cannot find the Segment ID for {}", destIpAddress);
+            return;
+        }
+        sendPacketOut(outport, ethReply, sid, destIpAddress, icmpReplyIpv6.getHopLimit());
     }
 
     ///////////////////////////////////////////
@@ -273,7 +332,7 @@
          */
         if (isNdpForGateway(pkt)) {
             log.debug("Sending NDP reply on behalf of the router");
-            sendNdpReply(pkt, config.getRouterMacForAGatewayIp(pkt.target()));
+            sendResponse(pkt, config.getRouterMacForAGatewayIp(pkt.target()), hostService);
         } else {
             /*
              * ND request for an host. We do a search by Ip.
@@ -360,49 +419,37 @@
     }
 
     /**
-     * Utility to send a ND reply using the supplied information.
+     * Sends a NDP request for the target IP address to all ports except in-port.
      *
-     * @param pkt the ndp request
-     * @param targetMac the target mac
+     * @param deviceId Switch device ID
+     * @param targetAddress target IP address for ARP
+     * @param inPort in-port
      */
-    private void sendNdpReply(NeighbourMessageContext pkt, MacAddress targetMac) {
-        HostId dstId = HostId.hostId(pkt.srcMac(), pkt.vlan());
-        Host dst = srManager.hostService.getHost(dstId);
-        if (dst == null) {
-            log.warn("Cannot send NDP response to host {} - does not exist in the store",
-                     dstId);
-            return;
-        }
-        pkt.reply(targetMac);
+    public void sendNdpRequest(DeviceId deviceId, IpAddress targetAddress, ConnectPoint inPort) {
+        byte[] senderMacAddress = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+        byte[] senderIpAddress = new byte[Ip6Address.BYTE_LENGTH];
+        /*
+         * Retrieves device info.
+         */
+        getSenderInfo(senderMacAddress, senderIpAddress, deviceId, targetAddress);
+        /*
+         * We have to compute the dst mac address and dst
+         * ip address.
+         */
+        byte[] dstIp = IPv6.getSolicitNodeAddress(targetAddress.toOctets());
+        byte[] dstMac = IPv6.getMCastMacAddress(dstIp);
+        /*
+         * Creates the request.
+         */
+        Ethernet ndpRequest = NeighborSolicitation.buildNdpSolicit(
+                targetAddress.toOctets(),
+                senderIpAddress,
+                dstIp,
+                senderMacAddress,
+                dstMac,
+                VlanId.NONE
+        );
+        flood(ndpRequest, inPort, targetAddress);
     }
 
-    /*
-     * 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 packet and context information
-     */
-    private void flood(NeighbourMessageContext pkt) {
-        try {
-            srManager.deviceConfiguration
-                    .getSubnetPortsMap(pkt.inPort().deviceId())
-                    .forEach((subnet, ports) -> {
-                        if (subnet.contains(pkt.target())) {
-                            ports.stream()
-                                    .filter(portNumber -> portNumber != pkt.inPort().port())
-                                    .forEach(portNumber -> {
-                                        ConnectPoint outPoint = new ConnectPoint(
-                                                pkt.inPort().deviceId(),
-                                                portNumber
-                                        );
-                                        pkt.forward(outPoint);
-                                    });
-                        }
-                    });
-        } catch (DeviceConfigNotFoundException e) {
-            log.warn(e.getMessage()
-                             + " Cannot flood in subnet as device config not available"
-                             + " for device: " + pkt.inPort().deviceId());
-        }
-    }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
index b8e16a8..235c82e 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
@@ -16,16 +16,18 @@
 package org.onosproject.segmentrouting;
 
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.IP;
 import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.packet.DefaultOutboundPacket;
-import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
@@ -37,6 +39,7 @@
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.packet.IpAddress.Version.INET6;
 
 /**
  * Handler of IP packets that forwards IP packets that are sent to the controller,
@@ -47,7 +50,7 @@
     private static Logger log = LoggerFactory.getLogger(IpHandler.class);
     private SegmentRoutingManager srManager;
     private DeviceConfiguration config;
-    private ConcurrentHashMap<Ip4Address, ConcurrentLinkedQueue<IPv4>> ipPacketQueue;
+    private ConcurrentHashMap<IpAddress, ConcurrentLinkedQueue<IP>> ipPacketQueue;
 
     /**
      * Creates an IpHandler object.
@@ -61,6 +64,53 @@
     }
 
     /**
+     * Enqueues the packet using the destination address as key.
+     *
+     * @param ipPacket the ip packet to store
+     * @param destinationAddress the destination address
+     */
+    private void enqueuePacket(IP ipPacket, IpAddress destinationAddress) {
+
+        ipPacketQueue
+            .computeIfAbsent(destinationAddress, a -> new ConcurrentLinkedQueue<>())
+            .add(ipPacket);
+
+    }
+
+    /**
+     * Dequeues the packet using the destination address as key.
+     *
+     * @param ipPacket the ip packet to remove
+     * @param destinationAddress the destination address
+     */
+    public void dequeuePacket(IP ipPacket, IpAddress destinationAddress) {
+
+        if (ipPacketQueue.get(destinationAddress) == null) {
+            return;
+        }
+        ipPacketQueue.get(destinationAddress).remove(ipPacket);
+    }
+
+    /**
+     * Forwards the packet to a given host and deque the packet.
+     *
+     * @param deviceId the target device
+     * @param eth the packet to send
+     * @param dest the target host
+     */
+    private void forwardToHost(DeviceId deviceId, Ethernet eth, Host dest) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().
+                setOutput(dest.location().port()).build();
+        OutboundPacket packet = new DefaultOutboundPacket(deviceId,
+                                                          treatment, ByteBuffer.wrap(eth.serialize()));
+        srManager.packetService.emit(packet);
+    }
+
+    //////////////////////
+    //  IPv4 Handling  //
+    ////////////////////
+
+    /**
      * Processes incoming IP packets.
      *
      * If it is an IP packet for known host, then forward it to the host.
@@ -68,27 +118,24 @@
      * to the subnet.
      *
      * @param pkt incoming packet
+     * @param connectPoint the target device
      */
-    public void processPacketIn(InboundPacket pkt) {
-        Ethernet ethernet = pkt.parsed();
-        IPv4 ipv4 = (IPv4) ethernet.getPayload();
+    public void processPacketIn(IPv4 pkt, ConnectPoint connectPoint) {
 
-        ConnectPoint connectPoint = pkt.receivedFrom();
         DeviceId deviceId = connectPoint.deviceId();
-        Ip4Address destinationAddress =
-                Ip4Address.valueOf(ipv4.getDestinationAddress());
+        Ip4Address destinationAddress = Ip4Address.valueOf(pkt.getDestinationAddress());
 
         // IP packet for know hosts
         if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
             forwardPackets(deviceId, destinationAddress);
 
-        // IP packet for unknown host in the subnet of the router
+        // IP packet for unknown host in one of the configured subnets of the router
         } else if (config.inSameSubnet(deviceId, destinationAddress)) {
             srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint);
 
         // IP packets for unknown host
         } else {
-            log.debug("IP request for unknown host {} which is not in the subnet",
+            log.debug("IPv4 packet for unknown host {} which is not in the subnet",
                     destinationAddress);
             // Do nothing
         }
@@ -107,12 +154,8 @@
         if (ipPacket.getProtocol() == IPv4.PROTOCOL_TCP) {
             return;
         }
-
-        Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
-
-        ipPacketQueue
-            .computeIfAbsent(destIpAddress, a -> new ConcurrentLinkedQueue<>())
-            .add(ipPacket);
+        IpAddress destIpAddress = IpAddress.valueOf(ipPacket.getDestinationAddress());
+        enqueuePacket(ipPacket, destIpAddress);
     }
 
     /**
@@ -127,33 +170,30 @@
         if (ipPacketQueue.get(destIpAddress) == null) {
             return;
         }
-
-        for (IPv4 ipPacket : ipPacketQueue.get(destIpAddress)) {
-            Ip4Address destAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
-            if (config.inSameSubnet(deviceId, destAddress)) {
-                ipPacket.setTtl((byte) (ipPacket.getTtl() - 1));
-                ipPacket.setChecksum((short) 0);
-                for (Host dest: srManager.hostService.getHostsByIp(destIpAddress)) {
-                    Ethernet eth = new Ethernet();
-                    eth.setDestinationMACAddress(dest.mac());
-                    try {
-                        eth.setSourceMACAddress(config.getDeviceMac(deviceId));
-                    } catch (DeviceConfigNotFoundException e) {
-                        log.warn(e.getMessage()
-                                + " Skipping forwardPackets for this destination.");
-                        continue;
+        for (IP ipPacket : ipPacketQueue.get(destIpAddress)) {
+            if (ipPacket.getVersion() == ((byte) 4)) {
+                IPv4 ipv4Packet = (IPv4) ipPacket;
+                Ip4Address destAddress = Ip4Address.valueOf(ipv4Packet.getDestinationAddress());
+                if (config.inSameSubnet(deviceId, destAddress)) {
+                    ipv4Packet.setTtl((byte) (ipv4Packet.getTtl() - 1));
+                    ipv4Packet.setChecksum((short) 0);
+                    for (Host dest : srManager.hostService.getHostsByIp(destIpAddress)) {
+                        Ethernet eth = new Ethernet();
+                        eth.setDestinationMACAddress(dest.mac());
+                        try {
+                            eth.setSourceMACAddress(config.getDeviceMac(deviceId));
+                        } catch (DeviceConfigNotFoundException e) {
+                            log.warn(e.getMessage()
+                                             + " Skipping forwardPackets for this destination.");
+                            continue;
+                        }
+                        eth.setEtherType(Ethernet.TYPE_IPV4);
+                        eth.setPayload(ipv4Packet);
+                        forwardToHost(deviceId, eth, dest);
+                        ipPacketQueue.get(destIpAddress).remove(ipPacket);
                     }
-                    eth.setEtherType(Ethernet.TYPE_IPV4);
-                    eth.setPayload(ipPacket);
-
-                    TrafficTreatment treatment = DefaultTrafficTreatment.builder().
-                            setOutput(dest.location().port()).build();
-                    OutboundPacket packet = new DefaultOutboundPacket(deviceId,
-                            treatment, ByteBuffer.wrap(eth.serialize()));
-                    srManager.packetService.emit(packet);
                     ipPacketQueue.get(destIpAddress).remove(ipPacket);
                 }
-                ipPacketQueue.get(destIpAddress).remove(ipPacket);
             }
         }
     }
@@ -163,6 +203,53 @@
     ////////////////////
 
     /**
+     * Processes incoming IPv6 packets.
+     *
+     * If it is an IPv6 packet for known host, then forward it to the host.
+     * If it is an IPv6 packet for unknown host in subnet, then send an NDP request
+     * to the subnet.
+     *
+     * @param pkt incoming packet
+     * @param connectPoint the target device
+     */
+    public void processPacketIn(IPv6 pkt, ConnectPoint connectPoint) {
+
+        DeviceId deviceId = connectPoint.deviceId();
+        Ip6Address destinationAddress = Ip6Address.valueOf(pkt.getDestinationAddress());
+
+        // IPv6 packet for know hosts
+        if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
+            forwardPackets(deviceId, destinationAddress);
+
+            // IPv6 packet for unknown host in one of the configured subnets of the router
+        } else if (config.inSameSubnet(deviceId, destinationAddress)) {
+            srManager.icmpHandler.sendNdpRequest(deviceId, destinationAddress, connectPoint);
+
+            // IPv6 packets for unknown host
+        } else {
+            log.debug("IPv6 packet for unknown host {} which is not in the subnet",
+                      destinationAddress);
+        }
+    }
+
+    /**
+     * Adds the IPv6 packet to a buffer.
+     * The packets are forwarded to corresponding destination when the destination
+     * MAC address is known via NDP response.
+     *
+     * @param ipPacket IP packet to add to the buffer
+     */
+    public void addToPacketBuffer(IPv6 ipPacket) {
+
+        // Better not buffer TCP packets due to out-of-order packet transfer
+        if (ipPacket.getNextHeader() == IPv6.PROTOCOL_TCP) {
+            return;
+        }
+        IpAddress destIpAddress = IpAddress.valueOf(INET6, ipPacket.getDestinationAddress());
+        enqueuePacket(ipPacket, destIpAddress);
+    }
+
+    /**
      * Forwards IP packets in the buffer to the destination IP address.
      * It is called when the controller finds the destination MAC address
      * via NDP replies.
@@ -171,9 +258,35 @@
      * @param destIpAddress the destination ip address
      */
     public void forwardPackets(DeviceId deviceId, Ip6Address destIpAddress) {
-        /*
-         * TODO in the following commit.
-         */
+        if (ipPacketQueue.get(destIpAddress) == null) {
+            return;
+        }
+        for (IP ipPacket : ipPacketQueue.get(destIpAddress)) {
+            if (ipPacket.getVersion() == ((byte) 6)) {
+                IPv6 ipv6Packet = (IPv6) ipPacket;
+                Ip6Address destAddress = Ip6Address.valueOf(ipv6Packet.getDestinationAddress());
+                if (config.inSameSubnet(deviceId, destAddress)) {
+                    ipv6Packet.setHopLimit((byte) (ipv6Packet.getHopLimit() - 1));
+                    for (Host dest : srManager.hostService.getHostsByIp(destIpAddress)) {
+                        Ethernet eth = new Ethernet();
+                        eth.setDestinationMACAddress(dest.mac());
+                        try {
+                            eth.setSourceMACAddress(config.getDeviceMac(deviceId));
+                        } catch (DeviceConfigNotFoundException e) {
+                            log.warn(e.getMessage()
+                                             + " Skipping forwardPackets for this destination.");
+                            continue;
+                        }
+                        eth.setEtherType(Ethernet.TYPE_IPV6);
+                        eth.setPayload(ipv6Packet);
+                        forwardToHost(deviceId, eth, dest);
+                        ipPacketQueue.get(destIpAddress).remove(ipPacket);
+                    }
+                    ipPacketQueue.get(destIpAddress).remove(ipPacket);
+                }
+            }
+            ipPacketQueue.get(destIpAddress).remove(ipPacket);
+        }
     }
 
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 757e408..1ea185e 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -602,6 +602,12 @@
                     ? VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET)
                     : srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
 
+            if (assignedVlan == null) {
+                log.warn("Assigned vlan is null for {} in {} - Aborting populateRouterMacVlanFilters.",
+                         port.number(), deviceId);
+                return null;
+            }
+
             FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
             fob.withKey(Criteria.matchInPort(port.number()))
                 .addCondition(Criteria.matchEthDst(deviceMac))
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 1b8887b..6694aea 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -23,6 +23,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IPv6;
 import org.onlab.packet.Ip4Prefix;
@@ -35,10 +36,10 @@
 import org.onosproject.event.Event;
 import org.onosproject.incubator.net.config.basics.McastConfig;
 import org.onosproject.incubator.net.intf.InterfaceService;
-import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
 import org.onosproject.incubator.net.routing.RouteEvent;
 import org.onosproject.incubator.net.routing.RouteListener;
 import org.onosproject.incubator.net.routing.RouteService;
+import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -187,7 +188,7 @@
     HostHandler hostHandler = null;
     private CordConfigHandler cordConfigHandler = null;
     private RouteHandler routeHandler = null;
-    private SegmentRoutingNeighbourHandler neighbourHandler = null;
+    private SegmentRoutingNeighbourDispatcher neighbourHandler = null;
     private InternalEventHandler eventHandler = new InternalEventHandler();
     private final InternalHostListener hostListener = new InternalHostListener();
     private final InternalConfigListener cfgListener = new InternalConfigListener(this);
@@ -360,7 +361,7 @@
         hostHandler = new HostHandler(this);
         cordConfigHandler = new CordConfigHandler(this);
         routeHandler = new RouteHandler(this);
-        neighbourHandler = new SegmentRoutingNeighbourHandler(this);
+        neighbourHandler = new SegmentRoutingNeighbourDispatcher(this);
 
         cfgService.addListener(cfgListener);
         cfgService.registerConfigFactory(deviceConfigFactory);
@@ -656,10 +657,10 @@
                          context.inPacket().receivedFrom());
                 log.debug("{}", ethernet);
             } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) {
-                IPv4 ipPacket = (IPv4) ethernet.getPayload();
-                // ipHandler.addToPacketBuffer(ipPacket);
-                if (ipPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
-                    icmpHandler.processPacketIn(pkt);
+                IPv4 ipv4Packet = (IPv4) ethernet.getPayload();
+                //ipHandler.addToPacketBuffer(ipv4Packet);
+                if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_ICMP) {
+                    icmpHandler.processIcmp(ethernet, pkt.receivedFrom());
                 } else {
                     // NOTE: We don't support IP learning at this moment so this
                     //       is not necessary. Also it causes duplication of DHCP packets.
@@ -667,9 +668,24 @@
                 }
             } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV6) {
                 IPv6 ipv6Packet = (IPv6) ethernet.getPayload();
+                //ipHandler.addToPacketBuffer(ipv6Packet);
                 /*
-                 * TODO send to ICMPv6 handler and generalize the interaction with IP Handler
+                 * We deal with the packet only if the packet is a ICMP6 ECHO/REPLY
                  */
+                if (ipv6Packet.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
+                    ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload();
+                    if (icmp6Packet.getIcmpType() == ICMP6.ECHO_REQUEST ||
+                            icmp6Packet.getIcmpType() == ICMP6.ECHO_REPLY) {
+                        icmpHandler.processIcmpv6(ethernet, pkt.receivedFrom());
+                    } else {
+                        log.warn("Received ICMPv6 0x{} - not handled",
+                                 Integer.toHexString(icmp6Packet.getIcmpType() & 0xff));
+                    }
+                } else {
+                   // NOTE: We don't support IP learning at this moment so this
+                   //       is not necessary. Also it causes duplication of DHCPv6 packets.
+                   // ipHandler.processPacketIn(ipv6Packet, pkt.receivedFrom());
+                }
             }
         }
     }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourDispatcher.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourDispatcher.java
new file mode 100644
index 0000000..4eaac4e
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourDispatcher.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-present 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.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.segmentrouting;
+
+import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This handler dispatches to the appropriate handlers the
+ * neighbour discovery protocols.
+ */
+public class SegmentRoutingNeighbourDispatcher implements NeighbourMessageHandler {
+
+    private static Logger log = LoggerFactory.getLogger(SegmentRoutingNeighbourDispatcher.class);
+    private SegmentRoutingManager manager;
+
+    /**
+     * Create a segment routing neighbour dispatcher.
+     *
+     * @param segmentRoutingManager the segment routing manager
+     */
+    public SegmentRoutingNeighbourDispatcher(SegmentRoutingManager segmentRoutingManager) {
+        this.manager = segmentRoutingManager;
+    }
+
+    @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);
+                }
+                break;
+            case NDP:
+                if (this.manager.icmpHandler != null) {
+                    this.manager.icmpHandler.processPacketIn(context, hostService);
+                }
+                break;
+            default:
+                log.warn("Unknown protocol", context.protocol());
+        }
+    }
+
+
+}
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));
+    }
 
 }