Improves ping responder

Patch contains several bugfixes and improvements:
- Fixes sid retrieval when the destination leaf is down
- Fixes sid retrieval when ping goes through the spine
- Fixes MPLS deserializer
- Improves Ethernet toString
- Fixes ping to looback for dh host when bond sends to wrong leaf

Change-Id: I05963e74b2976e526826ffd377cadeb462ba0a8d
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index e7c9203..fc3dd17 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -44,8 +44,10 @@
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Handler of ICMP packets that responses or forwards ICMP packets that
@@ -69,49 +71,94 @@
      *
      * @param outport the output port
      * @param payload the packet to send
-     * @param sid the segment id
+     * @param destSid the segment id of the dest device
      * @param destIpAddress the destination ip address
      * @param allowedHops the hop limit/ttl
      */
     private void sendPacketOut(ConnectPoint outport,
                                Ethernet payload,
-                               int sid,
+                               int destSid,
                                IpAddress destIpAddress,
                                byte allowedHops) {
-        int destSid;
-        if (destIpAddress.isIp4()) {
-            destSid = config.getIPv4SegmentId(payload.getDestinationMAC());
-        } else {
-            destSid = config.getIPv6SegmentId(payload.getDestinationMAC());
+        int origSid;
+        try {
+            if (destIpAddress.isIp4()) {
+                origSid = config.getIPv4SegmentId(outport.deviceId());
+            } else {
+                origSid = config.getIPv6SegmentId(outport.deviceId());
+            }
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting sendPacketOut");
+            return;
         }
 
-        if (sid == -1 || destSid == sid ||
-                config.inSameSubnet(outport.deviceId(), destIpAddress)) {
+        if (destSid == -1 || origSid == destSid ||
+                srManager.interfaceService.isConfigured(outport)) {
             TrafficTreatment treatment = DefaultTrafficTreatment.builder().
                     setOutput(outport.port()).build();
             OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
                                                               treatment, ByteBuffer.wrap(payload.serialize()));
+            log.trace("Sending packet {} to {}", payload, outport);
             srManager.packetService.emit(packet);
         } else {
-            log.trace("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.setLabel(destSid);
             mplsPkt.setTtl(allowedHops);
             mplsPkt.setPayload(payload.getPayload());
             payload.setPayload(mplsPkt);
-
             OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
                                                               treatment, ByteBuffer.wrap(payload.serialize()));
-
+            log.trace("Sending packet {} to {}", payload, outport);
             srManager.packetService.emit(packet);
         }
     }
 
+    private IpAddress selectRouterIpAddress(IpAddress destIpAddress, ConnectPoint outPort,
+                                            Set<ConnectPoint> connectPoints) {
+        IpAddress routerIpAddress;
+        // Let's get first the online connect points
+        Set<ConnectPoint> onlineCps =  connectPoints.stream()
+                .filter(connectPoint -> srManager.deviceService.isAvailable(connectPoint.deviceId()))
+                .collect(Collectors.toSet());
+        // Check if ping is local
+        if (onlineCps.contains(outPort)) {
+            routerIpAddress = config.getRouterIpAddress(destIpAddress, outPort.deviceId());
+            log.trace("Local ping received from {} - send to {}", destIpAddress, routerIpAddress);
+            return routerIpAddress;
+        }
+        // Check if it comes from a remote device. Loopbacks are sorted comparing byte by byte
+        // FIXME if we lose both links from the chosen leaf to spine - ping will fail
+        routerIpAddress = onlineCps.stream()
+                .filter(onlineCp -> !onlineCp.deviceId().equals(outPort.deviceId()))
+                .map(selectedCp -> config.getRouterIpAddress(destIpAddress, selectedCp.deviceId()))
+                .filter(Objects::nonNull)
+                .sorted()
+                .findFirst().orElse(null);
+        if (routerIpAddress != null) {
+            log.trace("Remote ping received from {} - send to {}", destIpAddress, routerIpAddress);
+        } else {
+            log.warn("Not found a valid loopback for ping coming from {} - {}", destIpAddress, outPort);
+        }
+        return routerIpAddress;
+    }
+
+    private Ip4Address selectRouterIp4Address(IpAddress destIpAddress, ConnectPoint outPort,
+                                              Set<ConnectPoint> connectPoints) {
+        IpAddress routerIpAddress = selectRouterIpAddress(destIpAddress, outPort, connectPoints);
+        return routerIpAddress != null ? routerIpAddress.getIp4Address() : null;
+    }
+
+    private Ip6Address selectRouterIp6Address(IpAddress destIpAddress, ConnectPoint outPort,
+                                              Set<ConnectPoint> connectPoints) {
+        IpAddress routerIpAddress = selectRouterIpAddress(destIpAddress, outPort, connectPoints);
+        return routerIpAddress != null ? routerIpAddress.getIp6Address() : null;
+    }
+
     //////////////////////////////////////
     //     ICMP Echo/Reply Protocol     //
     //////////////////////////////////////
@@ -127,25 +174,40 @@
     public void processIcmp(Ethernet eth, ConnectPoint inPort) {
         DeviceId deviceId = inPort.deviceId();
         IPv4 ipv4Packet = (IPv4) eth.getPayload();
+        ICMP icmp = (ICMP) ipv4Packet.getPayload();
         Ip4Address destinationAddress = Ip4Address.valueOf(ipv4Packet.getDestinationAddress());
         Set<IpAddress> gatewayIpAddresses = config.getPortIPs(deviceId);
         IpAddress routerIp;
+
+        // Only proceed with echo request
+        if (icmp.getIcmpType() != ICMP.TYPE_ECHO_REQUEST) {
+            return;
+        }
+
         try {
             routerIp = config.getRouterIpv4(deviceId);
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage() + " Aborting processPacketIn.");
             return;
         }
+
+        // Get pair ip - if it exists
+        IpAddress pairRouterIp;
+        try {
+            DeviceId pairDeviceId = config.getPairDeviceId(deviceId);
+            pairRouterIp = pairDeviceId != null ? config.getRouterIpv4(pairDeviceId) : null;
+        } catch (DeviceConfigNotFoundException e) {
+            pairRouterIp = null;
+        }
+
         // ICMP to the router IP or gateway IP
-        if (((ICMP) ipv4Packet.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
-                (destinationAddress.equals(routerIp.getIp4Address()) ||
-                        gatewayIpAddresses.contains(destinationAddress))) {
+        if (destinationAddress.equals(routerIp.getIp4Address()) ||
+                (pairRouterIp != null && destinationAddress.equals(pairRouterIp.getIp4Address())) ||
+                gatewayIpAddresses.contains(destinationAddress)) {
             sendIcmpResponse(eth, inPort);
         } else {
             log.trace("Ignore ICMP that targets for {}", destinationAddress);
         }
-        // We remove the packet from the queue
-        srManager.ipHandler.dequeuePacket(ipv4Packet, destinationAddress);
     }
 
     /**
@@ -159,7 +221,11 @@
         IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload();
         IPv4 icmpReplyIpv4 = (IPv4) icmpReplyEth.getPayload();
         Ip4Address destIpAddress = Ip4Address.valueOf(icmpRequestIpv4.getSourceAddress());
-        Ip4Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
+
+        // Get the available connect points
+        Set<ConnectPoint> destConnectPoints = config.getConnectPointsForASubnetHost(destIpAddress);
+        // Select a router
+        Ip4Address destRouterAddress = selectRouterIp4Address(destIpAddress, outport, destConnectPoints);
 
         // Note: Source IP of the ICMP request doesn't belong to any configured subnet.
         //       The source might be an indirectly attached host (e.g. behind a router)
@@ -215,18 +281,29 @@
 
         try {
             routerIp = config.getRouterIpv6(deviceId);
-
-            Optional<Ip6Address> linkLocalIp = getLinkLocalIp(inPort);
-            // Ensure ICMP to the router IP, EUI-64 link-local IP, or gateway IP
-            if (destinationAddress.equals(routerIp.getIp6Address()) ||
-                    (linkLocalIp.isPresent() && destinationAddress.equals(linkLocalIp.get())) ||
-                    gatewayIpAddresses.contains(destinationAddress)) {
-                sendIcmpv6Response(eth, inPort);
-            } else {
-                log.trace("Ignore ICMPv6 that targets for {}", destinationAddress);
-            }
         } catch (DeviceConfigNotFoundException e) {
-            log.warn(e.getMessage() + " Ignore ICMPv6 that targets to {}.", destinationAddress);
+            log.warn(e.getMessage() + " Aborting processPacketIn.");
+            return;
+        }
+
+        // Get pair ip - if it exists
+        IpAddress pairRouterIp;
+        try {
+            DeviceId pairDeviceId = config.getPairDeviceId(deviceId);
+            pairRouterIp = pairDeviceId != null ? config.getRouterIpv6(pairDeviceId) : null;
+        } catch (DeviceConfigNotFoundException e) {
+            pairRouterIp = null;
+        }
+
+        Optional<Ip6Address> linkLocalIp = getLinkLocalIp(inPort);
+        // Ensure ICMP to the router IP, EUI-64 link-local IP, or gateway IP
+        if (destinationAddress.equals(routerIp.getIp6Address()) ||
+                (linkLocalIp.isPresent() && destinationAddress.equals(linkLocalIp.get())) ||
+                (pairRouterIp != null && destinationAddress.equals(pairRouterIp.getIp6Address())) ||
+                gatewayIpAddresses.contains(destinationAddress)) {
+            sendIcmpv6Response(eth, inPort);
+        } else {
+            log.trace("Ignore ICMPv6 that targets for {}", destinationAddress);
         }
     }
 
@@ -237,7 +314,7 @@
      * @param outport the output port where the ICMP reply should be sent to
      */
     private void sendIcmpv6Response(Ethernet ethRequest, ConnectPoint outport) {
-        int sid = -1;
+        int destSid = -1;
         Ethernet ethReply = ICMP6.buildIcmp6Reply(ethRequest);
         IPv6 icmpRequestIpv6 = (IPv6) ethRequest.getPayload();
         IPv6 icmpReplyIpv6 = (IPv6) ethRequest.getPayload();
@@ -246,14 +323,18 @@
         // Destination IP of the echo "reply"
         Ip6Address destIpAddress = Ip6Address.valueOf(icmpRequestIpv6.getSourceAddress());
         Optional<Ip6Address> linkLocalIp = getLinkLocalIp(outport);
-        Ip6Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
 
         // Fast path if the echo request targets the link-local address of switch interface
         if (linkLocalIp.isPresent() && srcIpAddress.equals(linkLocalIp.get())) {
-            sendPacketOut(outport, ethReply, sid, destIpAddress, icmpReplyIpv6.getHopLimit());
+            sendPacketOut(outport, ethReply, destSid, destIpAddress, icmpReplyIpv6.getHopLimit());
             return;
         }
 
+        // Get the available connect points
+        Set<ConnectPoint> destConnectPoints = config.getConnectPointsForASubnetHost(destIpAddress);
+        // Select a router
+        Ip6Address destRouterAddress = selectRouterIp6Address(destIpAddress, outport, destConnectPoints);
+
         // Note: Source IP of the ICMP request doesn't belong to any configured subnet.
         //       The source might be an indirect host behind a router.
         //       Lookup the route store for the nexthop instead.
@@ -272,16 +353,13 @@
             }
         }
 
-        // Search SID only if store lookup is success otherwise proceed with "sid=-1"
-        if (destRouterAddress != null) {
-            sid = config.getIPv6SegmentId(destRouterAddress);
-            if (sid < 0) {
-                log.warn("Failed to lookup SID of the switch that {} attaches to. " +
-                        "Unable to process ICMPv6 request.", destIpAddress);
-                return;
-            }
+        destSid = config.getIPv6SegmentId(destRouterAddress);
+        if (destSid < 0) {
+            log.warn("Failed to lookup SID of the switch that {} attaches to. " +
+                    "Unable to process ICMPv6 request.", destIpAddress);
+            return;
         }
-        sendPacketOut(outport, ethReply, sid, destIpAddress, icmpReplyIpv6.getHopLimit());
+        sendPacketOut(outport, ethReply, destSid, destIpAddress, icmpReplyIpv6.getHopLimit());
     }
 
     ///////////////////////////////////////////
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index 81a161f..123da37 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -351,6 +351,24 @@
         }
     }
 
