| /* |
| * Copyright 2015-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 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); |
| 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); |
| } |
| } |
| } |