CORD-2803 Correctly reply to ICMPv6 echo request that targets link-local address of the switch interface

Change-Id: Id17f07bff6097b3bceb13b3cabcec15f446829bd
diff --git a/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index 8228bb5..b305c5e 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -216,14 +216,7 @@
         try {
             routerIp = config.getRouterIpv6(deviceId);
 
-            Optional<Ip6Address> linkLocalIp = srManager.interfaceService.getInterfacesByPort(inPort)
-                    .stream()
-                    .map(Interface::mac)
-                    .map(MacAddress::toBytes)
-                    .map(IPv6::getLinkLocalAddress)
-                    .map(Ip6Address::valueOf)
-                    .findFirst();
-
+            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())) ||
@@ -240,19 +233,27 @@
     /**
      * Sends an ICMPv6 reply message.
      *
-     * Note: we assume that packets sending from the edge switches to the hosts
-     * have untagged VLAN.
      * @param ethRequest the original ICMP request
      * @param outport the output port where the ICMP reply should be sent to
      */
     private void sendIcmpv6Response(Ethernet ethRequest, ConnectPoint outport) {
-        // Note: We assume that packets arrive at the edge switches have untagged VLAN.
+        int sid = -1;
         Ethernet ethReply = ICMP6.buildIcmp6Reply(ethRequest);
         IPv6 icmpRequestIpv6 = (IPv6) ethRequest.getPayload();
         IPv6 icmpReplyIpv6 = (IPv6) ethRequest.getPayload();
+        // Source IP of the echo "reply"
+        Ip6Address srcIpAddress = Ip6Address.valueOf(icmpRequestIpv6.getDestinationAddress());
+        // 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());
+            return;
+        }
+
         // 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,8 +273,7 @@
         }
 
         // Search SID only if store lookup is success otherwise proceed with "sid=-1"
-        int sid = -1;
-        if (destRouterAddress !=  null) {
+        if (destRouterAddress != null) {
             sid = config.getIPv6SegmentId(destRouterAddress);
             if (sid < 0) {
                 log.warn("Failed to lookup SID of the switch that {} attaches to. " +
@@ -335,14 +335,7 @@
             try {
                 DeviceId deviceId = pkt.inPort().deviceId();
 
-                Optional<Ip6Address> linkLocalIp = srManager.interfaceService.getInterfacesByPort(pkt.inPort())
-                        .stream()
-                        .map(Interface::mac)
-                        .map(MacAddress::toBytes)
-                        .map(IPv6::getLinkLocalAddress)
-                        .map(Ip6Address::valueOf)
-                        .findFirst();
-
+                Optional<Ip6Address> linkLocalIp = getLinkLocalIp(pkt.inPort());
                 if (linkLocalIp.isPresent() && pkt.target().equals(linkLocalIp.get())) {
                     MacAddress routerMac = config.getDeviceMac(deviceId);
                     sendResponse(pkt, routerMac, hostService);
@@ -463,4 +456,20 @@
         );
         flood(ndpRequest, inPort, targetAddress);
     }
+
+    /**
+     * Returns link-local IP of given connect point.
+     *
+     * @param cp connect point
+     * @return optional link-local IP
+     */
+    private Optional<Ip6Address> getLinkLocalIp(ConnectPoint cp) {
+        return srManager.interfaceService.getInterfacesByPort(cp)
+                .stream()
+                .map(Interface::mac)
+                .map(MacAddress::toBytes)
+                .map(IPv6::getLinkLocalAddress)
+                .map(Ip6Address::valueOf)
+                .findFirst();
+    }
 }