blob: b483f14fa596d6285e9204f9788cc8b0fee88622 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 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;
}
}
}