[CORD-638] ICMPv6 NDP support

Changes:
- Adds the support for the ND protocol;
- Changes in several places Ip4Address and Ip4Prefix for general objects;

Change-Id: I7429b8f4acc9ffe432b49b66e66da50045996f7c
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
index 246a3bb..675e63d 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -92,12 +92,14 @@
                 .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
         if (appConfig != null && appConfig.suppressSubnet().contains(pkt.inPort())) {
             // Ignore ARP packets come from suppressed ports
+            pkt.drop();
             return;
         }
 
         if (!validateArpSpa(pkt)) {
             log.debug("Ignore ARP packet discovered on {} with unexpected src protocol address {}.",
                     pkt.inPort(), pkt.sender().getIp4Address());
+            pkt.drop();
             return;
         }
 
@@ -312,7 +314,6 @@
      */
     private void forward(Ethernet packet, ConnectPoint outPort) {
         packet.setEtherType(Ethernet.TYPE_ARP);
-
         ByteBuffer buf = ByteBuffer.wrap(packet.serialize());
 
         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index 80b233b..9ac2b26 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -19,19 +19,27 @@
 import org.onlab.packet.ICMP;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MPLS;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
+import org.onosproject.incubator.net.neighbour.NeighbourMessageType;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
+import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,6 +68,10 @@
         this.config = checkNotNull(srManager.deviceConfiguration);
     }
 
