[CORD-631] ICMPv6 Echo support
Changes:
- ICMPv6 Echo support;
- Introduces SegmentRoutingNeighbourHandler;
- Simplifies ArpHandler;
- Simplifies IcmpHandler;
Change-Id: I93f04d94ff15f43ca83f96cbab3da5064c215f9c
diff --git a/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index 9ac2b26..1594364 100644
--- a/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -17,7 +17,9 @@
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;
@@ -25,6 +27,7 @@
import org.onlab.packet.MPLS;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
import org.onosproject.incubator.net.neighbour.NeighbourMessageType;
import org.onosproject.net.ConnectPoint;
@@ -35,10 +38,8 @@
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;
@@ -46,17 +47,13 @@
import java.nio.ByteBuffer;
import java.util.Set;
-import static com.google.common.base.Preconditions.checkNotNull;
-
/**
* Handler of ICMP packets that responses or forwards ICMP packets that
* are sent to the controller.
*/
-public class IcmpHandler {
+public class IcmpHandler extends SegmentRoutingNeighbourHandler {
private static Logger log = LoggerFactory.getLogger(IcmpHandler.class);
- private SegmentRoutingManager srManager;
- private DeviceConfiguration config;
/**
* Creates an IcmpHandler object.
@@ -64,8 +61,55 @@
* @param srManager SegmentRoutingManager object
*/
public IcmpHandler(SegmentRoutingManager srManager) {
- this.srManager = srManager;
- this.config = checkNotNull(srManager.deviceConfiguration);
+ super(srManager);
+ }
+
+ /**
+ * Utility function to send packet out.
+ *
+ * @param outport the output port
+ * @param payload the packet to send
+ * @param sid the segment id
+ * @param destIpAddress the destination ip address
+ * @param allowedHops the hop limit/ttl
+ */
+ private void sendPacketOut(ConnectPoint outport,
+ Ethernet payload,
+ int sid,
+ IpAddress destIpAddress,
+ byte allowedHops) {
+ int destSid;
+ if (destIpAddress.isIp4()) {
+ destSid = config.getIPv4SegmentId(payload.getDestinationMAC());
+ } else {
+ destSid = config.getIPv6SegmentId(payload.getDestinationMAC());
+ }
+
+ if (sid == -1 || destSid == sid ||
+ config.inSameSubnet(outport.deviceId(), destIpAddress)) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().
+ setOutput(outport.port()).build();
+ OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
+ treatment, ByteBuffer.wrap(payload.serialize()));
+ srManager.packetService.emit(packet);
+ } else {
+ log.debug("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.setTtl(allowedHops);
+ mplsPkt.setPayload(payload.getPayload());
+ payload.setPayload(mplsPkt);
+
+ OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
+ treatment, ByteBuffer.wrap(payload.serialize()));
+
+ srManager.packetService.emit(packet);
+ }
}
//////////////////////////////////////
@@ -79,17 +123,13 @@
* If it is an ICMP packet to unknown host in a subnet, then sends an ARP request
* to the subnet.
*
- * @param pkt inbound packet
+ * @param eth inbound ICMP packet
+ * @param inPort the input port
*/
- public void processPacketIn(InboundPacket pkt) {
-
- Ethernet ethernet = pkt.parsed();
- IPv4 ipv4 = (IPv4) ethernet.getPayload();
-
- ConnectPoint connectPoint = pkt.receivedFrom();
- DeviceId deviceId = connectPoint.deviceId();
- Ip4Address destinationAddress =
- Ip4Address.valueOf(ipv4.getDestinationAddress());
+ public void processIcmp(Ethernet eth, ConnectPoint inPort) {
+ DeviceId deviceId = inPort.deviceId();
+ IPv4 ipv4Packet = (IPv4) eth.getPayload();
+ Ip4Address destinationAddress = Ip4Address.valueOf(ipv4Packet.getDestinationAddress());
Set<IpAddress> gatewayIpAddresses = config.getPortIPs(deviceId);
IpAddress routerIp;
try {
@@ -98,14 +138,13 @@
log.warn(e.getMessage() + " Aborting processPacketIn.");
return;
}
- IpPrefix routerIpPrefix = IpPrefix.valueOf(routerIp, IpPrefix.MAX_INET_MASK_LENGTH);
- Ip4Address routerIpAddress = routerIpPrefix.getIp4Prefix().address();
-
// ICMP to the router IP or gateway IP
- if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
- (destinationAddress.equals(routerIpAddress) ||
+ if (((ICMP) ipv4Packet.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST &&
+ (destinationAddress.equals(routerIp.getIp4Address()) ||
gatewayIpAddresses.contains(destinationAddress))) {
- sendIcmpResponse(ethernet, connectPoint);
+ sendIcmpResponse(eth, inPort);
+ // We remove the packet from the queue
+ srManager.ipHandler.dequeuePacket(ipv4Packet, destinationAddress);
// ICMP for any known host
} else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
@@ -114,12 +153,13 @@
// ICMP for an unknown host in the subnet of the router
} else if (config.inSameSubnet(deviceId, destinationAddress)) {
- srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint);
+ srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, inPort);
// ICMP for an unknown host
} else {
log.debug("ICMP request for unknown host {} ", destinationAddress);
- // Do nothing
+ // We remove the packet from the queue
+ srManager.ipHandler.dequeuePacket(ipv4Packet, destinationAddress);
}
}
@@ -134,71 +174,90 @@
private void sendIcmpResponse(Ethernet icmpRequest, ConnectPoint outport) {
// Note: We assume that packets arrive at the edge switches have
// untagged VLAN.
- Ethernet icmpReplyEth = new Ethernet();
-
+ Ethernet icmpReplyEth = ICMP.buildIcmpReply(icmpRequest);
IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload();
- IPv4 icmpReplyIpv4 = new IPv4();
-
- int destAddress = icmpRequestIpv4.getDestinationAddress();
- icmpReplyIpv4.setDestinationAddress(icmpRequestIpv4.getSourceAddress());
- icmpReplyIpv4.setSourceAddress(destAddress);
- icmpReplyIpv4.setTtl((byte) 64);
- icmpReplyIpv4.setChecksum((short) 0);
-
- ICMP icmpReply = new ICMP();
- icmpReply.setPayload(((ICMP) icmpRequestIpv4.getPayload()).getPayload());
- icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY);
- icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY);
- icmpReply.setChecksum((short) 0);
- icmpReplyIpv4.setPayload(icmpReply);
-
- icmpReplyEth.setPayload(icmpReplyIpv4);
- icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4);
- icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress());
- icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress());
-
- Ip4Address destIpAddress = Ip4Address.valueOf(icmpReplyIpv4.getDestinationAddress());
+ IPv4 icmpReplyIpv4 = (IPv4) icmpReplyEth.getPayload();
+ Ip4Address destIpAddress = Ip4Address.valueOf(icmpRequestIpv4.getSourceAddress());
Ip4Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
int destSid = config.getIPv4SegmentId(destRouterAddress);
if (destSid < 0) {
- log.warn("Cannot find the Segment ID for {}", destAddress);
+ log.warn("Cannot find the Segment ID for {}", destIpAddress);
return;
}
-
- sendPacketOut(outport, icmpReplyEth, destSid);
-
+ sendPacketOut(outport, icmpReplyEth, destSid, destIpAddress, icmpReplyIpv4.getTtl());
}
- private void sendPacketOut(ConnectPoint outport, Ethernet payload, int destSid) {
+ ///////////////////////////////////////////
+ // ICMPv6 Echo/Reply Protocol //
+ ///////////////////////////////////////////
- IPv4 ipPacket = (IPv4) payload.getPayload();
- Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress());
-
- if (destSid == -1 || config.getIPv4SegmentId(payload.getDestinationMAC()) == destSid ||
- config.inSameSubnet(outport.deviceId(), destIpAddress)) {
- TrafficTreatment treatment = DefaultTrafficTreatment.builder().
- setOutput(outport.port()).build();
- OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
- treatment, ByteBuffer.wrap(payload.serialize()));
- srManager.packetService.emit(packet);
- } else {
- log.debug("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(destSid);
- mplsPkt.setTtl(((IPv4) payload.getPayload()).getTtl());
- mplsPkt.setPayload(payload.getPayload());
- payload.setPayload(mplsPkt);
-
- OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(),
- treatment, ByteBuffer.wrap(payload.serialize()));
-
- srManager.packetService.emit(packet);
+ /**
+ * Process incoming ICMPv6 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 eth the incoming ICMPv6 packet
+ * @param inPort the input port
+ */
+ public void processIcmpv6(Ethernet eth, ConnectPoint inPort) {
+ DeviceId deviceId = inPort.deviceId();
+ IPv6 ipv6Packet = (IPv6) eth.getPayload();
+ Ip6Address destinationAddress = Ip6Address.valueOf(ipv6Packet.getDestinationAddress());
+ Set<IpAddress> gatewayIpAddresses = config.getPortIPs(deviceId);
+ IpAddress routerIp;
+ try {
+ routerIp = config.getRouterIpv6(deviceId);
+ } catch (DeviceConfigNotFoundException e) {
+ log.warn(e.getMessage() + " Aborting processPacketIn.");
+ return;
}
+ ICMP6 icmp6 = (ICMP6) ipv6Packet.getPayload();
+ // ICMP to the router IP or gateway IP
+ if (icmp6.getIcmpType() == ICMP6.ECHO_REQUEST &&
+ (destinationAddress.equals(routerIp.getIp6Address()) ||
+ gatewayIpAddresses.contains(destinationAddress))) {
+ sendIcmpv6Response(eth, inPort);
+ // We remove the packet from the queue
+ srManager.ipHandler.dequeuePacket(ipv6Packet, destinationAddress);
+ // ICMP for any known host
+ } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
+ // TODO: known host packet should not be coming to controller - resend flows?
+ srManager.ipHandler.forwardPackets(deviceId, destinationAddress);
+ // ICMP for an unknown host in the subnet of the router
+ } else if (config.inSameSubnet(deviceId, destinationAddress)) {
+ sendNdpRequest(deviceId, destinationAddress, inPort);
+ // ICMP for an unknown host or not configured host
+ } else {
+ log.debug("ICMPv6 request for unknown host or not configured host {} ", destinationAddress);
+ // We remove the packet from the queue
+ srManager.ipHandler.dequeuePacket(ipv6Packet, destinationAddress);
+ }
+ }
+
+ /**
+ * 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.
+ Ethernet ethReply = ICMP6.buildIcmp6Reply(ethRequest);
+ IPv6 icmpRequestIpv6 = (IPv6) ethRequest.getPayload();
+ IPv6 icmpReplyIpv6 = (IPv6) ethRequest.getPayload();
+ Ip6Address destIpAddress = Ip6Address.valueOf(icmpRequestIpv6.getSourceAddress());
+ Ip6Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress);
+ int sid = config.getIPv6SegmentId(destRouterAddress);
+ if (sid < 0) {
+ log.warn("Cannot find the Segment ID for {}", destIpAddress);
+ return;
+ }
+ sendPacketOut(outport, ethReply, sid, destIpAddress, icmpReplyIpv6.getHopLimit());
}
///////////////////////////////////////////
@@ -273,7 +332,7 @@
*/
if (isNdpForGateway(pkt)) {
log.debug("Sending NDP reply on behalf of the router");
- sendNdpReply(pkt, config.getRouterMacForAGatewayIp(pkt.target()));
+ sendResponse(pkt, config.getRouterMacForAGatewayIp(pkt.target()), hostService);
} else {
/*
* ND request for an host. We do a search by Ip.
@@ -360,49 +419,37 @@
}
/**
- * Utility to send a ND reply using the supplied information.
+ * Sends a NDP request for the target IP address to all ports except in-port.
*
- * @param pkt the ndp request
- * @param targetMac the target mac
+ * @param deviceId Switch device ID
+ * @param targetAddress target IP address for ARP
+ * @param inPort in-port
*/
- 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);
+ public void sendNdpRequest(DeviceId deviceId, IpAddress targetAddress, ConnectPoint inPort) {
+ byte[] senderMacAddress = new byte[MacAddress.MAC_ADDRESS_LENGTH];
+ byte[] senderIpAddress = new byte[Ip6Address.BYTE_LENGTH];
+ /*
+ * Retrieves device info.
+ */
+ getSenderInfo(senderMacAddress, senderIpAddress, deviceId, targetAddress);
+ /*
+ * We have to compute the dst mac address and dst
+ * ip address.
+ */
+ byte[] dstIp = IPv6.getSolicitNodeAddress(targetAddress.toOctets());
+ byte[] dstMac = IPv6.getMCastMacAddress(dstIp);
+ /*
+ * Creates the request.
+ */
+ Ethernet ndpRequest = NeighborSolicitation.buildNdpSolicit(
+ targetAddress.toOctets(),
+ senderIpAddress,
+ dstIp,
+ senderMacAddress,
+ dstMac,
+ VlanId.NONE
+ );
+ flood(ndpRequest, inPort, targetAddress);
}
- /*
- * 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());
- }
- }
}