+    /**
+     * Gets router ip address based on the destination ip address.
+     *
+     * @param destIpAddress the destination ip address
+     * @param routerDeviceId the device id
+     * @return the ip address of the routes
+     */
+    public IpAddress getRouterIpAddress(IpAddress destIpAddress, DeviceId routerDeviceId) {
+        IpAddress routerIpAddress;
+        try {
+            routerIpAddress = destIpAddress.isIp4() ? getRouterIpv4(routerDeviceId) :
+                    getRouterIpv6(routerDeviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            routerIpAddress = null;
+        }
+        return routerIpAddress;
+    }
+
     @Override
     public boolean isEdgeDevice(DeviceId deviceId) throws DeviceConfigNotFoundException {
         SegmentRouterInfo srinfo = deviceConfigMap.get(deviceId);
@@ -624,6 +642,19 @@
     }
 
     /**
+     * Returns all the connect points of the segment routers that have the
+     * specified ip address in their subnets.
+     *
+     * @param destIpAddress target ip address
+     * @return connect points of the segment routers
+     */
+    public Set<ConnectPoint> getConnectPointsForASubnetHost(IpAddress destIpAddress) {
+        return srManager.interfaceService.getMatchingInterfaces(destIpAddress).stream()
+                .map(Interface::connectPoint)
+                .collect(Collectors.toSet());
+    }
+
+    /**
      * Returns the router ip address of segment router that has the
      * specified ip address in its subnets.
      *
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java
new file mode 100644
index 0000000..cc7c6a8
--- /dev/null
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/IcmpHandlerTest.java
@@ -0,0 +1,794 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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 com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+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.MPLS;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.config.basics.InterfaceConfig;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.segmentrouting.config.DeviceConfiguration;
+import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
+import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
+
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+import static org.onlab.packet.ICMP.*;
+import static org.onlab.packet.ICMP6.ECHO_REPLY;
+import static org.onosproject.segmentrouting.TestUtils.*;
+
+/**
+ * Tests for IcmpHandler.
+ */
+public class IcmpHandlerTest {
+
+    private IcmpHandler icmpHandler;
+    private MockPacketService packetService;
+    private SegmentRoutingManager segmentRoutingManager;
+    private ApplicationId testApplicationId = TestApplicationId.create("test");
+
+    @Before
+    public void setUp() {
+
+        // Init
+        ObjectMapper mapper = new ObjectMapper();
+        ConfigApplyDelegate delegate = config -> { };
+
+        // Setup configuration for app
+        SegmentRoutingAppConfig appConfig = new SegmentRoutingAppConfig();
+        JsonNode appTree = mapper.createObjectNode();
+        appConfig.init(testApplicationId, "icmp-handler-test", appTree, mapper, delegate);
+        appConfig.setSuppressSubnet(Collections.emptySet());
+
+        // Setup configuration for the devices
+        SegmentRoutingDeviceConfig remoteLeafConfig = new SegmentRoutingDeviceConfig();
+        JsonNode remoteLeafTree = mapper.createObjectNode();
+        remoteLeafConfig.init(REMOTE_LEAF, "icmp-handler-test", remoteLeafTree, mapper, delegate);
+        remoteLeafConfig.setNodeSidIPv4(REMOTE_LEAF_SID4)
+                .setNodeSidIPv6(REMOTE_LEAF_SID6)
+                .setRouterIpv4(REMOTE_LEAF_LB4)
+                .setRouterIpv6(REMOTE_LEAF_LB6)
+                .setIsEdgeRouter(true)
+                .setRouterMac(REMOTE_MAC.toString());
+        SegmentRoutingDeviceConfig localLeafConfig = new SegmentRoutingDeviceConfig();
+        JsonNode localLeafTree = mapper.createObjectNode();
+        localLeafConfig.init(LOCAL_LEAF, "icmp-handler-test", localLeafTree, mapper, delegate);
+        localLeafConfig.setNodeSidIPv4(LOCAL_LEAF_SID4)
+                .setRouterIpv4(LOCAL_LEAF_LB4)
+                .setNodeSidIPv6(LOCAL_LEAF_SID6)
+                .setRouterIpv6(LOCAL_LEAF_LB6)
+                .setIsEdgeRouter(true)
+                .setRouterMac(LOCAL_MAC.toString());
+        SegmentRoutingDeviceConfig localLeaf1Config = new SegmentRoutingDeviceConfig();
+        JsonNode localLeaf1Tree = mapper.createObjectNode();
+        localLeaf1Config.init(LOCAL_LEAF1, "icmp-handler-test", localLeaf1Tree, mapper, delegate);
+        localLeaf1Config.setNodeSidIPv4(LOCAL_LEAF1_SID4)
+                .setRouterIpv4(LOCAL_LEAF1_LB4)
+                .setNodeSidIPv6(LOCAL_LEAF1_SID6)
+                .setRouterIpv6(LOCAL_LEAF1_LB6)
+                .setIsEdgeRouter(true)
+                .setRouterMac(LOCAL_MAC1.toString())
+                .setPairDeviceId(LOCAL_LEAF2)
+                .setPairLocalPort(P3);
+        SegmentRoutingDeviceConfig localLeaf2Config = new SegmentRoutingDeviceConfig();
+        JsonNode localLeaf2Tree = mapper.createObjectNode();
+        localLeaf2Config.init(LOCAL_LEAF2, "icmp-handler-test", localLeaf2Tree, mapper, delegate);
+        localLeaf2Config.setNodeSidIPv4(LOCAL_LEAF2_SID4)
+                .setRouterIpv4(LOCAL_LEAF2_LB4)
+                .setNodeSidIPv6(LOCAL_LEAF2_SID6)
+                .setRouterIpv6(LOCAL_LEAF2_LB6)
+                .setIsEdgeRouter(true)
+                .setRouterMac(LOCAL_MAC2.toString())
+                .setPairDeviceId(LOCAL_LEAF1)
+                .setPairLocalPort(P3);
+
+        // Setup configuration for ports
+        InterfaceConfig remoteLeafPorts1Config = new InterfaceConfig();
+        ArrayNode remoteLeafPorts1Tree = mapper.createArrayNode();
+        remoteLeafPorts1Config.init(CP12, "icmp-handler-test", remoteLeafPorts1Tree, mapper, delegate);
+        remoteLeafPorts1Config.addInterface(INTF1);
+        InterfaceConfig remoteLeafPorts2Config = new InterfaceConfig();
+        ArrayNode remoteLeafPorts2Tree = mapper.createArrayNode();
+        remoteLeafPorts2Config.init(CP13, "icmp-handler-test", remoteLeafPorts2Tree, mapper, delegate);
+        remoteLeafPorts2Config.addInterface(INTF2);
+        InterfaceConfig localLeafPortsConfig = new InterfaceConfig();
+        ArrayNode localLeafPortsTree = mapper.createArrayNode();
+        localLeafPortsConfig.init(CP1011, "icmp-handler-test", localLeafPortsTree, mapper, delegate);
+        localLeafPortsConfig.addInterface(INTF111);
+        InterfaceConfig localLeaf1PortsConfig = new InterfaceConfig();
+        ArrayNode localLeaf1PortsTree = mapper.createArrayNode();
+        localLeaf1PortsConfig.init(CP2011, "icmp-handler-test", localLeaf1PortsTree, mapper, delegate);
+        localLeaf1PortsConfig.addInterface(INTF211);
+        InterfaceConfig localLeaf2Ports1Config = new InterfaceConfig();
+        ArrayNode localLeaf2Ports1Tree = mapper.createArrayNode();
+        localLeaf2Ports1Config.init(CP2021, "icmp-handler-test", localLeaf2Ports1Tree, mapper, delegate);
+        localLeaf2Ports1Config.addInterface(INTF212);
+        InterfaceConfig localLeaf2Ports2Config = new InterfaceConfig();
+        ArrayNode localLeaf2Ports2Tree = mapper.createArrayNode();
+        localLeaf2Ports2Config.init(CP2024, "icmp-handler-test", localLeaf2Ports2Tree, mapper, delegate);
+        localLeaf2Ports2Config.addInterface(INTF213);
+
+        // Apply config
+        MockNetworkConfigRegistry mockNetworkConfigRegistry = new MockNetworkConfigRegistry();
+        mockNetworkConfigRegistry.applyConfig(remoteLeafConfig);
+        mockNetworkConfigRegistry.applyConfig(remoteLeafPorts1Config);
+        mockNetworkConfigRegistry.applyConfig(remoteLeafPorts2Config);
+        mockNetworkConfigRegistry.applyConfig(localLeafConfig);
+        mockNetworkConfigRegistry.applyConfig(localLeafPortsConfig);
+        mockNetworkConfigRegistry.applyConfig(localLeaf1Config);
+        mockNetworkConfigRegistry.applyConfig(localLeaf1PortsConfig);
+        mockNetworkConfigRegistry.applyConfig(localLeaf2Config);
+        mockNetworkConfigRegistry.applyConfig(localLeaf2Ports1Config);
+        mockNetworkConfigRegistry.applyConfig(localLeaf2Ports2Config);
+
+        segmentRoutingManager = new SegmentRoutingManager();
+        segmentRoutingManager.appId = testApplicationId;
+        packetService = new MockPacketService();
+        segmentRoutingManager.packetService = packetService;
+        segmentRoutingManager.cfgService = mockNetworkConfigRegistry;
+        segmentRoutingManager.neighbourResolutionService = new MockNeighbourResolutionService();
+        segmentRoutingManager.interfaceService = new MockInterfaceService(ImmutableSet.of(
+                INTF1, INTF2, INTF111, INTF211, INTF212, INTF213));
+        segmentRoutingManager.deviceConfiguration = new DeviceConfiguration(segmentRoutingManager);
+        segmentRoutingManager.ipHandler = new IpHandler(segmentRoutingManager);
+        segmentRoutingManager.deviceService = createMock(DeviceService.class);
+        segmentRoutingManager.routeService = new MockRouteService(ROUTE_STORE);
+        segmentRoutingManager.hostService = new MockHostService(Collections.emptySet());
+        icmpHandler = new IcmpHandler(segmentRoutingManager);
+    }
+
+    // Ping to our gateway
+    @Test
+    public void testPing4MyGateway() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(REMOTE_LEAF))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_MY, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_MY.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_MY.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_MY.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV4_MY.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to our gateway
+    @Test
+    public void testPing6MyGateway() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(REMOTE_LEAF))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_MY, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_MY.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_MY.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_MY.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV6_MY.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to a gateway attached to our leaf
+    @Test
+    public void testPing4LocalGateway() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(REMOTE_LEAF))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_LOCAL, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_LOCAL.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_LOCAL.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_LOCAL.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_LOCAL.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV4_MY.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to a gateway attached to our leaf
+    @Test
+    public void testPing6LocalGateway() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(REMOTE_LEAF))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_LOCAL, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_LOCAL.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_LOCAL.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_LOCAL.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_LOCAL.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV6_MY.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to a gateway attached only to the pair leaf (routing through spine)
+    @Test
+    public void testPing4RemoteGatewaySamePair() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_SAME, CP2025);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_SAME.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_SAME.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_SAME.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof MPLS);
+        MPLS mpls = (MPLS) ethernet.getPayload();
+        assertThat(mpls.getLabel(), is(LOCAL_LEAF1_SID4));
+        assertTrue(mpls.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) mpls.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_SAME.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV41.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to a gateway attached only to the pair leaf (routing through spine)
+    @Test
+    public void testPing6RemoteGatewaySamePair() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_SAME, CP2025);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_SAME.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_SAME.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_SAME.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof MPLS);
+        MPLS mpls = (MPLS) ethernet.getPayload();
+        assertThat(mpls.getLabel(), is(LOCAL_LEAF1_SID6));
+        assertTrue(mpls.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) mpls.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_SAME.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV61.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to a gateway but destination leaf is down
+    @Test
+    public void testPing4RemoteGatewayLeafDown() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF))
+                .andReturn(false)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4, CP11);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4.getSourceMAC());
+        assertNull(ethernet);
+
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to a gateway but destination leaf is down
+    @Test
+    public void testPing6RemoteGatewayLeafDown() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF))
+                .andReturn(false)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6, CP11);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6.getSourceMAC());
+        assertNull(ethernet);
+
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to a gateway but one of the destination leaf is down
+    @Test
+    public void testPing4RemoteGatewayLeaf1Down() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(false)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV41, CP11);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV41.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV41.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV41.getSourceMAC()));
+        assertThat(ethernet.getEtherType(), is(Ethernet.MPLS_UNICAST));
+        assertTrue(ethernet.getPayload() instanceof MPLS);
+        MPLS mpls = (MPLS) ethernet.getPayload();
+        assertThat(mpls.getLabel(), is(LOCAL_LEAF2_SID4));
+        assertTrue(mpls.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) mpls.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV41.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to a gateway but one of the destination leaf is down
+    @Test
+    public void testPing6RemoteGatewayLeaf2Down() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(false)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV61, CP11);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV61.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV61.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV61.getSourceMAC()));
+        assertThat(ethernet.getEtherType(), is(Ethernet.MPLS_UNICAST));
+        assertTrue(ethernet.getPayload() instanceof MPLS);
+        MPLS mpls = (MPLS) ethernet.getPayload();
+        assertThat(mpls.getLabel(), is(LOCAL_LEAF1_SID6));
+        assertTrue(mpls.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) mpls.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV61.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to a link local address
+    @Test
+    public void testPing6LinkLocalAddress() {
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_LL, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_LL.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_LL.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_LL.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_LL.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV6_LL.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+    }
+
+    // Ping to the looback of our leaf
+    @Test
+    public void testPing4Loopback() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(REMOTE_LEAF))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_LOOPBACK, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_LOOPBACK.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_LOOPBACK.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_LOOPBACK.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_LOOPBACK.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV4_MY.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to the looback of our leaf
+    @Test
+    public void testPing6Loopback() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(REMOTE_LEAF))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_LOOPBACK, CP12);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_LOOPBACK.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_LOOPBACK.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_LOOPBACK.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_LOOPBACK.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV6_MY.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to the looback of our leaf (pair)
+    @Test
+    public void testPing4LoopbackPair() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_LOOPBACK_PAIR, CP2011);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_LOOPBACK_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_LOOPBACK_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_LOOPBACK_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_LOOPBACK_PAIR.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV41.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to the looback of our leaf (pair)
+    @Test
+    public void testPing6LoopbackPair() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_LOOPBACK_PAIR, CP2021);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_LOOPBACK_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_LOOPBACK_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_LOOPBACK_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_LOOPBACK_PAIR.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV61.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to the loopback of the leaf but hashing of the bond interfaces sends to wrong leaf
+    @Test
+    public void testPing4LoopbackPairDifferentLeaf() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_LOOPBACK_PAIR, CP2021);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_LOOPBACK_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_LOOPBACK_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_LOOPBACK_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_LOOPBACK_PAIR.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV41.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to the loopback of the leaf but hashing of the bond interfaces sends to wrong leaf
+    @Test
+    public void testPing6LoopbackPairDifferentLeaf() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_LOOPBACK_PAIR, CP2011);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_LOOPBACK_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_LOOPBACK_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_LOOPBACK_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_LOOPBACK_PAIR.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV61.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping loopback of a destination that is down but
+    // hashing of the bond interfaces sends to other leaf
+    @Test
+    public void testPing4LoopbackPairDifferentLeafDown() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(false)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_LOOPBACK_PAIR, CP2021);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_LOOPBACK_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_LOOPBACK_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_LOOPBACK_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_LOOPBACK_PAIR.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV41.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 loopback of a destination that is down but
+    // hashing of the bond interfaces sends to other leaf
+    @Test
+    public void testPing6LoopbackPairDifferentLeafDown() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(false)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_LOOPBACK_PAIR, CP2011);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_LOOPBACK_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_LOOPBACK_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_LOOPBACK_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_LOOPBACK_PAIR.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV61.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping to a dh gateway
+    @Test
+    public void testPing4GatewayPair() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmp(ETH_REQ_IPV4_GATEWAY_PAIR, CP2011);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV4_GATEWAY_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV4_GATEWAY_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV4_GATEWAY_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv4);
+        IPv4 ip = (IPv4) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV4_GATEWAY_PAIR.toInt()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV41.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REPLY));
+        assertThat(icmp.getIcmpCode(), is(CODE_ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+    // Ping6 to a dh gateway
+    @Test
+    public void testPing6GatewayPair() {
+        // Expected behavior
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF1))
+                .andReturn(true)
+                .times(1);
+        expect(segmentRoutingManager.deviceService.isAvailable(LOCAL_LEAF2))
+                .andReturn(true)
+                .times(1);
+        replay(segmentRoutingManager.deviceService);
+
+        // Process
+        icmpHandler.processIcmpv6(ETH_REQ_IPV6_GATEWAY_PAIR, CP2021);
+
+        // Verify packet-out
+        Ethernet ethernet = packetService.getEthernetPacket(ETH_REQ_IPV6_GATEWAY_PAIR.getSourceMAC());
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_REQ_IPV6_GATEWAY_PAIR.getDestinationMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_REQ_IPV6_GATEWAY_PAIR.getSourceMAC()));
+        assertTrue(ethernet.getPayload() instanceof IPv6);
+        IPv6 ip = (IPv6) ethernet.getPayload();
+        assertThat(ip.getSourceAddress(), is(DST_IPV6_GATEWAY_PAIR.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(SRC_IPV61.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REPLY));
+        // Verify behavior
+        verify(segmentRoutingManager.deviceService);
+    }
+
+}
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java
index 17422d2..6e170b0 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java
@@ -17,12 +17,17 @@
 package org.onosproject.segmentrouting;
 
 import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.impl.InterfaceManager;
 
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.toSet;
 
 /**
  * Mock Interface Service.
@@ -44,4 +49,37 @@
     public Set<Interface> getInterfaces() {
         return interfaces;
     }
+
+    @Override
+    public Interface getMatchingInterface(IpAddress ip) {
+        return getMatchingInterfacesStream(ip).findFirst().orElse(null);
+    }
+
+    @Override
+    public Set<Interface> getMatchingInterfaces(IpAddress ip) {
+        return getMatchingInterfacesStream(ip).collect(toSet());
+    }
+
+    private Stream<Interface> getMatchingInterfacesStream(IpAddress ip) {
+        return interfaces.stream()
+                .filter(intf -> intf.ipAddressesList().stream()
+                        .anyMatch(intfIp -> intfIp.subnetAddress().contains(ip)));
+    }
+
+    @Override
+    public boolean isConfigured(ConnectPoint connectPoint) {
+        Set<Interface> intfs = getInterfacesByPort(connectPoint);
+        if (intfs == null) {
+            return false;
+        }
+        for (Interface intf : intfs) {
+            if (!intf.ipAddressesList().isEmpty() || intf.vlan() != VlanId.NONE
+                    || intf.vlanNative() != VlanId.NONE
+                    || intf.vlanUntagged() != VlanId.NONE
+                    || !intf.vlanTagged().isEmpty()) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNeighbourResolutionService.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNeighbourResolutionService.java
new file mode 100644
index 0000000..5a41149
--- /dev/null
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNeighbourResolutionService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.neighbour.NeighbourHandlerRegistration;
+import org.onosproject.net.neighbour.NeighbourMessageHandler;
+import org.onosproject.net.neighbour.NeighbourResolutionService;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Mock Neighbour Resolution Service.
+ */
+public class MockNeighbourResolutionService implements NeighbourResolutionService {
+    @Override
+    public void registerNeighbourHandler(ConnectPoint connectPoint,
+                                         NeighbourMessageHandler handler, ApplicationId appId) {
+
+    }
+
+    @Override
+    public void registerNeighbourHandler(Interface intf,
+                                         NeighbourMessageHandler handler, ApplicationId appId) {
+
+    }
+
+    @Override
+    public void unregisterNeighbourHandler(ConnectPoint connectPoint,
+                                           NeighbourMessageHandler handler, ApplicationId appId) {
+
+    }
+
+    @Override
+    public void unregisterNeighbourHandler(Interface intf,
+                                           NeighbourMessageHandler handler, ApplicationId appId) {
+
+    }
+
+    @Override
+    public void unregisterNeighbourHandlers(ApplicationId appId) {
+
+    }
+
+    @Override
+    public Map<ConnectPoint, Collection<NeighbourHandlerRegistration>> getHandlerRegistrations() {
+        return null;
+    }
+}
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java
index 09f8da2..e18483e 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockNetworkConfigRegistry.java
@@ -16,10 +16,12 @@
 
 package org.onosproject.segmentrouting;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.config.NetworkConfigRegistryAdapter;
 
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -28,7 +30,7 @@
 class MockNetworkConfigRegistry extends NetworkConfigRegistryAdapter {
     private Set<Config> configs = Sets.newHashSet();
 
-    public void applyConfig(Config config) {
+    void applyConfig(Config config) {
         configs.add(config);
     }
 
@@ -40,4 +42,16 @@
                 .findFirst().orElse(null);
         return (C) c;
     }
+
+    @Override
+    public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subject, Class<C> configClass) {
+        ImmutableSet.Builder<S> builder = ImmutableSet.builder();
+        String cName = configClass.getName();
+        configs.forEach(k -> {
+            if (subject.isInstance(k.subject()) && Objects.equals(cName, k.getClass().getName())) {
+                builder.add((S) k.subject());
+            }
+        });
+        return builder.build();
+    }
 }
