[GEΑΝΤ] Refactored: sample migration from SDN-IP to SDX-L3.

Change-Id: I8845c877886e0575a3f0412a6b99043dba11a6fb
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3.java
new file mode 100644
index 0000000..f01cd80
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014-2015 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.sdxl3;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.component.ComponentService;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.edge.EdgePortService;
+import org.onosproject.net.host.HostService;
+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.onosproject.routing.IntentSynchronizationAdminService;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.BgpConfig;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Component for the SDX-L3 application.
+ */
+@Component(immediate = true)
+public class SdxL3  {
+
+    public static final String SDX_L3_APP = "org.onosproject.sdxl3";
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ApplicationService applicationService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService networkConfigService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentSynchronizationService intentSynchronizer;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentSynchronizationAdminService intentSynchronizerAdmin;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentService componentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected EdgePortService edgeService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    protected ArpHandler arpHandler = null;
+
+    private InternalPacketProcessor processor = null;
+
+    private ApplicationId appId;
+
+    private static List<String> components = new ArrayList<>();
+    static {
+        components.add("org.onosproject.routing.bgp.BgpSessionManager");
+        components.add("org.onosproject.routing.impl.Router");
+        components.add(org.onosproject.sdxl3.impl.PeerConnectivityManager.class.getName());
+        components.add(org.onosproject.sdxl3.SdxL3Fib.class.getName());
+    }
+
+    @Activate
+    protected void activate() {
+        components.forEach(name -> componentService.activate(appId, name));
+
+        appId = coreService.registerApplication(SDX_L3_APP);
+
+        ApplicationId routerAppId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
+        BgpConfig bgpConfig =
+                networkConfigService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
+
+        arpHandler = new ArpHandler(bgpConfig,
+                edgeService,
+                hostService,
+                packetService,
+                interfaceService);
+        processor = new InternalPacketProcessor();
+        packetService.addProcessor(processor, PacketProcessor.director(2));
+
+        // TODO fix removing intents
+        applicationService.registerDeactivateHook(appId,
+                intentSynchronizerAdmin::removeIntents);
+
+        log.info("SDX-L3 started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        components.forEach(name -> componentService.deactivate(appId, name));
+
+        packetService.removeProcessor(processor);
+
+        log.info("SDX-L3 stopped");
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+            if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                arpHandler.processPacketIn(pkt);
+            }
+        }
+    }
+
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3ArpHandler.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3ArpHandler.java
new file mode 100644
index 0000000..795d301
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3ArpHandler.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright 2016 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.sdxl3;
+
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborAdvertisement;
+import org.onlab.packet.ndp.NeighborDiscoveryOptions;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.edge.EdgePortService;
+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.PacketService;
+import org.onosproject.routing.config.BgpConfig;
+
+import java.nio.ByteBuffer;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.HostId.hostId;
+import static org.onosproject.security.AppGuard.checkPermission;
+import static org.onosproject.security.AppPermission.Type.PACKET_WRITE;
+
+public class SdxL3ArpHandler {
+
+    private static final String REQUEST_NULL = "ARP or NDP request cannot be null.";
+    private static final String MSG_NOT_REQUEST = "Message is not an ARP or NDP request";
+
+    private BgpConfig bgpConfig;
+    private EdgePortService edgeService;
+    private HostService hostService;
+    private PacketService packetService;
+    private InterfaceService interfaceService;
+
+    private enum Protocol {
+        ARP, NDP
+    }
+
+    private enum MessageType {
+        REQUEST, REPLY
+    }
+
+    /**
+     * Creates an ArpHandler object.
+     *
+     */
+    public SdxL3ArpHandler(BgpConfig bgpConfig,
+                           EdgePortService edgeService,
+                           HostService hostService,
+                           PacketService packetService,
+                           InterfaceService interfaceService) {
+        this.bgpConfig = bgpConfig;
+        this.edgeService = edgeService;
+        this.hostService = hostService;
+        this.packetService = packetService;
+        this.interfaceService = interfaceService;
+    }
+
+    /**
+     * Processes incoming ARP packets.
+     *
+     * @param pkt incoming packet
+     */
+    public void processPacketIn(InboundPacket pkt) {
+        checkPermission(PACKET_WRITE);
+
+        Ethernet eth = pkt.parsed();
+        checkNotNull(eth, REQUEST_NULL);
+
+        ConnectPoint inPort = pkt.receivedFrom();
+        MessageContext context = createContext(eth, inPort);
+        if (context != null) {
+            replyInternal(context);
+        }
+    }
+
+    /**
+     * Handles a request message also when it concerns an SDX peering address.
+     *
+     * If the MAC address of the target is known, we can reply directly to the
+     * requestor. Otherwise, we forward the request out other ports in an
+     * attempt to find the correct host.
+     *
+     * @param context request message context to process
+     */
+    private void replyInternal(MessageContext context) {
+        checkNotNull(context);
+        checkArgument(context.type() == MessageType.REQUEST, MSG_NOT_REQUEST);
+
+        if (hasIpAddress(context.inPort())) {
+            // If the request came from outside the network, only reply if it was
+            // for one of our external addresses.
+
+            interfaceService.getInterfacesByPort(context.inPort())
+                    .stream()
+                    .filter(intf -> intf.ipAddresses()
+                            .stream()
+                            .anyMatch(ia -> ia.ipAddress().equals(context.target())))
+                    .forEach(intf -> buildAndSendReply(context, intf.mac()));
+
+            if (!isPeerAddress(context.sender()) || !isPeerAddress(context.target())) {
+                // Only care about requests from/towards external BGP peers
+                return;
+            }
+        }
+
+        // See if we have the target host in the host store
+        Set<Host> hosts = hostService.getHostsByIp(context.target());
+
+        Host dst = null;
+        Host src = hostService.getHost(hostId(context.srcMac(), context.vlan()));
+
+        for (Host host : hosts) {
+            if (host.vlan().equals(context.vlan())) {
+                dst = host;
+                break;
+            }
+        }
+
+        if (src != null && dst != null) {
+            // We know the target host so we can respond
+            buildAndSendReply(context, dst.mac());
+            return;
+        }
+
+        // If the source address matches one of our external addresses
+        // it could be a request from an internal host to an external
+        // address. Forward it over to the correct port.
+        boolean matched = false;
+        Set<Interface> interfaces = interfaceService.getInterfacesByIp(context.sender());
+        for (Interface intf : interfaces) {
+            if (intf.vlan().equals(context.vlan())) {
+                matched = true;
+                sendTo(context.packet(), intf.connectPoint());
+                break;
+            }
+        }
+
+        if (matched) {
+            return;
+        }
+
+        // If the packets has a vlanId look if there are some other
+        // interfaces in the configuration on the same vlan and broadcast
+        // the packet out just of through those interfaces.
+        VlanId vlanId = context.vlan();
+
+        Set<Interface> filteredVlanInterfaces =
+                filterVlanInterfacesNoIp(interfaceService.getInterfacesByVlan(vlanId));
+
+        if (vlanId != null
+                && !vlanId.equals(VlanId.NONE)
+                && confContainsVlans(vlanId, context.inPort())) {
+            vlanFlood(context.packet(), filteredVlanInterfaces, context.inPort);
+            return;
+        }
+
+        // The request couldn't be resolved.
+        // Flood the request on all ports except the incoming port.
+        flood(context.packet(), context.inPort());
+    }
+
+    private boolean isPeerAddress(IpAddress ip) {
+        return bgpConfig.bgpSpeakers()
+                .stream()
+                .flatMap(speaker -> speaker.peers().stream())
+                .anyMatch(peerAddress -> peerAddress.equals(ip));
+    }
+
+    private Set<Interface> filterVlanInterfacesNoIp(Set<Interface> vlanInterfaces) {
+        return vlanInterfaces
+                .stream()
+                .filter(intf -> intf.ipAddresses().isEmpty())
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * States if the interface configuration contains more than one interface configured
+     * on a specific vlan, including the interface passed as argument.
+     *
+     * @param vlanId the vlanid to look for in the interface configuration
+     * @param connectPoint the connect point to exclude from the search
+     * @return true if interfaces are found. False otherwise
+     */
+    private boolean confContainsVlans(VlanId vlanId, ConnectPoint connectPoint) {
+        Set<Interface> vlanInterfaces = interfaceService.getInterfacesByVlan(vlanId);
+        return interfaceService.getInterfacesByVlan(vlanId)
+                .stream()
+                .anyMatch(intf -> intf.connectPoint().equals(connectPoint) && intf.ipAddresses().isEmpty())
+                && vlanInterfaces.size() > 1;
+    }
+
+    /**
+     * Builds and sends a reply message given a request context and the resolved
+     * MAC address to answer with.
+     *
+     * @param context message context of request
+     * @param targetMac MAC address to be given in the response
+     */
+    private void buildAndSendReply(MessageContext context, MacAddress targetMac) {
+        switch (context.protocol()) {
+            case ARP:
+                sendTo(ARP.buildArpReply((Ip4Address) context.target(),
+                        targetMac, context.packet()), context.inPort());
+                break;
+            case NDP:
+                sendTo(buildNdpReply((Ip6Address) context.target(), targetMac,
+                        context.packet()), context.inPort());
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Outputs a packet out a specific port.
+     *
+     * @param packet  the packet to send
+     * @param outPort the port to send it out
+     */
+    private void sendTo(Ethernet packet, ConnectPoint outPort) {
+        sendTo(outPort, ByteBuffer.wrap(packet.serialize()));
+    }
+
+    /**
+     * Outputs a packet out a specific port.
+     *
+     * @param outPort port to send it out
+     * @param packet packet to send
+     */
+    private void sendTo(ConnectPoint outPort, ByteBuffer packet) {
+        if (!edgeService.isEdgePoint(outPort)) {
+            // Sanity check to make sure we don't send the packet out an
+            // internal port and create a loop (could happen due to
+            // misconfiguration).
+            return;
+        }
+
+        TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
+        builder.setOutput(outPort.port());
+        packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
+                builder.build(), packet));
+    }
+
+    /**
+     * Returns whether the given port has any IP addresses configured or not.
+     *
+     * @param connectPoint the port to check
+     * @return true if the port has at least one IP address configured,
+     * false otherwise
+     */
+    private boolean hasIpAddress(ConnectPoint connectPoint) {
+        return interfaceService.getInterfacesByPort(connectPoint)
+                .stream()
+                .flatMap(intf -> intf.ipAddresses().stream())
+                .findAny()
+                .isPresent();
+    }
+
+    /**
+     * Returns whether the given port has any VLAN configured or not.
+     *
+     * @param connectPoint the port to check
+     * @return true if the port has at least one VLAN configured,
+     * false otherwise
+     */
+    private boolean hasVlan(ConnectPoint connectPoint) {
+        return interfaceService.getInterfacesByPort(connectPoint)
+                .stream()
+                .filter(intf -> !intf.vlan().equals(VlanId.NONE))
+                .findAny()
+                .isPresent();
+    }
+
+    /**
+     * Flood the arp request at all edges on a specifc VLAN.
+     *
+     * @param request the arp request
+     * @param dsts the destination interfaces
+     * @param inPort the connect point the arp request was received on
+     */
+    private void vlanFlood(Ethernet request, Set<Interface> dsts, ConnectPoint inPort) {
+        TrafficTreatment.Builder builder = null;
+        ByteBuffer buf = ByteBuffer.wrap(request.serialize());
+
+        for (Interface intf : dsts) {
+            ConnectPoint cPoint = intf.connectPoint();
+            if (cPoint.equals(inPort)) {
+                continue;
+            }
+
+            builder = DefaultTrafficTreatment.builder();
+            builder.setOutput(cPoint.port());
+            packetService.emit(new DefaultOutboundPacket(cPoint.deviceId(),
+                    builder.build(), buf));
+        }
+    }
+
+    /**
+     * Flood the arp request at all edges in the network.
+     *
+     * @param request the arp request
+     * @param inPort  the connect point the arp request was received on
+     */
+    private void flood(Ethernet request, ConnectPoint inPort) {
+        TrafficTreatment.Builder builder = null;
+        ByteBuffer buf = ByteBuffer.wrap(request.serialize());
+
+        for (ConnectPoint connectPoint : edgeService.getEdgePoints()) {
+            if (hasIpAddress(connectPoint)
+                    || hasVlan(connectPoint)
+                    || connectPoint.equals(inPort)) {
+                continue;
+            }
+
+            builder = DefaultTrafficTreatment.builder();
+            builder.setOutput(connectPoint.port());
+            packetService.emit(new DefaultOutboundPacket(connectPoint.deviceId(),
+                    builder.build(), buf));
+        }
+    }
+
+    /**
+     * Builds an Neighbor Discovery reply based on a request.
+     *
+     * @param srcIp   the IP address to use as the reply source
+     * @param srcMac  the MAC address to use as the reply source
+     * @param request the Neighbor Solicitation request we got
+     * @return an Ethernet frame containing the Neighbor Advertisement reply
+     */
+    private Ethernet buildNdpReply(Ip6Address srcIp, MacAddress srcMac,
+                                   Ethernet request) {
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(request.getSourceMAC());
+        eth.setSourceMACAddress(srcMac);
+        eth.setEtherType(Ethernet.TYPE_IPV6);
+        eth.setVlanID(request.getVlanID());
+
+        IPv6 requestIp = (IPv6) request.getPayload();
+        IPv6 ipv6 = new IPv6();
+        ipv6.setSourceAddress(srcIp.toOctets());
+        ipv6.setDestinationAddress(requestIp.getSourceAddress());
+        ipv6.setHopLimit((byte) 255);
+
+        ICMP6 icmp6 = new ICMP6();
+        icmp6.setIcmpType(ICMP6.NEIGHBOR_ADVERTISEMENT);
+        icmp6.setIcmpCode((byte) 0);
+
+        NeighborAdvertisement nadv = new NeighborAdvertisement();
+        nadv.setTargetAddress(srcIp.toOctets());
+        nadv.setSolicitedFlag((byte) 1);
+        nadv.setOverrideFlag((byte) 1);
+        nadv.addOption(NeighborDiscoveryOptions.TYPE_TARGET_LL_ADDRESS,
+                srcMac.toBytes());
+
+        icmp6.setPayload(nadv);
+        ipv6.setPayload(icmp6);
+        eth.setPayload(ipv6);
+        return eth;
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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;
+        }
+    }
+
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3Fib.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3Fib.java
new file mode 100644
index 0000000..68829e1
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3Fib.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2014-2015 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.sdxl3;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+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.intent.Constraint;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.onosproject.routing.RoutingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * FIB component of SDX-L3.
+ */
+@Component(immediate = true, enabled = false)
+public class SdxL3Fib {
+    private Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentSynchronizationService intentSynchronizer;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected RoutingService routingService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PeerConnectivityService peerConnectivity;
+
+    private final InternalFibListener fibListener = new InternalFibListener();
+
+    private static final int PRIORITY_OFFSET = 100;
+    private static final int PRIORITY_MULTIPLIER = 5;
+    protected static final ImmutableList<Constraint> CONSTRAINTS
+            = ImmutableList.of(new PartialFailureConstraint());
+
+    private final Map<IpPrefix, MultiPointToSinglePointIntent> routeIntents
+            = new ConcurrentHashMap<>();
+
+    private ApplicationId appId;
+
+    @Activate
+    public void activate() {
+        appId = coreService.getAppId(SdxL3.SDX_L3_APP);
+
+        routingService.addFibListener(fibListener);
+        routingService.start();
+    }
+
+    @Deactivate
+    public void deactivate() {
+        // TODO remove listener
+        routingService.stop();
+    }
+
+    private void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
+        int submitCount = 0, withdrawCount = 0;
+        //
+        // NOTE: Semantically, we MUST withdraw existing intents before
+        // submitting new intents.
+        //
+        synchronized (this) {
+            MultiPointToSinglePointIntent intent;
+
+            //
+            // Prepare the Intent batch operations for the intents to withdraw
+            //
+            for (FibUpdate withdraw : withdraws) {
+                checkArgument(withdraw.type() == FibUpdate.Type.DELETE,
+                        "FibUpdate with wrong type in withdraws list");
+
+                IpPrefix prefix = withdraw.entry().prefix();
+                intent = routeIntents.remove(prefix);
+                if (intent == null) {
+                    log.trace("SDX-L3 No intent in routeIntents to delete " +
+                            "for prefix: {}", prefix);
+                    continue;
+                }
+                intentSynchronizer.withdraw(intent);
+                withdrawCount++;
+            }
+
+            //
+            // Prepare the Intent batch operations for the intents to submit
+            //
+            for (FibUpdate update : updates) {
+                checkArgument(update.type() == FibUpdate.Type.UPDATE,
+                        "FibUpdate with wrong type in updates list");
+
+                IpPrefix prefix = update.entry().prefix();
+                intent = generateRouteIntent(prefix, update.entry().nextHopIp(),
+                        update.entry().nextHopMac());
+
+                if (intent == null) {
+                    // This preserves the old semantics - if an intent can't be
+                    // generated, we don't do anything with that prefix. But
+                    // perhaps we should withdraw the old intent anyway?
+                    continue;
+                }
+
+                routeIntents.put(prefix, intent);
+                intentSynchronizer.submit(intent);
+                submitCount++;
+            }
+
+            log.debug("SDX-L3 submitted {}/{}, withdrew = {}/{}", submitCount,
+                    updates.size(), withdrawCount, withdraws.size());
+        }
+    }
+
+    /**
+     * Generates a route intent for a prefix, the next hop IP address, and
+     * the next hop MAC address.
+     * <p/>
+     * This method will find the egress interface for the intent.
+     * Intent will match dst IP prefix and rewrite dst MAC address at all other
+     * border switches, then forward packets according to dst MAC address.
+     *
+     * @param prefix            IP prefix of the route to add
+     * @param nextHopIpAddress  IP address of the next hop
+     * @param nextHopMacAddress MAC address of the next hop
+     * @return the generated intent, or null if no intent should be submitted
+     */
+    private MultiPointToSinglePointIntent generateRouteIntent(
+            IpPrefix prefix,
+            IpAddress nextHopIpAddress,
+            MacAddress nextHopMacAddress) {
+
+        // Find the attachment point (egress interface) of the next hop
+        Interface egressInterface = peerConnectivity.getInterfaceForPeer(nextHopIpAddress);
+
+        if (egressInterface == null) {
+            log.warn("No outgoing interface found for {}",
+                    nextHopIpAddress);
+            return null;
+        }
+
+        // Generate the intent itself
+        Set<ConnectPoint> ingressPorts = new HashSet<>();
+        ConnectPoint egressPort = egressInterface.connectPoint();
+        log.debug("Generating intent for prefix {}, next hop mac {}",
+                prefix, nextHopMacAddress);
+
+        for (Interface intf : interfaceService.getInterfaces()) {
+            // TODO this should be only peering interfaces
+            if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
+                ConnectPoint srcPort = intf.connectPoint();
+                ingressPorts.add(srcPort);
+            }
+        }
+
+        // Match the destination IP prefix at the first hop
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        if (prefix.isIp4()) {
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            // if it is default route, then we do not need match destination
+            // IP address
+            if (prefix.prefixLength() != 0) {
+                selector.matchIPDst(prefix);
+            }
+        } else {
+            selector.matchEthType(Ethernet.TYPE_IPV6);
+            // if it is default route, then we do not need match destination
+            // IP address
+            if (prefix.prefixLength() != 0) {
+                selector.matchIPv6Dst(prefix);
+            }
+
+        }
+
+        // Rewrite the destination MAC address
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
+                .setEthDst(nextHopMacAddress);
+        if (!egressInterface.vlan().equals(VlanId.NONE)) {
+            treatment.setVlanId(egressInterface.vlan());
+            // If we set VLAN ID, we have to make sure a VLAN tag exists.
+            // TODO support no VLAN -> VLAN routing
+            selector.matchVlanId(VlanId.ANY);
+        }
+
+        int priority =
+                prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
+        Key key = Key.of(prefix.toString(), appId);
+        return MultiPointToSinglePointIntent.builder()
+                .appId(appId)
+                .key(key)
+                .selector(selector.build())
+                .treatment(treatment.build())
+                .ingressPoints(ingressPorts)
+                .egressPoint(egressPort)
+                .priority(priority)
+                .constraints(CONSTRAINTS)
+                .build();
+    }
+
+    private class InternalFibListener implements FibListener {
+        @Override
+        public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
+            SdxL3Fib.this.update(updates, withdraws);
+        }
+    }
+
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3PeerService.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3PeerService.java
new file mode 100644
index 0000000..6a2542c
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/SdxL3PeerService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-2016 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.sdxl3;
+
+import org.onlab.packet.IpAddress;
+import org.onosproject.incubator.net.intf.Interface;
+
+/*
+ * Service for managing peers connections
+ */
+public interface SdxL3PeerService {
+
+    String CONFIG_KEY = "bgpPeers";
+
+    /**
+     * Returns the interface used as connection point to peer.
+     *
+     * @param peeringAddress IP address of peer
+     * @return interface to the peer
+     */
+    Interface getInterfaceForPeer(IpAddress peeringAddress);
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/cli/BgpPeersListCommand.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/cli/BgpPeersListCommand.java
new file mode 100644
index 0000000..09f237b
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/cli/BgpPeersListCommand.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014-2015 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.sdxl3.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.BgpConfig;
+import org.onosproject.sdxl3.SdxL3;
+import org.onosproject.sdxl3.config.BgpPeersConfig;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Lists the BGP peers configured in the system.
+ */
+@Command(scope = "onos", name = "bgp-peers",
+        description = "Lists all BGP peers")
+public class BgpPeersListCommand extends AbstractShellCommand {
+
+    private static final String FORMAT = "ip=%s, interface=%s";
+    private static final String NAME_FORMAT = "%s: " + FORMAT;
+    private static final String AUTO_SELECTION = "auto";
+    public static final String NO_PEERS = "No peers configured";
+
+    private static final Comparator<BgpPeersConfig.PeerConfig> PEER_COMPARATOR =
+            Comparator.comparing(p -> p.ip());
+    public static final String EMPTY = "";
+
+    @Override
+    protected void execute() {
+        NetworkConfigService configService = get(NetworkConfigService.class);
+        CoreService coreService = get(CoreService.class);
+
+        ApplicationId routerAppId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
+        BgpConfig bgpConfig = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
+
+        ApplicationId sdxL3AppId = coreService.getAppId(SdxL3.SDX_L3_APP);
+        BgpPeersConfig peersConfig = configService.
+                getConfig(sdxL3AppId, BgpPeersConfig.class);
+
+        if (bgpConfig == null && peersConfig == null) {
+            print(NO_PEERS);
+            return;
+        }
+
+        List<IpAddress> peeringAddresses = Lists.newArrayList();
+        if (bgpConfig != null) {
+            peeringAddresses = getPeeringAddresses(bgpConfig);
+        }
+
+        List<BgpPeersConfig.PeerConfig> bgpPeers =
+                Lists.newArrayList();
+        if (peersConfig != null) {
+            bgpPeers.addAll(peersConfig.bgpPeers());
+        }
+
+        bgpPeers = mergePeers(peeringAddresses, bgpPeers);
+
+        if (bgpPeers.isEmpty()) {
+            print(NO_PEERS);
+            return;
+        }
+
+        bgpPeers.sort(PEER_COMPARATOR);
+        bgpPeers.forEach(p -> {
+            if (p.name().isPresent()) {
+                if (p.interfaceName() != EMPTY) {
+                    print(NAME_FORMAT, p.name().get(), p.ip(), p.interfaceName());
+                } else {
+                    print(NAME_FORMAT, p.name().get(), p.ip(), AUTO_SELECTION);
+                }
+            } else if (p.interfaceName() != EMPTY) {
+                print(FORMAT, p.ip(), p.interfaceName());
+            } else {
+                print(FORMAT, p.ip(), AUTO_SELECTION);
+            }
+        });
+    }
+
+    private List<IpAddress> getPeeringAddresses(BgpConfig bgpConfig) {
+        List<IpAddress> peeringAddresses = Lists.newArrayList();
+
+        List<BgpConfig.BgpSpeakerConfig> bgpSpeakers =
+                Lists.newArrayList(bgpConfig.bgpSpeakers());
+        bgpSpeakers.forEach(
+                s -> peeringAddresses.addAll(s.peers()));
+
+        return peeringAddresses;
+    }
+
+    private List<BgpPeersConfig.PeerConfig> mergePeers(
+            List<IpAddress> peeringAddresses,
+            List<BgpPeersConfig.PeerConfig> bgpPeers) {
+        peeringAddresses.forEach(a -> {
+            boolean exists = bgpPeers.stream()
+                    .filter(p -> p.ip().equals(a))
+                    .findAny().isPresent();
+            if (!exists) {
+                bgpPeers.add(new BgpPeersConfig
+                        .PeerConfig(Optional.<String>empty(), a, EMPTY));
+            }
+        });
+
+        return bgpPeers;
+    }
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/cli/package-info.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/cli/package-info.java
new file mode 100644
index 0000000..a5216de
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 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.
+ */
+
+/**
+ * SDX-L3 command-line handlers.
+ */
+package org.onosproject.sdxl3.cli;
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/config/BgpPeersConfig.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/config/BgpPeersConfig.java
new file mode 100644
index 0000000..dd89aed
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/config/BgpPeersConfig.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014-2015 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.sdxl3.config;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class BgpPeersConfig extends Config<ApplicationId> {
+    public static final String NAME = "name";
+    public static final String IP = "ip";
+    public static final String INTERFACE = "interface";
+
+    /**
+     * Gets the set of configured BGP peers.
+     *
+     * @return BGP peers
+     */
+    public Set<PeerConfig> bgpPeers() {
+        Set<PeerConfig> peers = Sets.newHashSet();
+
+        ArrayNode peersNode = array;
+        peersNode.forEach(jsonNode -> {
+            Optional<String> name;
+            if (jsonNode.get(NAME) == null) {
+                name = Optional.empty();
+            } else {
+                name = Optional.of(jsonNode.get(NAME).asText());
+            }
+
+            peers.add(new PeerConfig(name,
+                    IpAddress.valueOf(jsonNode.path(IP).asText()),
+                    jsonNode.path(INTERFACE).asText()));
+        });
+
+        return peers;
+    }
+
+    /**
+     * Gets the interface name configured for a given BGP peer.
+     *
+     * @param peerAddress IP address of the peer
+     * @return interface name
+     */
+
+    public String getInterfaceNameForPeer(IpAddress peerAddress) {
+        Optional<PeerConfig> match = bgpPeers()
+                .stream()
+                .filter(p -> p.ip().equals(peerAddress))
+                .findAny();
+
+        if (match.isPresent()) {
+            return match.get().interfaceName();
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * Configuration for a BGP peer.
+     */
+    public static class PeerConfig {
+
+        private Optional<String> name;
+        private IpAddress ip;
+        private String interfaceName;
+
+        public PeerConfig(Optional<String> name, IpAddress ip, String interfaceName) {
+            this.name = checkNotNull(name);
+            this.ip = checkNotNull(ip);
+            this.interfaceName = checkNotNull(interfaceName);
+        }
+
+        public Optional<String> name() {
+            return name;
+        }
+
+        public IpAddress ip() {
+            return ip;
+        }
+
+        public String interfaceName() {
+            return interfaceName;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof PeerConfig) {
+                final PeerConfig that = (PeerConfig) obj;
+                return Objects.equals(this.name, that.name) &&
+                        Objects.equals(this.ip, that.ip) &&
+                        Objects.equals(this.interfaceName, that.interfaceName);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(name, ip, interfaceName);
+        }
+    }
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/config/package-info.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/config/package-info.java
new file mode 100644
index 0000000..ebcfa19
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/config/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 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.
+ */
+
+/**
+ * BGP peers configuration interfaces.
+ */
+package org.onosproject.sdxl3.config;
\ No newline at end of file
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/impl/SdxL3PeerManager.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/impl/SdxL3PeerManager.java
new file mode 100644
index 0000000..b881ca2
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/impl/SdxL3PeerManager.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright 2016 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.sdxl3.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.SubjectFactories;
+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.host.InterfaceIpAddress;
+import org.onosproject.net.intent.IntentUtils;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.routing.IntentSynchronizationService;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.BgpConfig;
+import org.onosproject.sdxl3.PeerConnectivityService;
+import org.onosproject.sdxl3.SdxL3;
+import org.onosproject.sdxl3.config.BgpPeersConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Manages the connectivity requirements between peers.
+ */
+@Service
+@Component(immediate = true, enabled = false)
+public class SdxL3PeerManager implements SdxL3PeerService {
+
+    private static final String CONFIG_KEY = "bgpPeers";
+
+    private static final int PRIORITY_OFFSET = 1000;
+
+    private static final String SUFFIX_DST = "dst";
+    private static final String SUFFIX_SRC = "src";
+    private static final String SUFFIX_ICMP = "icmp";
+
+    private static final Logger log = LoggerFactory.getLogger(
+            PeerConnectivityManager.class);
+
+    private static final short BGP_PORT = 179;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InterfaceService interfaceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentSynchronizationService intentSynchronizer;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry registry;
+
+    private ConfigFactory configFactory =
+            new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY,
+                    BgpPeersConfig.class, CONFIG_KEY) {
+                @Override
+                public BgpPeersConfig createConfig() {
+                    return new BgpPeersConfig();
+                }
+            };
+
+    private ApplicationId sdxAppId;
+    private ApplicationId routerAppId;
+
+    private final Map<Key, PointToPointIntent> peerIntents = new HashMap<>();
+
+    private final InternalNetworkConfigListener configListener
+            = new InternalNetworkConfigListener();
+
+    @Activate
+    public void activate() {
+        sdxAppId = coreService.getAppId(SdxL3.SDX_L3_APP);
+        routerAppId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
+
+        registry.registerConfigFactory(configFactory);
+
+        configService.addListener(configListener);
+
+        setUpConnectivity();
+
+        log.info("Connectivity with BGP peers established");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        configService.removeListener(configListener);
+
+        log.info("Connectivity with BGP peers stopped");
+    }
+
+    /**
+     * Sets up paths to establish connectivity between all internal
+     * BGP speakers and external BGP peers.
+     */
+    private void setUpConnectivity() {
+        BgpConfig config = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
+
+        if (config == null) {
+            log.warn("No BgpConfig found");
+            return;
+        }
+
+        Map<Key, PointToPointIntent> existingIntents = new HashMap<>(peerIntents);
+
+        for (BgpConfig.BgpSpeakerConfig bgpSpeaker : config.bgpSpeakers()) {
+            log.debug("Start to set up BGP paths for BGP speaker: {}",
+                    bgpSpeaker);
+
+            buildSpeakerIntents(bgpSpeaker).forEach(i -> {
+                PointToPointIntent intent = existingIntents.remove(i.key());
+                if (intent == null || !IntentUtils.intentsAreEqual(i, intent)) {
+                    peerIntents.put(i.key(), i);
+                    intentSynchronizer.submit(i);
+                }
+            });
+        }
+
+        // Remove any remaining intents that we used to have that we don't need
+        // anymore
+        existingIntents.values().forEach(i -> {
+            peerIntents.remove(i.key());
+            intentSynchronizer.withdraw(i);
+        });
+    }
+
+    private Collection<PointToPointIntent> buildSpeakerIntents(BgpConfig.BgpSpeakerConfig speaker) {
+        List<PointToPointIntent> intents = new ArrayList<>();
+
+        for (IpAddress peerAddress : speaker.peers()) {
+            Interface peeringInterface = getInterfaceForPeer(peerAddress);
+
+            if (peeringInterface == null) {
+                log.debug("No peering interface found for peer {} on speaker {}",
+                        peerAddress, speaker);
+                continue;
+            }
+
+            IpAddress peeringAddress = null;
+            for (InterfaceIpAddress address : peeringInterface.ipAddresses()) {
+                if (address.subnetAddress().contains(peerAddress)) {
+                    peeringAddress = address.ipAddress();
+                    break;
+                }
+            }
+
+            checkNotNull(peeringAddress);
+
+            intents.addAll(buildIntents(speaker.connectPoint(), peeringAddress,
+                    peeringInterface.connectPoint(), peerAddress));
+        }
+
+        return intents;
+    }
+
+    private Interface getConfiguredInterfaceForPeer(IpAddress peerAddress) {
+        if (sdxAppId == null) {
+            return null;
+        }
+
+        BgpPeersConfig config = configService.getConfig(sdxAppId, BgpPeersConfig.class);
+        if (config == null) {
+            return null;
+        }
+
+        String intfName = config.getInterfaceNameForPeer(peerAddress);
+        if (intfName != null) {
+            return interfaceService.getInterfaceByName(intfName);
+        }
+        return null;
+    }
+
+    /**
+     * Builds the required intents between the two pairs of connect points and
+     * IP addresses.
+     *
+     * @param portOne the first connect point
+     * @param ipOne the first IP address
+     * @param portTwo the second connect point
+     * @param ipTwo the second IP address
+     * @return the intents to install
+     */
+    private Collection<PointToPointIntent> buildIntents(ConnectPoint portOne,
+                                                        IpAddress ipOne,
+                                                        ConnectPoint portTwo,
+                                                        IpAddress ipTwo) {
+
+        List<PointToPointIntent> intents = new ArrayList<>();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        TrafficSelector selector;
+        Key key;
+
+        byte tcpProtocol;
+        byte icmpProtocol;
+
+        if (ipOne.isIp4()) {
+            tcpProtocol = IPv4.PROTOCOL_TCP;
+            icmpProtocol = IPv4.PROTOCOL_ICMP;
+        } else {
+            tcpProtocol = IPv6.PROTOCOL_TCP;
+            icmpProtocol = IPv6.PROTOCOL_ICMP6;
+        }
+
+        // Path from BGP speaker to BGP peer matching destination TCP port 179
+        selector = buildSelector(tcpProtocol,
+                ipOne,
+                ipTwo,
+                null,
+                BGP_PORT);
+
+        key = buildKey(ipOne, ipTwo, SUFFIX_DST);
+
+        intents.add(PointToPointIntent.builder()
+                .appId(sdxAppId)
+                .key(key)
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(portOne)
+                .egressPoint(portTwo)
+                .priority(PRIORITY_OFFSET)
+                .build());
+
+        // Path from BGP speaker to BGP peer matching source TCP port 179
+        selector = buildSelector(tcpProtocol,
+                ipOne,
+                ipTwo,
+                BGP_PORT,
+                null);
+
+        key = buildKey(ipOne, ipTwo, SUFFIX_SRC);
+
+        intents.add(PointToPointIntent.builder()
+                .appId(sdxAppId)
+                .key(key)
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(portOne)
+                .egressPoint(portTwo)
+                .priority(PRIORITY_OFFSET)
+                .build());
+
+        // Path from BGP peer to BGP speaker matching destination TCP port 179
+        selector = buildSelector(tcpProtocol,
+                ipTwo,
+                ipOne,
+                null,
+                BGP_PORT);
+
+        key = buildKey(ipTwo, ipOne, SUFFIX_DST);
+
+        intents.add(PointToPointIntent.builder()
+                .appId(sdxAppId)
+                .key(key)
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(portTwo)
+                .egressPoint(portOne)
+                .priority(PRIORITY_OFFSET)
+                .build());
+
+        // Path from BGP peer to BGP speaker matching source TCP port 179
+        selector = buildSelector(tcpProtocol,
+                ipTwo,
+                ipOne,
+                BGP_PORT,
+                null);
+
+        key = buildKey(ipTwo, ipOne, SUFFIX_SRC);
+
+        intents.add(PointToPointIntent.builder()
+                .appId(sdxAppId)
+                .key(key)
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(portTwo)
+                .egressPoint(portOne)
+                .priority(PRIORITY_OFFSET)
+                .build());
+
+        // ICMP path from BGP speaker to BGP peer
+        selector = buildSelector(icmpProtocol,
+                ipOne,
+                ipTwo,
+                null,
+                null);
+
+        key = buildKey(ipOne, ipTwo, SUFFIX_ICMP);
+
+        intents.add(PointToPointIntent.builder()
+                .appId(sdxAppId)
+                .key(key)
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(portOne)
+                .egressPoint(portTwo)
+                .priority(PRIORITY_OFFSET)
+                .build());
+
+        // ICMP path from BGP peer to BGP speaker
+        selector = buildSelector(icmpProtocol,
+                ipTwo,
+                ipOne,
+                null,
+                null);
+
+        key = buildKey(ipTwo, ipOne, SUFFIX_ICMP);
+
+        intents.add(PointToPointIntent.builder()
+                .appId(sdxAppId)
+                .key(key)
+                .selector(selector)
+                .treatment(treatment)
+                .ingressPoint(portTwo)
+                .egressPoint(portOne)
+                .priority(PRIORITY_OFFSET)
+                .build());
+
+        return intents;
+    }
+
+    /**
+     * Builds a traffic selector based on the set of input parameters.
+     *
+     * @param ipProto IP protocol
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     * @param srcTcpPort source TCP port, or null if shouldn't be set
+     * @param dstTcpPort destination TCP port, or null if shouldn't be set
+     * @return the new traffic selector
+     */
+    private TrafficSelector buildSelector(byte ipProto, IpAddress srcIp,
+                                          IpAddress dstIp, Short srcTcpPort,
+                                          Short dstTcpPort) {
+        TrafficSelector.Builder builder = DefaultTrafficSelector.builder().matchIPProtocol(ipProto);
+
+        if (dstIp.isIp4()) {
+            builder.matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPSrc(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET_MASK_LENGTH))
+                    .matchIPDst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET_MASK_LENGTH));
+        } else {
+            builder.matchEthType(Ethernet.TYPE_IPV6)
+                    .matchIPv6Src(IpPrefix.valueOf(srcIp, IpPrefix.MAX_INET6_MASK_LENGTH))
+                    .matchIPv6Dst(IpPrefix.valueOf(dstIp, IpPrefix.MAX_INET6_MASK_LENGTH));
+        }
+
+        if (srcTcpPort != null) {
+            builder.matchTcpSrc(TpPort.tpPort(srcTcpPort));
+        }
+
+        if (dstTcpPort != null) {
+            builder.matchTcpDst(TpPort.tpPort(dstTcpPort));
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Builds an intent Key for a point-to-point intent based off the source
+     * and destination IP address, as well as a suffix String to distinguish
+     * between different types of intents between the same source and
+     * destination.
+     *
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     * @param suffix suffix string
+     * @return intent key
+     */
+    private Key buildKey(IpAddress srcIp, IpAddress dstIp, String suffix) {
+        String keyString = new StringBuilder()
+                .append(srcIp.toString())
+                .append("-")
+                .append(dstIp.toString())
+                .append("-")
+                .append(suffix)
+                .toString();
+
+        return Key.of(keyString, sdxAppId);
+    }
+
+    @Override
+    public Interface getInterfaceForPeer(IpAddress peeringAddress) {
+        Interface peeringInterface = getConfiguredInterfaceForPeer(peeringAddress);
+        if (peeringInterface == null) {
+            peeringInterface = interfaceService.getMatchingInterface(peeringAddress);
+        }
+        return peeringInterface;
+    }
+
+    private class InternalNetworkConfigListener implements NetworkConfigListener {
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+            switch (event.type()) {
+            case CONFIG_REGISTERED:
+                break;
+            case CONFIG_UNREGISTERED:
+                break;
+            case CONFIG_ADDED:
+            case CONFIG_UPDATED:
+            case CONFIG_REMOVED:
+                if (event.configClass() == RoutingService.CONFIG_CLASS) {
+                    setUpConnectivity();
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+}
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/impl/package-info.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/impl/package-info.java
new file mode 100644
index 0000000..d172822
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 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.
+ */
+
+/**
+ * Implementation of BGP peers connectivity functionality.
+ */
+package org.onosproject.sdxl3.impl;
\ No newline at end of file
diff --git a/sdx-l3/src/main/java/org/onosproject/sdxl3/package-info.java b/sdx-l3/src/main/java/org/onosproject/sdxl3/package-info.java
new file mode 100644
index 0000000..eb7b599
--- /dev/null
+++ b/sdx-l3/src/main/java/org/onosproject/sdxl3/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 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.
+ */
+
+/**
+ * SDX-L3 application for GEANT.
+ */
+package org.onosproject.sdxl3;
diff --git a/sdx-l3/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/sdx-l3/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..4757f78
--- /dev/null
+++ b/sdx-l3/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,23 @@
+<!--
+  ~ Copyright 2014-2015 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.
+  -->
+
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+  <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+      <command>
+          <action class="org.onosproject.sdxl3.cli.BgpPeersListCommand"/>
+      </command>
+  </command-bundle>
+</blueprint>
diff --git a/sdx-l3/src/main/resources/config-examples/README b/sdx-l3/src/main/resources/config-examples/README
new file mode 100644
index 0000000..061d0c6
--- /dev/null
+++ b/sdx-l3/src/main/resources/config-examples/README
@@ -0,0 +1,2 @@
+The SDX-L3 configuration should be copied to network configuration file:
+  $ONOS_HOME/tools/package/config/network-cfg.json
diff --git a/sdx-l3/src/main/resources/config-examples/network-cfg.json b/sdx-l3/src/main/resources/config-examples/network-cfg.json
new file mode 100644
index 0000000..a51ef92
--- /dev/null
+++ b/sdx-l3/src/main/resources/config-examples/network-cfg.json
@@ -0,0 +1,72 @@
+{
+  "ports" : {
+    "of:00000000000000a1/1" : {
+      "interfaces" : [
+        {
+          "name" : "AS1-conn1",
+          "ips"  : [ "10.0.1.101/24" ],
+          "mac"  : "00:00:00:00:00:01"
+        }
+      ]
+    },
+    "of:00000000000000a2/1" : {
+      "interfaces" : [
+        {
+          "name" : "AS1-conn2",
+          "ips"  : [ "10.0.1.101/24" ],
+          "mac"  : "00:00:00:00:00:01"
+        }
+      ]
+    },
+    "of:00000000000000a5/1" : {
+      "interfaces" : [
+        {
+          "name" : "AS3-conn1",
+          "ips"  : [ "10.0.3.101/24" ],
+          "mac"  : "00:00:00:00:00:01"
+        }
+      ]
+    },
+    "of:00000000000000a6/1" : {
+      "interfaces" : [
+        {
+          "name" : "AS4-conn1",
+          "ips"  : [ "10.0.4.101/24" ],
+          "mac"  : "00:00:00:00:00:01"
+        }
+      ]
+    }
+  },
+  "apps" : {
+    "org.onosproject.sdxl3" : {
+      "bgpPeers" : [
+        {
+          "name" : "AS1-Router1",
+          "ip" : "10.0.1.1",
+          "interface" : "AS1-conn1"
+        },
+        {
+          "name" : "AS1-Router2",
+          "ip" : "10.0.1.129",
+          "interface" : "AS1-conn2"
+        }
+      ]
+    },
+    "org.onosproject.router" : {
+      "bgp" : {
+        "bgpSpeakers" : [
+          {
+            "name" : "bgp",
+            "connectPoint" : "of:00000000000000a3/1",
+            "peers" : [
+              "10.0.1.1",
+              "10.0.1.129",
+              "10.0.3.1",
+              "10.0.4.1"
+            ]
+          }
+        ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/sdx-l3/src/main/resources/config-examples/route-server_network-cfg.json b/sdx-l3/src/main/resources/config-examples/route-server_network-cfg.json
new file mode 100644
index 0000000..c2ea2e3
--- /dev/null
+++ b/sdx-l3/src/main/resources/config-examples/route-server_network-cfg.json
@@ -0,0 +1,52 @@
+{
+  "ports" : {
+    "of:00000000000000a1/1" : {
+      "interfaces" : [
+        {
+          "name" : "AS 65001",
+          "ips"  : [ "10.0.0.3/24" ],
+          "mac"  : "00:00:00:00:00:01"
+        }
+      ]
+    },
+    "of:00000000000000a1/2" : {
+      "interfaces" : [
+        {
+          "name" : "AS 65002",
+          "ips"  : [ "10.0.0.3/24" ],
+          "mac"  : "00:00:00:00:00:01"
+        }
+      ]
+    }
+  },
+  "apps" : {
+    "org.onosproject.sdxl3" : {
+      "bgpPeers" : [
+        {
+          "name" : "AS65001-R1",
+          "ip" : "10.0.0.1",
+          "interface" : "AS 65001"
+        },
+        {
+          "name" : "AS65002-R2",
+          "ip" : "10.0.0.2",
+          "interface" : "AS 65002"
+        }
+      ]
+    },
+    "org.onosproject.router" : {
+      "bgp" : {
+        "bgpSpeakers" : [
+          {
+            "name" : "bgp",
+            "connectPoint" : "of:00000000000000a1/3",
+            "peers" : [
+              "10.0.0.1",
+              "10.0.0.2"
+            ]
+          }
+        ]
+      }
+    }
+  }
+}
\ No newline at end of file