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();
+            }
+
+        }
+    }
 }