blob: c66d2a1eedebf8bb1fda4a798f2c76073cc1f45c [file] [log] [blame]
/*
* Copyright 2014 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.onlab.onos.sdnip.bgp;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.util.Tools.namedThreads;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.LinkedList;
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.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.onlab.onos.sdnip.RouteListener;
import org.onlab.onos.sdnip.RouteUpdate;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* BGP Session Manager class.
*/
public class BgpSessionManager {
private static final Logger log =
LoggerFactory.getLogger(BgpSessionManager.class);
boolean isShutdown = true;
private Channel serverChannel; // Listener for incoming BGP connections
private ServerBootstrap serverBootstrap;
private ChannelGroup allChannels = new DefaultChannelGroup();
private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
new ConcurrentHashMap<>();
private Ip4Address myBgpId; // Same BGP ID for all peers
private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
private ConcurrentMap<Ip4Prefix, 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();
}
/**
* Adds the channel for a BGP session.
*
* @param channel the channel to add
*/
void addSessionChannel(Channel channel) {
allChannels.add(channel);
}
/**
* Removes the channel for a BGP session.
*
* @param channel the channel to remove
*/
void removeSessionChannel(Channel channel) {
allChannels.remove(channel);
}
/**
* 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();
Ip4Address ip4Address = Ip4Address.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(Ip4Address 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
*/
Ip4Address 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 start(int listenPortNumber) {
log.debug("BGP Session Manager start.");
isShutdown = false;
ChannelFactory channelFactory =
new NioServerSocketChannelFactory(Executors.newCachedThreadPool(namedThreads("BGP-SM-boss-%d")),
Executors.newCachedThreadPool(namedThreads("BGP-SM-worker-%d")));
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 = 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);
allChannels.add(serverChannel);
} catch (ChannelException e) {
log.debug("Exception binding to BGP port {}: ",
listenAddress.getPort(), e);
}
}
/**
* Stops the BGP Session Manager operation.
*/
public void stop() {
isShutdown = true;
allChannels.close().awaitUninterruptibly();
serverBootstrap.releaseExternalResources();
}
/**
* 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) {
Collection<RouteUpdate> routeUpdates = new LinkedList<>();
RouteUpdate routeUpdate;
if (isShutdown) {
return; // Ignore any leftover updates if shutdown
}
// Process the deleted route entries
for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
routeUpdate = processDeletedRoute(bgpSession, bgpRouteEntry);
if (routeUpdate != null) {
routeUpdates.add(routeUpdate);
}
}
// Process the added/updated route entries
for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
routeUpdate = processAddedRoute(bgpSession, bgpRouteEntry);
if (routeUpdate != null) {
routeUpdates.add(routeUpdate);
}
}
routeListener.update(routeUpdates);
}
/**
* 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
* @return the result route update that should be forwarded to the
* Route Listener, or null if no route update should be forwarded
*/
private RouteUpdate 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);
return routeUpdate;
}
//
// 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 null; // Nothing to do
}
// 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);
return routeUpdate;
}
/**
* Processes a deleted route entry.
*
* @param bgpSession the BGP session the route entry update was
* received on
* @param bgpRouteEntry the deleted route entry
* @return the result route update that should be forwarded to the
* Route Listener, or null if no route update should be forwarded
*/
private RouteUpdate 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 null; // 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);
return routeUpdate;
}
//
// No route found. Remove the route entry
//
bgpRoutes.remove(bgpRouteEntry.prefix());
routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
bgpRouteEntry);
return 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(Ip4Prefix 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;
}
}
}