[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