\ No newline at end of file
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockPacketService.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockPacketService.java
new file mode 100644
index 0000000..73a8b65
--- /dev/null
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockPacketService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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 com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketServiceAdapter;
+
+import java.util.Map;
+
+/**
+ * Mock Packet Service.
+ * It is used for tests related to packet-ins management.
+ */
+public class MockPacketService extends PacketServiceAdapter {
+
+    private final Map<MacAddress, Pair<OutboundPacket, Ethernet>> outBoundPackets = Maps.newHashMap();
+
+    @Override
+    public void emit(OutboundPacket packet) {
+        try {
+            Ethernet ethernetPacket = Ethernet.deserializer().deserialize(packet.data().array(),
+                                                                          packet.data().arrayOffset(),
+                                                                          packet.data().array().length);
+            outBoundPackets.put(ethernetPacket.getDestinationMAC(), Pair.of(packet, ethernetPacket));
+        } catch (DeserializationException e) {
+
+        }
+    }
+
+    Ethernet getEthernetPacket(MacAddress key) {
+        Pair<OutboundPacket, Ethernet> pair = outBoundPackets.get(key);
+        return pair != null ? pair.getRight() : null;
+    }
+}
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java
index bfeca1f..c3730dd 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRouteService.java
@@ -17,6 +17,7 @@
 package org.onosproject.segmentrouting;
 
 import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onosproject.routeservice.ResolvedRoute;
 import org.onosproject.routeservice.RouteInfo;
