/* * Copyright 2017-present 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 static org.onlab.util.Tools.groupedThreads; 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(groupedThreads("BgpSession", "timer-%d", log)); 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); log.debug("Exception:", e.getCause()); 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(addedRoutes, deletedRoutes4); bgpRouteSelector.routeUpdates(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); } } }