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