@@ -25,6 +26,7 @@
 
 import java.util.Collection;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -53,4 +55,10 @@
     public Collection<RouteTableId> getRouteTables() {
         return Sets.newHashSet(new RouteTableId("default"));
     }
+
+    @Override
+    public Optional<ResolvedRoute> longestPrefixLookup(IpAddress ip) {
+        return this.routeStore.get(ip.toIpPrefix()).stream()
+                .findFirst();
+    }
 }
\ No newline at end of file
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/TestUtils.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/TestUtils.java
new file mode 100644
index 0000000..339b2c8
--- /dev/null
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/TestUtils.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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 com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.Route;
+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;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ICMPEcho;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.onlab.packet.ICMP.TYPE_ECHO_REQUEST;
+import static org.onlab.packet.ICMP6.ECHO_REQUEST;
+import static org.onlab.packet.IPv4.PROTOCOL_ICMP;
+import static org.onosproject.routeservice.Route.Source.STATIC;
+
+/**
+ * Utilities class for unit tests.
+ */
+public final class TestUtils {
+
+    private TestUtils() {
+
+    }
+
+    // Device configuration section
+    static final DeviceId REMOTE_LEAF = DeviceId.deviceId("of:0000000000000001");
+    static final int REMOTE_LEAF_SID4 = 1;
+    static final String REMOTE_LEAF_LB4 = "192.168.0.1";
+    static final int REMOTE_LEAF_SID6 = 10;
+    static final String REMOTE_LEAF_LB6 = "2000::c0a8:1";
+    private static final PortNumber P1 = PortNumber.portNumber(1);
+    static final MacAddress REMOTE_MAC = MacAddress.valueOf("00:00:00:00:00:02");
+
+    static final DeviceId LOCAL_LEAF = DeviceId.deviceId("of:0000000000000101");
+    static final int LOCAL_LEAF_SID4 = 101;
+    static final String LOCAL_LEAF_LB4 = "192.168.0.101";
+    static final int LOCAL_LEAF_SID6 = 111;
+    static final String LOCAL_LEAF_LB6 = "2000::c0a8:101";
+    static final MacAddress LOCAL_MAC = MacAddress.valueOf("00:00:00:00:01:01");
+
+    // Configure a pair
+    static final DeviceId LOCAL_LEAF1 = DeviceId.deviceId("of:0000000000000201");
+    static final int LOCAL_LEAF1_SID4 = 201;
+    static final String LOCAL_LEAF1_LB4 = "192.168.0.201";
+    static final int LOCAL_LEAF1_SID6 = 211;
+    static final String LOCAL_LEAF1_LB6 = "2000::c0a8:201";
+    static final MacAddress LOCAL_MAC1 = MacAddress.valueOf("00:00:00:00:02:01");
+
+    static final DeviceId LOCAL_LEAF2 = DeviceId.deviceId("of:0000000000000202");
+    static final int LOCAL_LEAF2_SID4 = 202;
+    static final String LOCAL_LEAF2_LB4 = "192.168.0.202";
+    static final int LOCAL_LEAF2_SID6 = 212;
+    static final String LOCAL_LEAF2_LB6 = "2000::c0a8:202";
+    static final MacAddress LOCAL_MAC2 = MacAddress.valueOf("00:00:00:00:02:02");
+
+    // Pair port
+    static final PortNumber P3 = PortNumber.portNumber(3);
+
+    // Ports configuration section
+    static final ConnectPoint CP11 = new ConnectPoint(REMOTE_LEAF, P1);
+    private static final PortNumber P2 = PortNumber.portNumber(2);
+    static final ConnectPoint CP12 = new ConnectPoint(REMOTE_LEAF, P2);
+    private static final IpAddress IP4_1 = IpAddress.valueOf("10.0.0.254");
+    private static final IpPrefix PREFIX4_1 = IpPrefix.valueOf("10.0.0.254/24");
+    private static final IpAddress IP6_1 = IpAddress.valueOf("2000::ff");
+    private static final IpPrefix PREFIX6_1 = IpPrefix.valueOf("2000::ff/120");
+    private static final InterfaceIpAddress INTF_IP4_1 = new InterfaceIpAddress(
+            IP4_1, PREFIX4_1);
+    private static final InterfaceIpAddress INTF_IP6_1 = new InterfaceIpAddress(
+            IP6_1, PREFIX6_1);
+    private static final VlanId INTF_VLAN_UNTAGGED = VlanId.vlanId((short) 10);
+    static final Interface INTF1 = new Interface(
+            "INTF1", CP12, Lists.newArrayList(INTF_IP4_1, INTF_IP6_1), MacAddress.NONE,
+            null, INTF_VLAN_UNTAGGED, null, null);
+    static final ConnectPoint CP13 = new ConnectPoint(REMOTE_LEAF, P3);
+    private static final IpAddress IP4_2 = IpAddress.valueOf("10.0.3.254");
+    private static final IpPrefix PREFIX4_2 = IpPrefix.valueOf("10.0.3.254/24");
+    private static final IpAddress IP6_2 = IpAddress.valueOf("2000::3ff");
+    private static final IpPrefix PREFIX6_2 = IpPrefix.valueOf("2000::3ff/120");
+    private static final InterfaceIpAddress INTF_IP4_2 = new InterfaceIpAddress(
+            IP4_2, PREFIX4_2);
+    private static final InterfaceIpAddress INTF_IP6_2 = new InterfaceIpAddress(
+            IP6_2, PREFIX6_2);
+    static final Interface INTF2 = new Interface(
+            "INTF2", CP13, Lists.newArrayList(INTF_IP4_2, INTF_IP6_2), MacAddress.NONE,
+            null, INTF_VLAN_UNTAGGED, null, null);
+
+    static final ConnectPoint CP1011 = new ConnectPoint(LOCAL_LEAF, P1);
+    private static final IpAddress IP4_11 = IpAddress.valueOf("10.0.1.254");
+    private static final IpPrefix PREFIX4_11 = IpPrefix.valueOf("10.0.1.254/24");
+    private static final InterfaceIpAddress INTF_IP4_11 = new InterfaceIpAddress(
+            IP4_11, PREFIX4_11);
+    private static final IpAddress IP6_11 = IpAddress.valueOf("2000::1ff");
+    private static final IpPrefix PREFIX6_11 = IpPrefix.valueOf("2000::1ff/120");
+    private static final InterfaceIpAddress INTF_IP6_11 = new InterfaceIpAddress(
+            IP6_11, PREFIX6_11);
+    static final Interface INTF111 = new Interface(
+            "INTF111", CP1011, Lists.newArrayList(INTF_IP4_11, INTF_IP6_11), MacAddress.NONE, null,
+            INTF_VLAN_UNTAGGED, null, null);
+
+    static final ConnectPoint CP2011 = new ConnectPoint(LOCAL_LEAF1, P1);
+    private static final IpAddress IP4_21 = IpAddress.valueOf("10.0.2.254");
+    private static final IpPrefix PREFIX4_21 = IpPrefix.valueOf("10.0.2.254/24");
+    private static final InterfaceIpAddress INTF_IP4_21 = new InterfaceIpAddress(
+            IP4_21, PREFIX4_21);
+    private static final IpAddress IP6_21 = IpAddress.valueOf("2000::2ff");
+    private static final IpPrefix PREFIX6_21 = IpPrefix.valueOf("2000::2ff/120");
+    private static final InterfaceIpAddress INTF_IP6_21 = new InterfaceIpAddress(
+            IP6_21, PREFIX6_21);
+    static final Interface INTF211 = new Interface(
+            "INTF211", CP2011, Lists.newArrayList(INTF_IP4_21, INTF_IP6_21), MacAddress.NONE, null,
+            INTF_VLAN_UNTAGGED, null, null);
+
+    static final ConnectPoint CP2021 = new ConnectPoint(LOCAL_LEAF2, P1);
+    private static final IpAddress IP4_22 = IpAddress.valueOf("10.0.2.254");
+    private static final IpPrefix PREFIX4_22 = IpPrefix.valueOf("10.0.2.254/24");
+    private static final InterfaceIpAddress INTF_IP4_22 = new InterfaceIpAddress(
+            IP4_22, PREFIX4_22);
+    private static final IpAddress IP6_22 = IpAddress.valueOf("2000::2ff");
+    private static final IpPrefix PREFIX6_22 = IpPrefix.valueOf("2000::2ff/120");
+    private static final InterfaceIpAddress INTF_IP6_22 = new InterfaceIpAddress(
+            IP6_22, PREFIX6_22);
+    static final Interface INTF212 = new Interface(
+            "INTF212", CP2021, Lists.newArrayList(INTF_IP4_22, INTF_IP6_22), MacAddress.NONE, null,
+            INTF_VLAN_UNTAGGED, null, null);
+    private static final PortNumber P4 = PortNumber.portNumber(4);
+    static final ConnectPoint CP2024 = new ConnectPoint(LOCAL_LEAF2, P4);
+    private static final PortNumber P5 = PortNumber.portNumber(5);
+    static final ConnectPoint CP2025 = new ConnectPoint(LOCAL_LEAF2, P5);
+    private static final IpAddress IP4_23 = IpAddress.valueOf("10.0.4.254");
+    private static final IpPrefix PREFIX4_23 = IpPrefix.valueOf("10.0.4.254/24");
+    private static final InterfaceIpAddress INTF_IP4_23 = new InterfaceIpAddress(
+            IP4_23, PREFIX4_23);
+    private static final IpAddress IP6_23 = IpAddress.valueOf("2000::4ff");
+    private static final IpPrefix PREFIX6_23 = IpPrefix.valueOf("2000::4ff/120");
+    private static final InterfaceIpAddress INTF_IP6_23 = new InterfaceIpAddress(
+            IP6_23, PREFIX6_23);
+    static final Interface INTF213 = new Interface(
+            "INTF212", CP2024, Lists.newArrayList(INTF_IP4_23, INTF_IP6_23), MacAddress.NONE, null,
+            INTF_VLAN_UNTAGGED, null, null);
+
+    // Packet-ins section
+    private static final MacAddress SRC_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+
+    private static final ICMPEcho ICMP_ECHO = new ICMPEcho()
+            .setIdentifier((short) 0)
+            .setSequenceNum((short) 0);
+
+    private static final ICMP ICMP_REQUEST = (ICMP) new ICMP()
+            .setIcmpType(TYPE_ECHO_REQUEST)
+            .setPayload(ICMP_ECHO);
+
+    private static final Ip4Address SRC_IPV4 = Ip4Address.valueOf("10.0.1.1");
+    static final Ip4Address DST_IPV4 = Ip4Address.valueOf("10.0.0.254");
+
+    private static final IPv4 IPV4_REQUEST = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4.toInt())
+            .setSourceAddress(SRC_IPV4.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4 = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV4_REQUEST);
+
+    private static final ICMP6 ICMP6_REQUEST = new ICMP6()
+            .setIcmpType(ECHO_REQUEST);
+
+    private static final Ip6Address SRC_IPV6 = Ip6Address.valueOf("2000::101");
+    static final Ip6Address DST_IPV6 = Ip6Address.valueOf("2000::ff");
+
+    private static final IPv6 IPV6_REQUEST = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6.toOctets())
+            .setSourceAddress(SRC_IPV6.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6 = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV6_REQUEST);
+
+    static final Ip4Address SRC_IPV41 = Ip4Address.valueOf("10.0.2.1");
+
+    private static final IPv4 IPV41_REQUEST = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4.toInt())
+            .setSourceAddress(SRC_IPV41.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV41 = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV41_REQUEST);
+
+    static final Ip6Address SRC_IPV61 = Ip6Address.valueOf("2000::201");
+
+    private static final IPv6 IPV61_REQUEST = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6.toOctets())
+            .setSourceAddress(SRC_IPV61.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV61 = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV61_REQUEST);
+
+    private static final MacAddress SRC_MAC_MY = MacAddress.valueOf("00:01:00:00:00:01");
+    static final Ip4Address SRC_IPV4_MY = Ip4Address.valueOf("10.0.0.1");
+
+    private static final IPv4 IPV4_REQUEST_MY = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4.toInt())
+            .setSourceAddress(SRC_IPV4_MY.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4_MY = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV4_REQUEST_MY);
+
+    static final Ip6Address SRC_IPV6_MY = Ip6Address.valueOf("2000::1");
+
+    private static final IPv6 IPV6_REQUEST_MY = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6.toOctets())
+            .setSourceAddress(SRC_IPV6_MY.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_MY = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV6_REQUEST_MY);
+
+    static final Ip4Address DST_IPV4_LOCAL = Ip4Address.valueOf("10.0.3.254");
+
+    private static final IPv4 IPV4_REQUEST_LOCAL = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4_LOCAL.toInt())
+            .setSourceAddress(SRC_IPV4_MY.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4_LOCAL = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV4_REQUEST_LOCAL);
+
+    static final Ip6Address DST_IPV6_LOCAL = Ip6Address.valueOf("2000::3ff");
+
+    private static final IPv6 IPV6_REQUEST_LOCAL = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6_LOCAL.toOctets())
+            .setSourceAddress(SRC_IPV6_MY.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_LOCAL = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV6_REQUEST_LOCAL);
+
+    static final Ip4Address DST_IPV4_SAME = Ip4Address.valueOf("10.0.4.254");
+
+    private static final IPv4 IPV4_REQUEST_SAME = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4_SAME.toInt())
+            .setSourceAddress(SRC_IPV41.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4_SAME = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(LOCAL_MAC2)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV4_REQUEST_SAME);
+
+    static final Ip6Address DST_IPV6_SAME = Ip6Address.valueOf("2000::4ff");
+
+    private static final IPv6 IPV6_REQUEST_SAME = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6_SAME.toOctets())
+            .setSourceAddress(SRC_IPV61.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_SAME = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(LOCAL_MAC2)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV6_REQUEST_SAME);
+
+    static final Ip6Address DST_IPV6_LL = Ip6Address.valueOf(
+            IPv6.getLinkLocalAddress(MacAddress.NONE.toBytes()));
+    static final Ip6Address SRC_IPV6_LL = Ip6Address.valueOf(
+            IPv6.getLinkLocalAddress(SRC_MAC_MY.toBytes()));
+
+    private static final IPv6 IPV6_REQUEST_LL = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6_LL.toOctets())
+            .setSourceAddress(SRC_IPV6_LL.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_LL = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(MacAddress.NONE)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV6_REQUEST_LL);
+
+    static final Ip4Address DST_IPV4_LOOPBACK = Ip4Address.valueOf(REMOTE_LEAF_LB4);
+
+    private static final IPv4 IPV4_REQUEST_LOOPBACK = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4_LOOPBACK.toInt())
+            .setSourceAddress(SRC_IPV4_MY.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4_LOOPBACK = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV4_REQUEST_LOOPBACK);
+
+    static final Ip6Address DST_IPV6_LOOPBACK = Ip6Address.valueOf(REMOTE_LEAF_LB6);
+
+    private static final IPv6 IPV6_REQUEST_LOOPBACK = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6_LOOPBACK.toOctets())
+            .setSourceAddress(SRC_IPV6_MY.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_LOOPBACK = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(REMOTE_MAC)
+            .setSourceMACAddress(SRC_MAC_MY)
+            .setPayload(IPV6_REQUEST_LOOPBACK);
+
+    static final Ip4Address DST_IPV4_LOOPBACK_PAIR = Ip4Address.valueOf(LOCAL_LEAF1_LB4);
+
+    private static final IPv4 IPV4_REQUEST_LOOPBACK_PAIR = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4_LOOPBACK_PAIR.toInt())
+            .setSourceAddress(SRC_IPV41.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4_LOOPBACK_PAIR = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(LOCAL_MAC1)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV4_REQUEST_LOOPBACK_PAIR);
+
+    static final Ip6Address DST_IPV6_LOOPBACK_PAIR = Ip6Address.valueOf(LOCAL_LEAF2_LB6);
+
+    private static final IPv6 IPV6_REQUEST_LOOPBACK_PAIR = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6_LOOPBACK_PAIR.toOctets())
+            .setSourceAddress(SRC_IPV61.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_LOOPBACK_PAIR = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(LOCAL_MAC2)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV6_REQUEST_LOOPBACK_PAIR);
+
+    static final Ip4Address DST_IPV4_GATEWAY_PAIR = Ip4Address.valueOf("10.0.2.254");
+
+    private static final IPv4 IPV4_REQUEST_GATEWAY_PAIR = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4_GATEWAY_PAIR.toInt())
+            .setSourceAddress(SRC_IPV41.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV4_GATEWAY_PAIR = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV4)
+            .setDestinationMACAddress(LOCAL_MAC1)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV4_REQUEST_GATEWAY_PAIR);
+
+    static final Ip6Address DST_IPV6_GATEWAY_PAIR = Ip6Address.valueOf("2000::2ff");
+
+    private static final IPv6 IPV6_REQUEST_GATEWAY_PAIR = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6_GATEWAY_PAIR.toOctets())
+            .setSourceAddress(SRC_IPV61.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6_REQUEST);
+
+    static final Ethernet ETH_REQ_IPV6_GATEWAY_PAIR = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.TYPE_IPV6)
+            .setDestinationMACAddress(LOCAL_MAC2)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(IPV6_REQUEST_GATEWAY_PAIR);
+
+    // Resolved route
+    private static final ResolvedRoute IPV4_ROUTE = new ResolvedRoute(
+            new Route(STATIC, SRC_IPV4.toIpPrefix(), SRC_IPV4), MacAddress.NONE);
+    private static final ResolvedRoute IPV6_ROUTE = new ResolvedRoute(
+            new Route(STATIC, SRC_IPV6.toIpPrefix(), SRC_IPV6), MacAddress.NONE);
+    static final Map<IpPrefix, Set<ResolvedRoute>> ROUTE_STORE = ImmutableMap.of(SRC_IPV4.toIpPrefix(),
+                                                                                 Sets.newHashSet(IPV4_ROUTE),
+                                                                                 SRC_IPV6.toIpPrefix(),
+                                                                                 Sets.newHashSet(IPV6_ROUTE));
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
index 5fd07bf..fb249a9 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -514,13 +514,13 @@
      */
     @Override
     public String toString() {
-
         final StringBuilder sb = new StringBuilder("\n");
-
         final IPacket pkt = this.getPayload();
 
         if (pkt instanceof ARP) {
             sb.append("arp");
+        } else if (pkt instanceof MPLS) {
+            sb.append("mpls");
         } else if (pkt instanceof LLDP) {
             sb.append("lldp");
         } else if (pkt instanceof ICMP) {
@@ -530,11 +530,7 @@
         } else if (pkt instanceof DHCP) {
             sb.append("dhcp");
         } else {
-            /*
-             * When we don't know the protocol, we print using
-             * the well known hex format instead of a decimal
-             * value.
-             */
+            // Just print the ethertype
             sb.append(String.format(HEX_PROTO,
                                     Integer.toHexString(this.getEtherType() & 0xffff)));
         }
@@ -567,6 +563,12 @@
             sb.append("\nnw_dst: ");
             sb.append(IPv4.fromIPv4Address(IPv4.toIPv4Address(p
                     .getTargetProtocolAddress())));
+        } else if (pkt instanceof MPLS) {
+            final MPLS p = (MPLS) pkt;
+            sb.append("\nmpls: ");
+            sb.append(this.etherType == MPLS_UNICAST ? "unicast" : "multicast");
+            sb.append("\nmpls_label: ");
+            sb.append(p.label);
         } else if (pkt instanceof LLDP) {
             sb.append("lldp packet");
         } else if (pkt instanceof ICMP) {
@@ -593,7 +595,6 @@
                     sb.append(((TCP) payload).getSourcePort());
                     sb.append("\ntp_dst: ");
                     sb.append(((TCP) payload).getDestinationPort());
-
                 } else if (payload instanceof UDP) {
                     sb.append("\ntp_src: ");
                     sb.append(((UDP) payload).getSourcePort());
@@ -705,7 +706,6 @@
         } else {
             sb.append("\nunknown packet");
         }
