Port the BGP implementation of SDN-IP.
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
new file mode 100644
index 0000000..097c002
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -0,0 +1,355 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelException;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.onlab.onos.sdnip.RouteListener;
+import org.onlab.onos.sdnip.RouteUpdate;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * BGP Session Manager class.
+ */
+public class BgpSessionManager {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpSessionManager.class);
+    private Channel serverChannel;     // Listener for incoming BGP connections
+    private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
+        new ConcurrentHashMap<>();
+    private IpAddress myBgpId;         // Same BGP ID for all peers
+
+    private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
+    private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
+        new ConcurrentHashMap<>();
+
+    private final RouteListener routeListener;
+
+    /**
+     * Constructor for given route listener.
+     *
+     * @param routeListener the route listener to use
+     */
+    public BgpSessionManager(RouteListener routeListener) {
+        this.routeListener = checkNotNull(routeListener);
+    }
+
+    /**
+     * Gets the BGP sessions.
+     *
+     * @return the BGP sessions
+     */
+    public Collection<BgpSession> getBgpSessions() {
+        return bgpSessions.values();
+    }
+
+    /**
+     * Gets the BGP routes.
+     *
+     * @return the BGP routes
+     */
+    public Collection<BgpRouteEntry> getBgpRoutes() {
+        return bgpRoutes.values();
+    }
+
+    /**
+     * Processes the connection from a BGP peer.
+     *
+     * @param bgpSession the BGP session for the peer
+     * @return true if the connection can be established, otherwise false
+     */
+    boolean peerConnected(BgpSession bgpSession) {
+
+        // Test whether there is already a session from the same remote
+        if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
+            return false;               // Duplicate BGP session
+        }
+        bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
+
+        //
+        // If the first connection, set my BGP ID to the local address
+        // of the socket.
+        //
+        if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
+            InetAddress inetAddr =
+                ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
+            IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
+            updateMyBgpId(ip4Address);
+        }
+        return true;
+    }
+
+    /**
+     * Processes the disconnection from a BGP peer.
+     *
+     * @param bgpSession the BGP session for the peer
+     */
+    void peerDisconnected(BgpSession bgpSession) {
+        bgpSessions.remove(bgpSession.getRemoteAddress());
+    }
+
+    /**
+     * Conditionally updates the local BGP ID if it wasn't set already.
+     * <p/>
+     * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
+     *
+     * @param ip4Address the IPv4 address to use as BGP ID
+     */
+    private synchronized void updateMyBgpId(IpAddress ip4Address) {
+        if (myBgpId == null) {
+            myBgpId = ip4Address;
+            log.debug("BGP: My BGP ID is {}", myBgpId);
+        }
+    }
+
+    /**
+     * Gets the local BGP Identifier as an IPv4 address.
+     *
+     * @return the local BGP Identifier as an IPv4 address
+     */
+    IpAddress getMyBgpId() {
+        return myBgpId;
+    }
+
+    /**
+     * Gets the BGP Route Selector.
+     *
+     * @return the BGP Route Selector
+     */
+    BgpRouteSelector getBgpRouteSelector() {
+        return bgpRouteSelector;
+    }
+
+    /**
+     * Starts up BGP Session Manager operation.
+     *
+     * @param listenPortNumber the port number to listen on. By default
+     * it should be BgpConstants.BGP_PORT (179)
+     */
+    public void startUp(int listenPortNumber) {
+        log.debug("BGP Session Manager startUp()");
+
+        ChannelFactory channelFactory =
+            new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
+                                              Executors.newCachedThreadPool());
+        ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+                @Override
+                public ChannelPipeline getPipeline() throws Exception {
+                    // Allocate a new session per connection
+                    BgpSession bgpSessionHandler =
+                        new BgpSession(BgpSessionManager.this);
+                    BgpFrameDecoder bgpFrameDecoder =
+                        new BgpFrameDecoder(bgpSessionHandler);
+
+                    // Setup the processing pipeline
+                    ChannelPipeline pipeline = Channels.pipeline();
+                    pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
+                    pipeline.addLast("BgpSession", bgpSessionHandler);
+                    return pipeline;
+                }
+            };
+        InetSocketAddress listenAddress =
+            new InetSocketAddress(listenPortNumber);
+
+        ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
+        // serverBootstrap.setOptions("reuseAddr", true);
+        serverBootstrap.setOption("child.keepAlive", true);
+        serverBootstrap.setOption("child.tcpNoDelay", true);
+        serverBootstrap.setPipelineFactory(pipelineFactory);
+        try {
+            serverChannel = serverBootstrap.bind(listenAddress);
+        } catch (ChannelException e) {
+            log.debug("Exception binding to BGP port {}: ",
+                      listenAddress.getPort(), e);
+        }
+    }
+
+    /**
+     * Shuts down the BGP Session Manager operation.
+     */
+    public void shutDown() {
+        // TODO: Complete the implementation: remove routes, etc.
+        if (serverChannel != null) {
+            serverChannel.close();
+        }
+    }
+
+    /**
+     * Class to receive and process the BGP routes from each BGP Session/Peer.
+     */
+    class BgpRouteSelector {
+        /**
+         * Processes route entry updates: added/updated and deleted route
+         * entries.
+         *
+         * @param bgpSession the BGP session the route entry updates were
+         * received on
+         * @param addedBgpRouteEntries the added/updated route entries to
+         * process
+         * @param deletedBgpRouteEntries the deleted route entries to process
+         */
+        synchronized void routeUpdates(BgpSession bgpSession,
+                        Collection<BgpRouteEntry> addedBgpRouteEntries,
+                        Collection<BgpRouteEntry> deletedBgpRouteEntries) {
+            //
+            // TODO: Merge the updates from different BGP Peers,
+            // by choosing the best route.
+            //
+
+            // Process the deleted route entries
+            for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
+                processDeletedRoute(bgpSession, bgpRouteEntry);
+            }
+
+            // Process the added/updated route entries
+            for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
+                processAddedRoute(bgpSession, bgpRouteEntry);
+            }
+        }
+
+        /**
+         * Processes an added/updated route entry.
+         *
+         * @param bgpSession the BGP session the route entry update was
+         * received on
+         * @param bgpRouteEntry the added/updated route entry
+         */
+        private void processAddedRoute(BgpSession bgpSession,
+                                       BgpRouteEntry bgpRouteEntry) {
+            RouteUpdate routeUpdate;
+            BgpRouteEntry bestBgpRouteEntry =
+                bgpRoutes.get(bgpRouteEntry.prefix());
+
+            //
+            // Install the new route entry if it is better than the
+            // current best route.
+            //
+            if ((bestBgpRouteEntry == null) ||
+                bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
+                bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
+                routeUpdate =
+                    new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
+                // Forward the result route updates to the Route Listener
+                routeListener.update(routeUpdate);
+                return;
+            }
+
+            //
+            // If the route entry arrived on the same BGP Session as
+            // the current best route, then elect the next best route
+            // and install it.
+            //
+            if (bestBgpRouteEntry.getBgpSession() !=
+                bgpRouteEntry.getBgpSession()) {
+                return;
+            }
+
+            // Find the next best route
+            bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+            if (bestBgpRouteEntry == null) {
+                //
+                // TODO: Shouldn't happen. Install the new route as a
+                // pre-caution.
+                //
+                log.debug("BGP next best route for prefix {} is missing. " +
+                          "Adding the route that is currently processed.",
+                          bgpRouteEntry.prefix());
+                bestBgpRouteEntry = bgpRouteEntry;
+            }
+            // Install the next best route
+            bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
+            routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                          bestBgpRouteEntry);
+            // Forward the result route updates to the Route Listener
+            routeListener.update(routeUpdate);
+        }
+
+        /**
+         * Processes a deleted route entry.
+         *
+         * @param bgpSession the BGP session the route entry update was
+         * received on
+         * @param bgpRouteEntry the deleted route entry
+         */
+        private void processDeletedRoute(BgpSession bgpSession,
+                                         BgpRouteEntry bgpRouteEntry) {
+            RouteUpdate routeUpdate;
+            BgpRouteEntry bestBgpRouteEntry =
+                bgpRoutes.get(bgpRouteEntry.prefix());
+
+            //
+            // Remove the route entry only if it was the best one.
+            // Install the the next best route if it exists.
+            //
+            // NOTE: We intentionally use "==" instead of method equals(),
+            // because we need to check whether this is same object.
+            //
+            if (bgpRouteEntry != bestBgpRouteEntry) {
+                return;         // Nothing to do
+            }
+
+            //
+            // Find the next best route
+            //
+            bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+            if (bestBgpRouteEntry != null) {
+                // Install the next best route
+                bgpRoutes.put(bestBgpRouteEntry.prefix(),
+                              bestBgpRouteEntry);
+                routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                              bestBgpRouteEntry);
+                // Forward the result route updates to the Route Listener
+                routeListener.update(routeUpdate);
+                return;
+            }
+
+            //
+            // No route found. Remove the route entry
+            //
+            bgpRoutes.remove(bgpRouteEntry.prefix());
+            routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
+                                          bgpRouteEntry);
+            // Forward the result route updates to the Route Listener
+            routeListener.update(routeUpdate);
+        }
+
+        /**
+         * Finds the best route entry among all BGP Sessions.
+         *
+         * @param prefix the prefix of the route
+         * @return the best route if found, otherwise null
+         */
+        private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
+            BgpRouteEntry bestRoute = null;
+
+            // Iterate across all BGP Sessions and select the best route
+            for (BgpSession bgpSession : bgpSessions.values()) {
+                BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
+                if (route == null) {
+                    continue;
+                }
+                if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
+                    bestRoute = route;
+                }
+            }
+            return bestRoute;
+        }
+    }
+}