Skip to content

Commit

Permalink
[CORD-638] ICMPv6 NDP support
Browse files Browse the repository at this point in the history
Changes:
- Adds the support for the ND protocol;
- Changes in several places Ip4Address and Ip4Prefix for general objects;

Change-Id: I7429b8f4acc9ffe432b49b66e66da50045996f7c
  • Loading branch information
pierventre authored and ray-milkey committed Jan 21, 2017
1 parent 8e78f46 commit f4b5fce
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ public void processPacketIn(NeighbourMessageContext pkt, HostService hostService
.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;
}

Expand Down Expand Up @@ -312,7 +314,6 @@ private void flood(NeighbourMessageContext pkt) {
*/
private void forward(Ethernet packet, ConnectPoint outPort) {
packet.setEtherType(Ethernet.TYPE_ARP);

ByteBuffer buf = ByteBuffer.wrap(packet.serialize());

TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -60,6 +68,10 @@ public IcmpHandler(SegmentRoutingManager srManager) {
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.
Expand Down Expand Up @@ -111,19 +123,6 @@ public void processPacketIn(InboundPacket pkt) {
}
}

/**
* 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.
*
Expand Down Expand Up @@ -202,5 +201,208 @@ private void sendPacketOut(ConnectPoint outport, Ethernet payload, int destSid)
}
}

///////////////////////////////////////////
// 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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -157,4 +158,22 @@ public void forwardPackets(DeviceId deviceId, Ip4Address destIpAddress) {
}
}

//////////////////////
// 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.
*/
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ public Tunnel getTunnel(String tunnelId) {
* 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.
Expand Down Expand Up @@ -563,19 +563,11 @@ public VlanId getSubnetAssignedVlanId(DeviceId deviceId, IpPrefix subnet) {
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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void handleMessage(NeighbourMessageContext context, HostService hostServi
break;
case NDP:
if (this.manager.icmpHandler != null) {
this.manager.icmpHandler.processPacketIn(context);
this.manager.icmpHandler.processPacketIn(context, hostService);
}
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ protected void execute() {
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));
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ public DeviceConfiguration(SegmentRoutingManager srManager) {
// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ public SubnetAssignedVidStoreKey(DeviceId deviceId, IpPrefix subnet) {
this.subnet = subnet;
}

/**
* Default constructor for Kryo.
*/
protected SubnetAssignedVidStoreKey() {
this.deviceId = null;
this.subnet = null;
}

/**
* Returns the device identification used to create this key.
*
Expand Down

0 comments on commit f4b5fce

Please sign in to comment.