+    //////////////////////////////////////
+    //     ICMP Echo/Reply Protocol     //
+    //////////////////////////////////////
+
     /**
      * Process incoming ICMP packet.
      * If it is an ICMP request to router or known host, then sends an ICMP response.
@@ -112,19 +124,6 @@
     }
 
     /**
-     * Process incoming ICMP packet.
-     * If it is an ICMP request to router or known host, then sends an ICMP response.
-     * If it is an ICMP packet to known host and forward the packet to the host.
-     * If it is an ICMP packet to unknown host in a subnet, then sends an ARP request
-     * to the subnet.
-     *
-     * @param pkt inbound packet
-     */
-    public void processPacketIn(NeighbourMessageContext pkt) {
-
-    }
-
-    /**
      * Sends an ICMP reply message.
      *
      * Note: we assume that packets sending from the edge switches to the hosts
@@ -202,5 +201,208 @@
         }
     }
 
+    ///////////////////////////////////////////
+    //  ICMPv6 Neighbour Discovery Protocol  //
+    ///////////////////////////////////////////
 
+    /**
+     * Process incoming NDP packet.
+     *
+     * If it is an NDP request for the router or for the gateway, then sends a NDP reply.
+     * If it is an NDP request to unknown host flood in the subnet.
+     * If it is an NDP packet to known host forward the packet to the host.
+     *
+     * FIXME If the NDP packets use link local addresses we fail.
+     *
+     * @param pkt inbound packet
+     * @param hostService the host service
+     */
+    public void processPacketIn(NeighbourMessageContext pkt, HostService hostService) {
+        /*
+         * First we validate the ndp packet
+         */
+        SegmentRoutingAppConfig appConfig = srManager.cfgService
+                .getConfig(srManager.appId, SegmentRoutingAppConfig.class);
+        if (appConfig != null && appConfig.suppressSubnet().contains(pkt.inPort())) {
+            // Ignore NDP packets come from suppressed ports
+            pkt.drop();
+            return;
+        }
+        if (!validateSrcIp(pkt)) {
+            log.debug("Ignore NDP packet discovered on {} with unexpected src ip address {}.",
+                      pkt.inPort(), pkt.sender());
+            pkt.drop();
+            return;
+        }
+
+        if (pkt.type() == NeighbourMessageType.REQUEST) {
+            handleNdpRequest(pkt, hostService);
+        } else {
+            handleNdpReply(pkt, hostService);
+        }
+
+    }
+
+    /**
+     * Utility function to verify if the src ip belongs to the same
+     * subnet configured on the port it is seen.
+     *
+     * @param pkt the ndp packet and context information
+     * @return true if the src ip is a valid address for the subnet configured
+     * for the connect point
+     */
+    private boolean validateSrcIp(NeighbourMessageContext pkt) {
+        ConnectPoint connectPoint = pkt.inPort();
+        IpPrefix subnet = config.getPortIPv6Subnet(
+                connectPoint.deviceId(),
+                connectPoint.port()
+        ).getIp6Prefix();
+        return subnet != null && subnet.contains(pkt.sender());
+    }
+
+    /**
+     * Helper method to handle the ndp requests.
+     *
+     * @param pkt the ndp packet request and context information
+     * @param hostService the host service
+     */
+    private void handleNdpRequest(NeighbourMessageContext pkt, HostService hostService) {
+        /*
+         * ND request for the gateway. We have to reply on behalf
+         * of the gateway.
+         */
+        if (isNdpForGateway(pkt)) {
+            log.debug("Sending NDP reply on behalf of the router");
+            sendNdpReply(pkt, config.getRouterMacForAGatewayIp(pkt.target()));
+        } else {
+            /*
+             * ND request for an host. We do a search by Ip.
+             */
+            Set<Host> hosts = hostService.getHostsByIp(pkt.target());
+            /*
+             * Possible misconfiguration ? In future this case
+             * should be handled we can have same hosts in different
+             * vlans.
+             */
+            if (hosts.size() > 1) {
+                log.warn("More than one host with IP {}", pkt.target());
+            }
+            Host targetHost = hosts.stream().findFirst().orElse(null);
+            /*
+             * If we know the host forward to its attachment
+             * point.
+             */
+            if (targetHost != null) {
+                log.debug("Forward NDP request to the target host");
+                pkt.forward(targetHost.location());
+            } else {
+                /*
+                 * Flood otherwise.
+                 */
+                log.debug("Flood NDP request to the target subnet");
+                flood(pkt);
+            }
+        }
+    }
+
+    /**
+     * Helper method to handle the ndp replies.
+     *
+     * @param pkt the ndp packet reply and context information
+     * @param hostService the host service
+     */
+    private void handleNdpReply(NeighbourMessageContext pkt, HostService hostService) {
+        if (isNdpForGateway(pkt)) {
+            log.debug("Forwarding all the ip packets we stored");
+            Ip6Address hostIpAddress = pkt.sender().getIp6Address();
+            srManager.ipHandler.forwardPackets(pkt.inPort().deviceId(), hostIpAddress);
+        } else {
+            HostId hostId = HostId.hostId(pkt.dstMac(), pkt.vlan());
+            Host targetHost = hostService.getHost(hostId);
+            if (targetHost != null) {
+                log.debug("Forwarding the reply to the host");
+                pkt.forward(targetHost.location());
+            } else {
+                /*
+                 * We don't have to flood towards spine facing ports.
+                 */
+                if (pkt.vlan().equals(VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET))) {
+                    return;
+                }
+                log.debug("Flooding the reply to the subnet");
+                flood(pkt);
+            }
+        }
+    }
+
+    /**
+     * Utility to verify if the ND are for the gateway.
+     *
+     * @param pkt the ndp packet
+     * @return true if the ndp is for the gateway. False otherwise
+     */
+    private boolean isNdpForGateway(NeighbourMessageContext pkt) {
+        DeviceId deviceId = pkt.inPort().deviceId();
+        Set<IpAddress> gatewayIpAddresses = null;
+        try {
+            if (pkt.target().equals(config.getRouterIpv6(deviceId))) {
+                return true;
+            }
+            gatewayIpAddresses = config.getPortIPs(deviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting check for router IP in processing ndp");
+        }
+        if (gatewayIpAddresses != null &&
+                gatewayIpAddresses.contains(pkt.target())) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Utility to send a ND reply using the supplied information.
+     *
+     * @param pkt the ndp request
+     * @param targetMac the target mac
+     */
+    private void sendNdpReply(NeighbourMessageContext pkt, MacAddress targetMac) {
+        HostId dstId = HostId.hostId(pkt.srcMac(), pkt.vlan());
+        Host dst = srManager.hostService.getHost(dstId);
+        if (dst == null) {
+            log.warn("Cannot send NDP response to host {} - does not exist in the store",
+                     dstId);
+            return;
+        }
+        pkt.reply(targetMac);
+    }
+
+    /*
+     * Floods only on the port which have been configured with the subnet
+     * of the target address. The in port is excluded.
+     *
+     * @param pkt the ndp packet and context information
+     */
+    private void flood(NeighbourMessageContext pkt) {
+        try {
+            srManager.deviceConfiguration
+                    .getSubnetPortsMap(pkt.inPort().deviceId())
+                    .forEach((subnet, ports) -> {
+                        if (subnet.contains(pkt.target())) {
+                            ports.stream()
+                                    .filter(portNumber -> portNumber != pkt.inPort().port())
+                                    .forEach(portNumber -> {
+                                        ConnectPoint outPoint = new ConnectPoint(
+                                                pkt.inPort().deviceId(),
+                                                portNumber
+                                        );
+                                        pkt.forward(outPoint);
+                                    });
+                        }
+                    });
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage()
+                             + " Cannot flood in subnet as device config not available"
+                             + " for device: " + pkt.inPort().deviceId());
+        }
+    }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
index 00be6f5..b8e16a8 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
@@ -18,6 +18,7 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
@@ -157,4 +158,22 @@
         }
     }
 