-
         return sb.toString();
     }
 
diff --git a/utils/misc/src/main/java/org/onlab/packet/MPLS.java b/utils/misc/src/main/java/org/onlab/packet/MPLS.java
index 948075b..07ead0c 100644
--- a/utils/misc/src/main/java/org/onlab/packet/MPLS.java
+++ b/utils/misc/src/main/java/org/onlab/packet/MPLS.java
@@ -30,13 +30,15 @@
     public static final int HEADER_LENGTH = 4;
 
     public static final byte PROTOCOL_IPV4 = 0x1;
+    public static final byte PROTOCOL_IPV6 = 0x2;
     public static final byte PROTOCOL_MPLS = 0x6;
     // mutable for Testing
     static Map<Byte, Deserializer<? extends IPacket>> protocolDeserializerMap =
             ImmutableMap.<Byte, Deserializer<? extends IPacket>>builder()
-                .put(PROTOCOL_IPV4, IPv4.deserializer())
-                .put(PROTOCOL_MPLS, MPLS.deserializer())
-                .build();
+                    .put(PROTOCOL_IPV6, IPv6.deserializer())
+                    .put(PROTOCOL_IPV4, IPv4.deserializer())
+                    .put(PROTOCOL_MPLS, MPLS.deserializer())
+                    .build();
 
     protected int label; //20bits
     protected byte bos; //1bit
