diff options
Diffstat (limited to 'framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java')
-rw-r--r-- | framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java new file mode 100644 index 00000000..dedf6ac3 --- /dev/null +++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java @@ -0,0 +1,473 @@ +/* + * 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.routing.bgp; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.Timer; +import org.jboss.netty.util.TimerTask; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.Ip6Prefix; +import org.onlab.packet.IpPrefix; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +/** + * Class for handling the BGP peer sessions. + * There is one instance per each BGP peer session. + */ +public class BgpSession extends SimpleChannelHandler { + private static final Logger log = + LoggerFactory.getLogger(BgpSession.class); + + private final BgpSessionManager bgpSessionManager; + + // Local flag to indicate the session is closed. + // It is used to avoid the Netty's asynchronous closing of a channel. + private boolean isClosed = false; + + // BGP session info: local and remote + private final BgpSessionInfo localInfo; // BGP session local info + private final BgpSessionInfo remoteInfo; // BGP session remote info + + // Timers state + private Timer timer = new HashedWheelTimer(); + private volatile Timeout keepaliveTimeout; // Periodic KEEPALIVE + private volatile Timeout sessionTimeout; // Session timeout + + // BGP RIB-IN routing entries from this peer + private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRibIn4 = + new ConcurrentHashMap<>(); + private ConcurrentMap<Ip6Prefix, BgpRouteEntry> bgpRibIn6 = + new ConcurrentHashMap<>(); + + /** + * Constructor for a given BGP Session Manager. + * + * @param bgpSessionManager the BGP Session Manager to use + */ + BgpSession(BgpSessionManager bgpSessionManager) { + this.bgpSessionManager = bgpSessionManager; + this.localInfo = new BgpSessionInfo(); + this.remoteInfo = new BgpSessionInfo(); + + // NOTE: We support only BGP4 + this.localInfo.setBgpVersion(BgpConstants.BGP_VERSION); + } + + /** + * Gets the BGP Session Manager. + * + * @return the BGP Session Manager + */ + BgpSessionManager getBgpSessionManager() { + return bgpSessionManager; + } + + /** + * Gets the BGP Session local information. + * + * @return the BGP Session local information. + */ + public BgpSessionInfo localInfo() { + return localInfo; + } + + /** + * Gets the BGP Session remote information. + * + * @return the BGP Session remote information. + */ + public BgpSessionInfo remoteInfo() { + return remoteInfo; + } + + /** + * Gets the BGP Multiprotocol Extensions for the session. + * + * @return true if the BGP Multiprotocol Extensions are enabled for the + * session, otherwise false + */ + public boolean mpExtensions() { + return remoteInfo.mpExtensions() && localInfo.mpExtensions(); + } + + /** + * Gets the BGP session 4 octet AS path capability. + * + * @return true when the BGP session is 4 octet AS path capable + */ + public boolean isAs4OctetCapable() { + return remoteInfo.as4OctetCapability() && + localInfo.as4OctetCapability(); + } + + /** + * Gets the IPv4 BGP RIB-IN routing entries. + * + * @return the IPv4 BGP RIB-IN routing entries + */ + public Collection<BgpRouteEntry> getBgpRibIn4() { + return bgpRibIn4.values(); + } + + /** + * Gets the IPv6 BGP RIB-IN routing entries. + * + * @return the IPv6 BGP RIB-IN routing entries + */ + public Collection<BgpRouteEntry> getBgpRibIn6() { + return bgpRibIn6.values(); + } + + /** + * Finds an IPv4 BGP routing entry for a prefix in the IPv4 BGP RIB-IN. + * + * @param prefix the IPv4 prefix of the route to search for + * @return the IPv4 BGP routing entry if found, otherwise null + */ + public BgpRouteEntry findBgpRoute(Ip4Prefix prefix) { + return bgpRibIn4.get(prefix); + } + + /** + * Finds an IPv6 BGP routing entry for a prefix in the IPv6 BGP RIB-IN. + * + * @param prefix the IPv6 prefix of the route to search for + * @return the IPv6 BGP routing entry if found, otherwise null + */ + public BgpRouteEntry findBgpRoute(Ip6Prefix prefix) { + return bgpRibIn6.get(prefix); + } + + /** + * Finds a BGP routing entry for a prefix in the BGP RIB-IN. The prefix + * can be either IPv4 or IPv6. + * + * @param prefix the IP prefix of the route to search for + * @return the BGP routing entry if found, otherwise null + */ + public BgpRouteEntry findBgpRoute(IpPrefix prefix) { + if (prefix.isIp4()) { + // IPv4 prefix + Ip4Prefix ip4Prefix = prefix.getIp4Prefix(); + return bgpRibIn4.get(ip4Prefix); + } + + // IPv6 prefix + Ip6Prefix ip6Prefix = prefix.getIp6Prefix(); + return bgpRibIn6.get(ip6Prefix); + } + + /** + * Adds a BGP route. The route can be either IPv4 or IPv6. + * + * @param bgpRouteEntry the BGP route entry to use + */ + void addBgpRoute(BgpRouteEntry bgpRouteEntry) { + if (bgpRouteEntry.isIp4()) { + // IPv4 route + Ip4Prefix ip4Prefix = bgpRouteEntry.prefix().getIp4Prefix(); + bgpRibIn4.put(ip4Prefix, bgpRouteEntry); + } else { + // IPv6 route + Ip6Prefix ip6Prefix = bgpRouteEntry.prefix().getIp6Prefix(); + bgpRibIn6.put(ip6Prefix, bgpRouteEntry); + } + } + + /** + * Removes an IPv4 BGP route for a prefix. + * + * @param prefix the prefix to use + * @return true if the route was found and removed, otherwise false + */ + boolean removeBgpRoute(Ip4Prefix prefix) { + return (bgpRibIn4.remove(prefix) != null); + } + + /** + * Removes an IPv6 BGP route for a prefix. + * + * @param prefix the prefix to use + * @return true if the route was found and removed, otherwise false + */ + boolean removeBgpRoute(Ip6Prefix prefix) { + return (bgpRibIn6.remove(prefix) != null); + } + + /** + * Removes a BGP route for a prefix. The prefix can be either IPv4 or IPv6. + * + * @param prefix the prefix to use + * @return true if the route was found and removed, otherwise false + */ + boolean removeBgpRoute(IpPrefix prefix) { + if (prefix.isIp4()) { + return (bgpRibIn4.remove(prefix.getIp4Prefix()) != null); // IPv4 + } + return (bgpRibIn6.remove(prefix.getIp6Prefix()) != null); // IPv6 + } + + /** + * Tests whether the session is closed. + * <p> + * NOTE: We use this method to avoid the Netty's asynchronous closing + * of a channel. + * </p> + * @return true if the session is closed + */ + boolean isClosed() { + return isClosed; + } + + /** + * Closes the session. + * + * @param ctx the Channel Handler Context + */ + void closeSession(ChannelHandlerContext ctx) { + timer.stop(); + closeChannel(ctx); + } + + /** + * Closes the Netty channel. + * + * @param ctx the Channel Handler Context + */ + void closeChannel(ChannelHandlerContext ctx) { + isClosed = true; + ctx.getChannel().close(); + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, + ChannelStateEvent channelEvent) { + bgpSessionManager.addSessionChannel(channelEvent.getChannel()); + } + + @Override + public void channelClosed(ChannelHandlerContext ctx, + ChannelStateEvent channelEvent) { + bgpSessionManager.removeSessionChannel(channelEvent.getChannel()); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, + ChannelStateEvent channelEvent) { + localInfo.setAddress(ctx.getChannel().getLocalAddress()); + remoteInfo.setAddress(ctx.getChannel().getRemoteAddress()); + + // Assign the local and remote IPv4 addresses + InetAddress inetAddr; + if (localInfo.address() instanceof InetSocketAddress) { + inetAddr = ((InetSocketAddress) localInfo.address()).getAddress(); + localInfo.setIp4Address(Ip4Address.valueOf(inetAddr.getAddress())); + } + if (remoteInfo.address() instanceof InetSocketAddress) { + inetAddr = ((InetSocketAddress) remoteInfo.address()).getAddress(); + remoteInfo.setIp4Address(Ip4Address.valueOf(inetAddr.getAddress())); + } + + log.debug("BGP Session Connected from {} on {}", + remoteInfo.address(), localInfo.address()); + if (!bgpSessionManager.peerConnected(this)) { + log.debug("Cannot setup BGP Session Connection from {}. Closing...", + remoteInfo.address()); + ctx.getChannel().close(); + } + + // + // Assign the local BGP ID + // NOTE: This should be configuration-based + // + localInfo.setBgpId(bgpSessionManager.getMyBgpId()); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, + ChannelStateEvent channelEvent) { + log.debug("BGP Session Disconnected from {} on {}", + ctx.getChannel().getRemoteAddress(), + ctx.getChannel().getLocalAddress()); + processChannelDisconnected(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { + log.debug("BGP Session Exception Caught from {} on {}: {}", + ctx.getChannel().getRemoteAddress(), + ctx.getChannel().getLocalAddress(), + e); + processChannelDisconnected(); + } + + /** + * Processes the channel being disconnected. + */ + private void processChannelDisconnected() { + // + // Withdraw the routes advertised by this BGP peer + // + // NOTE: We must initialize the RIB-IN before propagating the withdraws + // for further processing. Otherwise, the BGP Decision Process + // will use those routes again. + // + Collection<BgpRouteEntry> deletedRoutes4 = bgpRibIn4.values(); + Collection<BgpRouteEntry> deletedRoutes6 = bgpRibIn6.values(); + bgpRibIn4 = new ConcurrentHashMap<>(); + bgpRibIn6 = new ConcurrentHashMap<>(); + + // Push the updates to the BGP Merged RIB + BgpRouteSelector bgpRouteSelector = + bgpSessionManager.getBgpRouteSelector(); + Collection<BgpRouteEntry> addedRoutes = Collections.emptyList(); + bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes4); + bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes6); + + bgpSessionManager.peerDisconnected(this); + } + + /** + * Restarts the BGP KeepaliveTimer. + * + * @param ctx the Channel Handler Context to use + */ + void restartKeepaliveTimer(ChannelHandlerContext ctx) { + long localKeepaliveInterval = 0; + + // + // Compute the local Keepalive interval + // + if (localInfo.holdtime() != 0) { + localKeepaliveInterval = Math.max(localInfo.holdtime() / + BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL, + BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL); + } + + // Restart the Keepalive timer + if (localKeepaliveInterval == 0) { + return; // Nothing to do + } + keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx), + localKeepaliveInterval, + TimeUnit.SECONDS); + } + + /** + * Task class for transmitting KEEPALIVE messages. + */ + private final class TransmitKeepaliveTask implements TimerTask { + private final ChannelHandlerContext ctx; + + /** + * Constructor for given Channel Handler Context. + * + * @param ctx the Channel Handler Context to use + */ + TransmitKeepaliveTask(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + @Override + public void run(Timeout timeout) throws Exception { + if (timeout.isCancelled()) { + return; + } + if (!ctx.getChannel().isOpen()) { + return; + } + + // Transmit the KEEPALIVE + ChannelBuffer txMessage = BgpKeepalive.prepareBgpKeepalive(); + ctx.getChannel().write(txMessage); + + // Restart the KEEPALIVE timer + restartKeepaliveTimer(ctx); + } + } + + /** + * Restarts the BGP Session Timeout Timer. + * + * @param ctx the Channel Handler Context to use + */ + void restartSessionTimeoutTimer(ChannelHandlerContext ctx) { + if (remoteInfo.holdtime() == 0) { + return; // Nothing to do + } + if (sessionTimeout != null) { + sessionTimeout.cancel(); + } + sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx), + remoteInfo.holdtime(), + TimeUnit.SECONDS); + } + + /** + * Task class for BGP Session timeout. + */ + private final class SessionTimeoutTask implements TimerTask { + private final ChannelHandlerContext ctx; + + /** + * Constructor for given Channel Handler Context. + * + * @param ctx the Channel Handler Context to use + */ + SessionTimeoutTask(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + @Override + public void run(Timeout timeout) throws Exception { + if (timeout.isCancelled()) { + return; + } + if (!ctx.getChannel().isOpen()) { + return; + } + + log.debug("BGP Session Timeout: peer {}", remoteInfo.address()); + // + // ERROR: Invalid Optional Parameter Length field: Unspecific + // + // Send NOTIFICATION and close the connection + int errorCode = BgpConstants.Notifications.HoldTimerExpired.ERROR_CODE; + int errorSubcode = BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC; + ChannelBuffer txMessage = + BgpNotification.prepareBgpNotification(errorCode, errorSubcode, + null); + ctx.getChannel().write(txMessage); + closeChannel(ctx); + } + } +} |