+    //////////////////////
+    //  IPv6 Handling  //
+    ////////////////////
+
+    /**
+     * Forwards IP packets in the buffer to the destination IP address.
+     * It is called when the controller finds the destination MAC address
+     * via NDP replies.
+     *
+     * @param deviceId the target device
+     * @param destIpAddress the destination ip address
+     */
+    public void forwardPackets(DeviceId deviceId, Ip6Address destIpAddress) {
+        /*
+         * TODO in the following commit.
+         */
+    }
+
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index b6a6219..1b8887b 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -520,7 +520,7 @@
      * per subnet.
      *
      * @param deviceId switch dpid
-     * @param subnet IPv4 prefix for which assigned vlan is desired
+     * @param subnet IP prefix for which assigned vlan is desired
      * @return VlanId assigned for the subnet on the device, or
      *         null if no vlan assignment was found and this instance is not
      *         the master for the device.
@@ -563,19 +563,11 @@
             nextAssignedVlan = (short) (Collections.min(assignedVlans) - 1);
         }
         for (Ip4Prefix unsub : unassignedSubnets) {
-            // Special case for default route. Assign default VLAN ID to /32 and /0 subnets
-            if (unsub.prefixLength() == IpPrefix.MAX_INET_MASK_LENGTH ||
-                    unsub.prefixLength() == 0) {
-                subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub),
-                        VlanId.vlanId(ASSIGNED_VLAN_NO_SUBNET));
-            } else {
-                subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub),
-                        VlanId.vlanId(nextAssignedVlan--));
-                log.info("Assigned vlan: {} to subnet: {} on device: {}",
-                        nextAssignedVlan + 1, unsub, deviceId);
-            }
+            subnetVidStore.put(new SubnetAssignedVidStoreKey(deviceId, unsub),
+                    VlanId.vlanId(nextAssignedVlan--));
+            log.info("Assigned vlan: {} to subnet: {} on device: {}",
+                    nextAssignedVlan + 1, unsub, deviceId);
         }
-
         return subnetVidStore.get(new SubnetAssignedVidStoreKey(deviceId, subnet));
     }
 
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
index 92a4371..5ea5694 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingNeighbourHandler.java
@@ -54,7 +54,7 @@
                 break;
             case NDP:
                 if (this.manager.icmpHandler != null) {
-                    this.manager.icmpHandler.processPacketIn(context);
+                    this.manager.icmpHandler.processPacketIn(context, hostService);
                 }
                 break;
             default:
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/DeviceSubnetListCommand.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/DeviceSubnetListCommand.java
index da3dd1c..eca9c45 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/DeviceSubnetListCommand.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/DeviceSubnetListCommand.java
@@ -41,9 +41,7 @@
     private void printDeviceSubnetMap(Map<DeviceId, Set<IpPrefix>> deviceSubnetMap) {
         deviceSubnetMap.forEach(((deviceId, ipPrefices) -> {
             print("%s", deviceId);
-            ipPrefices.forEach(ipPrefix -> {
-                print("    %s", ipPrefix);
-            });
+            ipPrefices.forEach(ipPrefix -> print("    %s", ipPrefix));
         }));
     }
 }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index 94f95f4..76a46f0 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -119,7 +119,6 @@
         // Read gatewayIps and subnets from port subject. Ignore suppressed ports.
         Set<ConnectPoint> portSubjects = srManager.cfgService
                 .getSubjects(ConnectPoint.class, InterfaceConfig.class);
-
         portSubjects.stream().filter(subject -> !isSuppressedPort(subject)).forEach(subject -> {
             InterfaceConfig config =
                     srManager.cfgService.getConfig(subject, InterfaceConfig.class);
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/storekey/SubnetAssignedVidStoreKey.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/storekey/SubnetAssignedVidStoreKey.java
index 917122f..2d41e62 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/storekey/SubnetAssignedVidStoreKey.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/storekey/SubnetAssignedVidStoreKey.java
@@ -39,6 +39,14 @@
     }
 
     /**
+     * Default constructor for Kryo.
+     */
+    protected SubnetAssignedVidStoreKey() {
+        this.deviceId = null;
+        this.subnet = null;
+    }
+
+    /**
      * Returns the device identification used to create this key.
      *
      * @return the device identifier