@@ -108,6 +110,22 @@
         this.ttl = ttl;
     }
 
+    @Override
+    public IPacket setPayload(final IPacket payload) {
+        // We implicitly assume that traffic can be only of these three types
+        if (payload instanceof MPLS) {
+            this.bos = 0;
+            this.protocol = PROTOCOL_MPLS;
+        } else if (payload instanceof IPv6) {
+            this.bos = 1;
+            this.protocol = PROTOCOL_IPV6;
+        } else {
+            this.bos = 1;
+            this.protocol = PROTOCOL_IPV4;
+        }
+        return super.setPayload(payload);
+    }
+
     /**
      * Deserializer function for MPLS packets.
      *
@@ -124,7 +142,11 @@
             mpls.label = ((mplsheader & 0xfffff000) >>> 12);
             mpls.bos = (byte) ((mplsheader & 0x00000100) >> 8);
             mpls.ttl = (byte) (mplsheader & 0x000000ff);
-            mpls.protocol = (mpls.bos == 1) ? PROTOCOL_IPV4 : PROTOCOL_MPLS;
+
+            ByteBuffer duplicate = bb.duplicate();
+            short protocol = (short) ((duplicate.get() & 0xf0) >> 4);
+            mpls.protocol = (mpls.bos == 1) ? protocol == 4 ?
+                    PROTOCOL_IPV4 : PROTOCOL_IPV6 : PROTOCOL_MPLS;
 
             Deserializer<? extends IPacket> deserializer;
             if (protocolDeserializerMap.containsKey(mpls.protocol)) {
diff --git a/utils/misc/src/test/java/org/onlab/packet/MplsTest.java b/utils/misc/src/test/java/org/onlab/packet/MplsTest.java
index 4c247fa..6231636 100644
--- a/utils/misc/src/test/java/org/onlab/packet/MplsTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/MplsTest.java
@@ -21,10 +21,18 @@
 import org.junit.Test;
 
 import java.nio.ByteBuffer;
-import java.util.HashMap;
 
+import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.onlab.packet.ICMP.TYPE_ECHO_REQUEST;
+import static org.onlab.packet.ICMP6.ECHO_REQUEST;
+import static org.onlab.packet.IPv4.PROTOCOL_ICMP;
+import static org.onlab.packet.MPLS.PROTOCOL_IPV4;
+import static org.onlab.packet.MPLS.PROTOCOL_IPV6;
+import static org.onlab.packet.MPLS.PROTOCOL_MPLS;
 
 /**
  * Unit tests for MPLS class.
@@ -36,22 +44,91 @@
     private int label = 1048575;
     private byte bos = 1;
     private byte ttl = 20;
-    private byte protocol = MPLS.PROTOCOL_IPV4;
+    private byte protocol = PROTOCOL_IPV4;
 
     private byte[] bytes;
+    private byte[] truncatedBytes;
+
+    // Define packets to deserialize
+    private static final MacAddress SRC_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final MacAddress DST_MAC = MacAddress.valueOf("00:00:00:00:00:02");
+
+    private static final ICMPEcho ICMP_ECHO = new ICMPEcho()
+            .setIdentifier((short) 0)
+            .setSequenceNum((short) 0);
+
+    private static final ICMP ICMP = (ICMP) new ICMP()
+            .setIcmpType(TYPE_ECHO_REQUEST)
+            .setPayload(ICMP_ECHO);
+
+    private static final Ip4Address SRC_IPV4 = Ip4Address.valueOf("10.0.1.1");
+    private static final Ip4Address DST_IPV4 = Ip4Address.valueOf("10.0.0.254");
+
+    private static final IPv4 IPV4 = (IPv4) new IPv4()
+            .setDestinationAddress(DST_IPV4.toInt())
+            .setSourceAddress(SRC_IPV4.toInt())
+            .setTtl((byte) 64)
+            .setProtocol(PROTOCOL_ICMP)
+            .setPayload(ICMP);
+
+    private static final ICMP6 ICMP6 = new ICMP6()
+            .setIcmpType(ECHO_REQUEST);
+
+    private static final Ip6Address SRC_IPV6 = Ip6Address.valueOf("2000::101");
+    private static final Ip6Address DST_IPV6 = Ip6Address.valueOf("2000::ff");
+
+    private static final IPv6 IPV6 = (IPv6) new IPv6()
+            .setDestinationAddress(DST_IPV6.toOctets())
+            .setSourceAddress(SRC_IPV6.toOctets())
+            .setHopLimit((byte) 255)
+            .setNextHeader(IPv6.PROTOCOL_ICMP6)
+            .setPayload(ICMP6);
+
+    private static final MPLS MPLS_IPV4 = new MPLS();
+    private static final MPLS MPLS_BOS_IPV4 = new MPLS();
+    private static final MPLS MPLS_IPV6 = new MPLS();
+    private static final MPLS MPLS_BOS_IPV6 = new MPLS();
+
+    private static final Ethernet ETH_IPV4 = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.MPLS_UNICAST)
+            .setDestinationMACAddress(DST_MAC)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(MPLS_IPV4);
+
+    private static final Ethernet ETH_IPV6 = (Ethernet) new Ethernet()
+            .setEtherType(Ethernet.MPLS_UNICAST)
+            .setDestinationMACAddress(DST_MAC)
+            .setSourceMACAddress(SRC_MAC)
+            .setPayload(MPLS_IPV6);
 
     @Before
     public void setUp() throws Exception {
-        // Replace normal deserializer map with an empty map. This will cause
-        // the DataDeserializer to be used which will silently handle 0-byte input.
-        MPLS.protocolDeserializerMap = new HashMap<>();
-
+        // Setup packets
         deserializer = MPLS.deserializer();
 
-        ByteBuffer bb = ByteBuffer.allocate(MPLS.HEADER_LENGTH);
+        byte[] ipv4 = IPV4.serialize();
+        ByteBuffer bb = ByteBuffer.allocate(MPLS.HEADER_LENGTH + IPV4.getTotalLength());
         bb.putInt(((label & 0x000fffff) << 12) | ((bos & 0x1) << 8 | (ttl & 0xff)));
+        bb.put(ipv4);
 
         bytes = bb.array();
+
+        bb = ByteBuffer.allocate(MPLS.HEADER_LENGTH);
+        bb.putInt(((label & 0x000fffff) << 12) | ((bos & 0x1) << 8 | (ttl & 0xff)));
+
+        truncatedBytes = bb.array();
+
+        MPLS_BOS_IPV4.setLabel(101);
+        MPLS_BOS_IPV4.setPayload(IPV4);
+        MPLS_IPV4.setLabel(1);
+        MPLS_IPV4.setPayload(MPLS_BOS_IPV4);
+        ETH_IPV4.setPayload(MPLS_IPV4);
+
+        MPLS_BOS_IPV6.setLabel(201);
+        MPLS_BOS_IPV6.setPayload(IPV6);
+        MPLS_IPV6.setLabel(2);
+        MPLS_IPV6.setPayload(MPLS_BOS_IPV6);
+        ETH_IPV6.setPayload(MPLS_IPV6);
     }
 
     @Test
@@ -61,7 +138,7 @@
 
     @Test
     public void testDeserializeTruncated() throws Exception {
-        PacketTestUtils.testDeserializeTruncated(deserializer, bytes);
+        PacketTestUtils.testDeserializeTruncated(deserializer, truncatedBytes);
     }
 
     /**
@@ -90,4 +167,78 @@
         assertTrue(StringUtils.contains(str, "ttl=" + ttl));
         assertTrue(StringUtils.contains(str, "protocol=" + protocol));
     }
+
+    @Test
+    public void testIpv4OverMplsDeserialize() throws Exception {
+        // Serialize
+        byte[] packet = ETH_IPV4.serialize();
+        assertThat(MPLS_IPV4.protocol, is(PROTOCOL_MPLS));
+        assertThat(MPLS_IPV4.bos, is((byte) 0));
+        assertThat(MPLS_BOS_IPV4.protocol, is(PROTOCOL_IPV4));
+        assertThat(MPLS_BOS_IPV4.bos, is((byte) 1));
+        // Deserialize
+        Ethernet ethernet;
+        ethernet = Ethernet.deserializer().deserialize(packet, 0, packet.length);
+
+        // Verify
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_IPV4.getSourceMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_IPV4.getDestinationMAC()));
+        assertThat(ethernet.getEtherType(), is(Ethernet.MPLS_UNICAST));
+        assertTrue(ethernet.getPayload() instanceof MPLS);
+        MPLS mpls = (MPLS) ethernet.getPayload();
+        assertThat(mpls.getLabel(), is(1));
+        assertThat(mpls.getTtl(), is((byte) 0));
+        assertThat(mpls.protocol, is(PROTOCOL_MPLS));
+        assertThat(mpls.bos, is((byte) 0));
+        assertTrue(mpls.getPayload() instanceof MPLS);
+        mpls = (MPLS) mpls.getPayload();
+        assertThat(mpls.getLabel(), is(101));
+        assertThat(mpls.getTtl(), is((byte) 0));
+        assertThat(mpls.protocol, is(PROTOCOL_IPV4));
+        assertThat(mpls.bos, is((byte) 1));
+        IPv4 ip = (IPv4) mpls.getPayload();
+        assertThat(ip.getSourceAddress(), is(SRC_IPV4.toInt()));
+        assertThat(ip.getDestinationAddress(), is(DST_IPV4.toInt()));
+        assertTrue(ip.getPayload() instanceof ICMP);
+        ICMP icmp = (ICMP) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(TYPE_ECHO_REQUEST));
+    }
+
+    @Test
+    public void testIpv6OverMplsDeserialize() throws Exception {
+        // Serialize
+        byte[] packet = ETH_IPV6.serialize();
+        assertThat(MPLS_IPV6.protocol, is(PROTOCOL_MPLS));
+        assertThat(MPLS_IPV6.bos, is((byte) 0));
+        assertThat(MPLS_BOS_IPV6.protocol, is(PROTOCOL_IPV6));
+        assertThat(MPLS_BOS_IPV6.bos, is((byte) 1));
+        // Deserialize
+        Ethernet ethernet;
+        ethernet = Ethernet.deserializer().deserialize(packet, 0, packet.length);
+
+        // Verify
+        assertNotNull(ethernet);
+        assertThat(ethernet.getSourceMAC(), is(ETH_IPV6.getSourceMAC()));
+        assertThat(ethernet.getDestinationMAC(), is(ETH_IPV6.getDestinationMAC()));
+        assertThat(ethernet.getEtherType(), is(Ethernet.MPLS_UNICAST));
+        assertTrue(ethernet.getPayload() instanceof MPLS);
+        MPLS mpls = (MPLS) ethernet.getPayload();
+        assertThat(mpls.getLabel(), is(2));
+        assertThat(mpls.getTtl(), is((byte) 0));
+        assertThat(mpls.protocol, is(PROTOCOL_MPLS));
+        assertThat(mpls.bos, is((byte) 0));
+        assertTrue(mpls.getPayload() instanceof MPLS);
+        mpls = (MPLS) mpls.getPayload();
+        assertThat(mpls.getLabel(), is(201));
+        assertThat(mpls.getTtl(), is((byte) 0));
+        assertThat(mpls.protocol, is(PROTOCOL_IPV6));
+        assertThat(mpls.bos, is((byte) 1));
+        IPv6 ip = (IPv6) mpls.getPayload();
+        assertThat(ip.getSourceAddress(), is(SRC_IPV6.toOctets()));
+        assertThat(ip.getDestinationAddress(), is(DST_IPV6.toOctets()));
+        assertTrue(ip.getPayload() instanceof ICMP6);
+        ICMP6 icmp = (ICMP6) ip.getPayload();
+        assertThat(icmp.getIcmpType(), is(ECHO_REQUEST));
+    }
 }