Adds ARP/NDP handler for SDX-L2 and updates the README
Changes:
- Implements the internal ARP/NDP handler;
- Updates the README file;
- Adds interaction with PacketService
Change-Id: I245b2d5df2acaaa4b3f00ca2eed07d18337e41a5
diff --git a/sdx-l2/README.md b/sdx-l2/README.md
index 19baf7b..8b25f24 100644
--- a/sdx-l2/README.md
+++ b/sdx-l2/README.md
@@ -1,9 +1,9 @@
![GEANT logo](http://www.geant.org/Style%20Library/Geant/Images/logo.png "http://geant.org")
-Software Defined eXchange - L2
+Software Defined eXchange - L2 (SDX-L2)
==========================
-SDXL2 is an application for ONOS project which can provide layer 2 connectivity between edge ports of a given SDN network.
+SDX-L2 is an application for ONOS project which can provide layer 2 connectivity between edge ports of a given SDN network.
License
=======
@@ -18,12 +18,12 @@
TBD.
-SDXL2 dependencies
+SDX-L2 dependencies
=============================
-TBD.
+SDX-L2 implements its own ARP/NDP handler, it is important to disable the ONOS ARP/NDP handler.
-SDXL2 installation
+SDX-L2 installation
=============================
- Compile the project:
@@ -36,45 +36,48 @@
- Install in your ONOS deployment:
- onos-app $OC1 install target/SDXL2-1.0-SNAPSHOT.oar
+ onos-app $OC1 install target/onos-app-sdx-l2-1.7.0-SNAPSHOT.oar
-SDXL2 cli commands
+SDX-L2 cli commands
=============================
- Create a named SDX:
- add-sdxl2 $sdxname
+ sdxl2-add $sdxname
- Delete a named SDX:
- remove-sdxl2 $sdxname
+ sdxl2-remove $sdxname
- List all the active SDXs:
- list-sdxl2s
+ sdxl2-list
-- Create a named SDX connection point:
+- Create a named SDX-L2 connection point:
- add-sdxl2cp [-ce_mac] $mac $sdxname $connectionpoint $vlans $sdxcpname
+ sdxl2cp-add [-ce_mac] $mac $sdxname $connectionpoint $vlans $sdxcpname
-- Remove a named SDX connection point:
+- Remove a named SDX-L2 connection point:
- remove-sdxl2cp $sdxcpname
+ sdxl2cp-remove $sdxcpname
-- List all the active SDX connection points or all the active SDX connection points related to an SDX:
+- List all the active SDX-L2 connection points or all the active SDX connection points related to an SDX:
- list-sdxl2cps [$sdxname]
+ sdxl2cps-list [$sdxname]
-- Get the information of an SDX connection point:
+- Get the information of an SDX-L2 connection point:
sdxl2cp $sdxcpname
+<!---
+
+NOT YET MERGED!!!
- Create a VC between two connection points:
@@ -98,4 +101,6 @@
SDXL2 GUI
=============================
-- TBD
\ No newline at end of file
+- TBD
+
+--->
\ No newline at end of file
diff --git a/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2ArpNdpHandler.java b/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2ArpNdpHandler.java
new file mode 100644
index 0000000..48e78b9
--- /dev/null
+++ b/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2ArpNdpHandler.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.sdxl2;
+
+
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.Criterion.Type;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.onosproject.security.AppPermission.Type.PACKET_WRITE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of ARP and NDP handler based on ProxyArpManager.
+ */
+public class SdxL2ArpNdpHandler {
+
+ private final Logger log = getLogger(SdxL2ArpNdpHandler.class);
+
+ private static final String MSG_NULL = "ARP or NDP message cannot be null.";
+
+ protected IntentService intentService;
+
+ protected ApplicationId applicationId;
+
+ protected PacketService packetService;
+
+ public static String vcType;
+
+
+ public SdxL2ArpNdpHandler(IntentService intentService,
+ PacketService packetService, ApplicationId applicationId) {
+ this.intentService = intentService;
+ this.packetService = packetService;
+ this.applicationId = applicationId;
+ }
+
+ /**
+ * Handles the ARP/NDP packets.
+ *
+ * @param context containing the packet to process.
+ * @return true if the packet has been handled otherwise false.
+ */
+ public boolean handlePacket(PacketContext context) {
+
+ checkPermission(PACKET_WRITE);
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+
+ if (ethPkt == null) {
+ return false;
+ }
+
+ MessageContext msgContext = createContext(ethPkt, pkt.receivedFrom());
+
+ if (msgContext == null) {
+ return false;
+ }
+
+ switch (msgContext.type()) {
+ case REPLY:
+ return relay(msgContext);
+ case REQUEST:
+ return relay(msgContext);
+ default:
+ }
+
+ return false;
+ }
+
+ /**
+ * Relays the packets edge-to-edge
+ * applying the egress actions.
+ *
+ * @param ctx containing the ARP/NDP packet.
+ * @return true if the packet has been handled otherwise false.
+ */
+ private boolean relay(MessageContext ctx) {
+
+ Iterator<Intent> intents = intentService.getIntents().iterator();
+ boolean resolved = false;
+ Intent intent;
+ while (intents.hasNext()) {
+
+ intent = intents.next();
+ if (intent.appId().equals(applicationId) &&
+ intentService.getIntentState(intent.key()) == IntentState.INSTALLED) {
+
+ PointToPointIntent ppIntent = (PointToPointIntent) intent;
+ TrafficSelector selector = ctx.selector();
+
+ if (ctx.inPort().equals(ppIntent.ingressPoint()) &&
+ partialequals(selector, ppIntent.selector())) {
+
+ Ethernet packet = ctx.packet();
+
+ checkNotNull(packet, MSG_NULL);
+
+ applytreatment(packet, ppIntent.treatment());
+
+ TrafficTreatment.Builder builder;
+ builder = DefaultTrafficTreatment.builder();
+ builder.setOutput(ppIntent.egressPoint().port());
+ TrafficTreatment treatment = builder.build();
+
+
+ ByteBuffer buf = ByteBuffer.wrap(packet.serialize());
+ packetService.emit(new DefaultOutboundPacket(ppIntent.egressPoint().deviceId(),
+ treatment, buf));
+ resolved = true;
+ break;
+
+ }
+ }
+ }
+
+ return resolved;
+ }
+
+ /**
+ * Verifies if the TrafficSelector of the packet is equal
+ * to the one derived from an SDX-L2 Intent.
+ *
+ * @param generated the traffic selector of the packet.
+ * @param fromIntent the traffic selector of the intent.
+ * @return true if all the criteria match.
+ */
+ private boolean partialequals(TrafficSelector generated, TrafficSelector fromIntent) {
+
+ Set<Criterion> criteria = generated.criteria();
+ for (Criterion criterion : criteria) {
+ if (!vcType.equals("MAC") && criterion.type().equals(Type.ETH_SRC)) {
+ continue;
+ }
+
+ if (!fromIntent.criteria().contains(criterion)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Applies the egress TrafficTreatment we found
+ * in the associated SDX-L2 Intent.
+ *
+ * @param packet the packet to modify.
+ * @param treatment the TrafficTreatment to apply.
+ */
+ private void applytreatment(Ethernet packet, TrafficTreatment treatment) {
+
+ for (Instruction instruction : treatment.allInstructions()) {
+ if (instruction.type() == Instruction.Type.L2MODIFICATION) {
+ L2ModificationInstruction l2instruction = (L2ModificationInstruction) instruction;
+ switch (l2instruction.subtype()) {
+ case VLAN_ID:
+ ModVlanIdInstruction modVlanIdInstruction = (ModVlanIdInstruction) l2instruction;
+ packet.setVlanID(modVlanIdInstruction.vlanId().toShort());
+ break;
+ case VLAN_PUSH:
+ packet.setVlanID((short) 1);
+ break;
+ case VLAN_POP:
+ packet.setVlanID(Ethernet.VLAN_UNTAGGED);
+ break;
+ default:
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Attempts to create a MessageContext for the given Ethernet frame. If the
+ * frame is a valid ARP or NDP request or response, a context will be
+ * created.
+ *
+ * @param eth input Ethernet frame
+ * @param inPort in port
+ * @return MessageContext if the packet was ARP or NDP, otherwise null
+ */
+ private MessageContext createContext(Ethernet eth, ConnectPoint inPort) {
+ if (eth.getEtherType() == Ethernet.TYPE_ARP) {
+ return createArpContext(eth, inPort);
+ } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
+ return createNdpContext(eth, inPort);
+ }
+
+ return null;
+ }
+
+ /**
+ * Extracts context information from ARP packets.
+ *
+ * @param eth input Ethernet frame that is thought to be ARP
+ * @param inPort in port
+ * @return MessageContext object if the packet was a valid ARP packet,
+ * otherwise null
+ */
+ private MessageContext createArpContext(Ethernet eth, ConnectPoint inPort) {
+ if (eth.getEtherType() != Ethernet.TYPE_ARP) {
+ return null;
+ }
+
+ ARP arp = (ARP) eth.getPayload();
+
+ IpAddress target = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+ IpAddress sender = Ip4Address.valueOf(arp.getSenderProtocolAddress());
+
+ MessageType type;
+ if (arp.getOpCode() == ARP.OP_REQUEST) {
+ type = MessageType.REQUEST;
+ } else if (arp.getOpCode() == ARP.OP_REPLY) {
+ type = MessageType.REPLY;
+ } else {
+ return null;
+ }
+
+ return new MessageContext(eth, inPort, Protocol.ARP, type, target, sender);
+ }
+
+ /**
+ * Extracts context information from NDP packets.
+ *
+ * @param eth input Ethernet frame that is thought to be NDP
+ * @param inPort in port
+ * @return MessageContext object if the packet was a valid NDP packet,
+ * otherwise null
+ */
+ private MessageContext createNdpContext(Ethernet eth, ConnectPoint inPort) {
+ if (eth.getEtherType() != Ethernet.TYPE_IPV6) {
+ return null;
+ }
+ IPv6 ipv6 = (IPv6) eth.getPayload();
+
+ if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+ return null;
+ }
+ ICMP6 icmpv6 = (ICMP6) ipv6.getPayload();
+
+ IpAddress sender = Ip6Address.valueOf(ipv6.getSourceAddress());
+ IpAddress target = null;
+
+ MessageType type;
+ if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) {
+ type = MessageType.REQUEST;
+ NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload();
+ target = Ip6Address.valueOf(nsol.getTargetAddress());
+ } else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) {
+ type = MessageType.REPLY;
+ } else {
+ return null;
+ }
+
+ return new MessageContext(eth, inPort, Protocol.NDP, type, target, sender);
+ }
+
+
+
+ private enum Protocol {
+ ARP, NDP
+ }
+
+ private enum MessageType {
+ REQUEST, REPLY
+ }
+
+ /**
+ * Provides context information for a particular ARP or NDP message, with
+ * a unified interface to access data regardless of protocol.
+ */
+ private class MessageContext {
+ private Protocol protocol;
+ private MessageType type;
+
+ private IpAddress target;
+ private IpAddress sender;
+
+ private Ethernet eth;
+ private ConnectPoint inPort;
+
+
+ public MessageContext(Ethernet eth, ConnectPoint inPort,
+ Protocol protocol, MessageType type,
+ IpAddress target, IpAddress sender) {
+ this.eth = eth;
+ this.inPort = inPort;
+ this.protocol = protocol;
+ this.type = type;
+ this.target = target;
+ this.sender = sender;
+ }
+
+ public ConnectPoint inPort() {
+ return inPort;
+ }
+
+ public Ethernet packet() {
+ return eth;
+ }
+
+ public Protocol protocol() {
+ return protocol;
+ }
+
+ public MessageType type() {
+ return type;
+ }
+
+ public VlanId vlan() {
+ return VlanId.vlanId(eth.getVlanID());
+ }
+
+ public MacAddress srcMac() {
+ return MacAddress.valueOf(eth.getSourceMACAddress());
+ }
+
+ public IpAddress target() {
+ return target;
+ }
+
+ public IpAddress sender() {
+ return sender;
+ }
+
+ /**
+ * Builds TrafficSelector using data
+ * in MessageContext.
+ *
+ * @return the TrafficSelector object
+ */
+ public TrafficSelector selector() {
+ switch (vcType) {
+ case "MPLS":
+ case "VLAN":
+ default:
+ }
+
+ return buildMacSelector(this.srcMac(), this.vlan());
+
+ }
+
+ /**
+ * /**
+ * Builds TrafficSelector for MAC
+ * based tunnels.
+ *
+ * @param srcMac the sender's MAC address.
+ * @param ingressTag the VLAN tag at ingress.
+ * @return the TrafficSelector object
+ */
+ private TrafficSelector buildMacSelector(MacAddress srcMac, VlanId ingressTag) {
+
+ TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthSrc(srcMac);
+ if (ingressTag.toShort() != VlanId.UNTAGGED) {
+ selectorBuilder.matchVlanId(ingressTag);
+ }
+ return selectorBuilder.build();
+ }
+
+ }
+
+}
diff --git a/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2Manager.java b/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2Manager.java
index d59b909..21dd06a 100644
--- a/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2Manager.java
+++ b/sdx-l2/src/main/java/org/onosproject/sdxl2/SdxL2Manager.java
@@ -23,12 +23,22 @@
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv6;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
import org.onosproject.net.edge.EdgePortService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.Key;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,6 +49,12 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static org.onlab.packet.Ethernet.TYPE_ARP;
+import static org.onlab.packet.Ethernet.TYPE_IPV6;
+import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
+import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
+import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
+import static org.onosproject.net.packet.PacketPriority.CONTROL;
/**
* Implementation of the SdxL2Service.
@@ -62,20 +78,34 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected EdgePortService edgePortService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ protected SdxL2Processor processor = new SdxL2Processor();
+
+ protected SdxL2ArpNdpHandler arpndpHandler;
+
protected ApplicationId appId;
protected SdxL2MonitoringService monitoringManager;
+ protected String vcType = "MAC";
+
+
@Activate
protected void activate(ComponentContext context) {
appId = coreService.registerApplication(SDXL2_APP);
monitoringManager = new SdxL2MonitoringManager(appId, intentService, edgePortService);
+ handleArpNdp();
log.info("Started");
}
+
+
@Deactivate
protected void deactivate() {
this.cleanSdxL2();
+ unhandleArpNdp();
log.info("Stopped");
}
@@ -236,4 +266,115 @@
this.monitoringManager.cleanup();
}
+ /**
+ * It requests ARP and NDP packets to the PacketService
+ * and registers the SDX-L2 PacketProcessor.
+ */
+ private void handleArpNdp() {
+ SdxL2ArpNdpHandler.vcType = vcType;
+ arpndpHandler = new SdxL2ArpNdpHandler(intentService, packetService, appId);
+ packetService.addProcessor(processor, PacketProcessor.director(1));
+
+ // ARP packet
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(TYPE_ARP);
+ packetService.requestPackets(selectorBuilder.build(),
+ CONTROL, appId, Optional.<DeviceId>empty());
+
+ // IPv6 Neighbor Solicitation packet.
+ selectorBuilder = DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(TYPE_IPV6);
+ selectorBuilder.matchIPProtocol(PROTOCOL_ICMP6);
+ selectorBuilder.matchIcmpv6Type(NEIGHBOR_SOLICITATION);
+ packetService.requestPackets(selectorBuilder.build(),
+ CONTROL, appId, Optional.<DeviceId>empty());
+
+ // IPv6 Neighbor Advertisement packet.
+ selectorBuilder = DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(TYPE_IPV6);
+ selectorBuilder.matchIPProtocol(PROTOCOL_ICMP6);
+ selectorBuilder.matchIcmpv6Type(NEIGHBOR_ADVERTISEMENT);
+ packetService.requestPackets(selectorBuilder.build(),
+ CONTROL, appId, Optional.<DeviceId>empty());
+ }
+
+ /**
+ * Withdraws the requests for ARP/NDP packets and
+ * unregisters the SDX-L2 PacketProcessor.
+ */
+ private void unhandleArpNdp() {
+ arpndpHandler = null;
+ packetService.removeProcessor(processor);
+ processor = null;
+
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(TYPE_ARP);
+ packetService.cancelPackets(selectorBuilder.build(),
+ CONTROL, appId, Optional.<DeviceId>empty());
+
+ selectorBuilder = DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(TYPE_IPV6);
+ selectorBuilder.matchIPProtocol(PROTOCOL_ICMP6);
+ selectorBuilder.matchIcmpv6Type(NEIGHBOR_SOLICITATION);
+ packetService.cancelPackets(selectorBuilder.build(),
+ CONTROL, appId, Optional.<DeviceId>empty());
+
+ selectorBuilder = DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(TYPE_IPV6);
+ selectorBuilder.matchIPProtocol(PROTOCOL_ICMP6);
+ selectorBuilder.matchIcmpv6Type(NEIGHBOR_ADVERTISEMENT);
+ packetService.cancelPackets(selectorBuilder.build(),
+ CONTROL, appId, Optional.<DeviceId>empty());
+ }
+
+ /**
+ * Packet processor responsible for forwarding packets along their paths.
+ */
+ private class SdxL2Processor implements PacketProcessor {
+
+ /**
+ * Processes the inbound packet as specified in the given context.
+ *
+ * @param context packet processing context
+ */
+ @Override
+ public void process(PacketContext context) {
+
+ /** Stop processing if the packet has been handled, since we
+ * can't do any more to it
+ */
+ if (context.isHandled()) {
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+
+ boolean handled = false;
+ if (ethPkt.getEtherType() == TYPE_ARP) {
+ //handle the arp packet.
+ handled = arpndpHandler.handlePacket(context);
+ } else if (ethPkt.getEtherType() == TYPE_IPV6) {
+ IPv6 ipv6Pkt = (IPv6) ethPkt.getPayload();
+ if (ipv6Pkt.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
+ ICMP6 icmp6Pkt = (ICMP6) ipv6Pkt.getPayload();
+ if (icmp6Pkt.getIcmpType() == NEIGHBOR_SOLICITATION ||
+ icmp6Pkt.getIcmpType() == NEIGHBOR_ADVERTISEMENT) {
+ // handle ICMPv6 solicitations and advertisements
+ handled = arpndpHandler.handlePacket(context);
+ }
+ }
+ }
+
+ if (handled) {
+ context.block();
+ }
+
+ }
+ }
}