blob: c66d2a1eedebf8bb1fda4a798f2c76073cc1f45c [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
Jonathan Hartab63aac2014-10-16 08:52:55 -070016package org.onlab.onos.sdnip.bgp;
17
18import static com.google.common.base.Preconditions.checkNotNull;
Yuta HIGUCHIf845cc32014-11-25 19:28:12 -080019import static org.onlab.util.Tools.namedThreads;
Jonathan Hartab63aac2014-10-16 08:52:55 -070020
21import java.net.InetAddress;
22import java.net.InetSocketAddress;
23import java.net.SocketAddress;
24import java.util.Collection;
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -080025import java.util.LinkedList;
Jonathan Hartab63aac2014-10-16 08:52:55 -070026import java.util.concurrent.ConcurrentHashMap;
27import java.util.concurrent.ConcurrentMap;
28import java.util.concurrent.Executors;
29
30import org.jboss.netty.bootstrap.ServerBootstrap;
31import org.jboss.netty.channel.Channel;
32import org.jboss.netty.channel.ChannelException;
33import org.jboss.netty.channel.ChannelFactory;
34import org.jboss.netty.channel.ChannelPipeline;
35import org.jboss.netty.channel.ChannelPipelineFactory;
36import org.jboss.netty.channel.Channels;
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080037import org.jboss.netty.channel.group.ChannelGroup;
38import org.jboss.netty.channel.group.DefaultChannelGroup;
Jonathan Hartab63aac2014-10-16 08:52:55 -070039import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
40import org.onlab.onos.sdnip.RouteListener;
41import org.onlab.onos.sdnip.RouteUpdate;
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080042import org.onlab.packet.Ip4Address;
43import org.onlab.packet.Ip4Prefix;
Jonathan Hartab63aac2014-10-16 08:52:55 -070044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
47/**
48 * BGP Session Manager class.
49 */
50public class BgpSessionManager {
51 private static final Logger log =
52 LoggerFactory.getLogger(BgpSessionManager.class);
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080053 boolean isShutdown = true;
Jonathan Hartab63aac2014-10-16 08:52:55 -070054 private Channel serverChannel; // Listener for incoming BGP connections
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080055 private ServerBootstrap serverBootstrap;
56 private ChannelGroup allChannels = new DefaultChannelGroup();
Jonathan Hartab63aac2014-10-16 08:52:55 -070057 private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
58 new ConcurrentHashMap<>();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080059 private Ip4Address myBgpId; // Same BGP ID for all peers
Jonathan Hartab63aac2014-10-16 08:52:55 -070060
61 private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080062 private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRoutes =
Jonathan Hartab63aac2014-10-16 08:52:55 -070063 new ConcurrentHashMap<>();
64
65 private final RouteListener routeListener;
66
67 /**
68 * Constructor for given route listener.
69 *
70 * @param routeListener the route listener to use
71 */
72 public BgpSessionManager(RouteListener routeListener) {
73 this.routeListener = checkNotNull(routeListener);
74 }
75
76 /**
77 * Gets the BGP sessions.
78 *
79 * @return the BGP sessions
80 */
81 public Collection<BgpSession> getBgpSessions() {
82 return bgpSessions.values();
83 }
84
85 /**
86 * Gets the BGP routes.
87 *
88 * @return the BGP routes
89 */
90 public Collection<BgpRouteEntry> getBgpRoutes() {
91 return bgpRoutes.values();
92 }
93
94 /**
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080095 * Adds the channel for a BGP session.
96 *
97 * @param channel the channel to add
98 */
99 void addSessionChannel(Channel channel) {
100 allChannels.add(channel);
101 }
102
103 /**
104 * Removes the channel for a BGP session.
105 *
106 * @param channel the channel to remove
107 */
108 void removeSessionChannel(Channel channel) {
109 allChannels.remove(channel);
110 }
111
112 /**
Jonathan Hartab63aac2014-10-16 08:52:55 -0700113 * Processes the connection from a BGP peer.
114 *
115 * @param bgpSession the BGP session for the peer
116 * @return true if the connection can be established, otherwise false
117 */
118 boolean peerConnected(BgpSession bgpSession) {
119
120 // Test whether there is already a session from the same remote
121 if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
122 return false; // Duplicate BGP session
123 }
124 bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
125
126 //
127 // If the first connection, set my BGP ID to the local address
128 // of the socket.
129 //
130 if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
131 InetAddress inetAddr =
132 ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800133 Ip4Address ip4Address = Ip4Address.valueOf(inetAddr.getAddress());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700134 updateMyBgpId(ip4Address);
135 }
136 return true;
137 }
138
139 /**
140 * Processes the disconnection from a BGP peer.
141 *
142 * @param bgpSession the BGP session for the peer
143 */
144 void peerDisconnected(BgpSession bgpSession) {
145 bgpSessions.remove(bgpSession.getRemoteAddress());
146 }
147
148 /**
149 * Conditionally updates the local BGP ID if it wasn't set already.
150 * <p/>
151 * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
152 *
153 * @param ip4Address the IPv4 address to use as BGP ID
154 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800155 private synchronized void updateMyBgpId(Ip4Address ip4Address) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700156 if (myBgpId == null) {
157 myBgpId = ip4Address;
158 log.debug("BGP: My BGP ID is {}", myBgpId);
159 }
160 }
161
162 /**
163 * Gets the local BGP Identifier as an IPv4 address.
164 *
165 * @return the local BGP Identifier as an IPv4 address
166 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800167 Ip4Address getMyBgpId() {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700168 return myBgpId;
169 }
170
171 /**
172 * Gets the BGP Route Selector.
173 *
174 * @return the BGP Route Selector
175 */
176 BgpRouteSelector getBgpRouteSelector() {
177 return bgpRouteSelector;
178 }
179
180 /**
181 * Starts up BGP Session Manager operation.
182 *
183 * @param listenPortNumber the port number to listen on. By default
184 * it should be BgpConstants.BGP_PORT (179)
185 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800186 public void start(int listenPortNumber) {
187 log.debug("BGP Session Manager start.");
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800188 isShutdown = false;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700189
190 ChannelFactory channelFactory =
Yuta HIGUCHIf845cc32014-11-25 19:28:12 -0800191 new NioServerSocketChannelFactory(Executors.newCachedThreadPool(namedThreads("BGP-SM-boss-%d")),
192 Executors.newCachedThreadPool(namedThreads("BGP-SM-worker-%d")));
Jonathan Hartab63aac2014-10-16 08:52:55 -0700193 ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
194 @Override
195 public ChannelPipeline getPipeline() throws Exception {
196 // Allocate a new session per connection
197 BgpSession bgpSessionHandler =
198 new BgpSession(BgpSessionManager.this);
199 BgpFrameDecoder bgpFrameDecoder =
200 new BgpFrameDecoder(bgpSessionHandler);
201
202 // Setup the processing pipeline
203 ChannelPipeline pipeline = Channels.pipeline();
204 pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
205 pipeline.addLast("BgpSession", bgpSessionHandler);
206 return pipeline;
207 }
208 };
209 InetSocketAddress listenAddress =
210 new InetSocketAddress(listenPortNumber);
211
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800212 serverBootstrap = new ServerBootstrap(channelFactory);
Jonathan Hartab63aac2014-10-16 08:52:55 -0700213 // serverBootstrap.setOptions("reuseAddr", true);
214 serverBootstrap.setOption("child.keepAlive", true);
215 serverBootstrap.setOption("child.tcpNoDelay", true);
216 serverBootstrap.setPipelineFactory(pipelineFactory);
217 try {
218 serverChannel = serverBootstrap.bind(listenAddress);
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800219 allChannels.add(serverChannel);
Jonathan Hartab63aac2014-10-16 08:52:55 -0700220 } catch (ChannelException e) {
221 log.debug("Exception binding to BGP port {}: ",
222 listenAddress.getPort(), e);
223 }
224 }
225
226 /**
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800227 * Stops the BGP Session Manager operation.
Jonathan Hartab63aac2014-10-16 08:52:55 -0700228 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800229 public void stop() {
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800230 isShutdown = true;
231 allChannels.close().awaitUninterruptibly();
232 serverBootstrap.releaseExternalResources();
Jonathan Hartab63aac2014-10-16 08:52:55 -0700233 }
234
235 /**
236 * Class to receive and process the BGP routes from each BGP Session/Peer.
237 */
238 class BgpRouteSelector {
239 /**
240 * Processes route entry updates: added/updated and deleted route
241 * entries.
242 *
243 * @param bgpSession the BGP session the route entry updates were
244 * received on
245 * @param addedBgpRouteEntries the added/updated route entries to
246 * process
247 * @param deletedBgpRouteEntries the deleted route entries to process
248 */
249 synchronized void routeUpdates(BgpSession bgpSession,
250 Collection<BgpRouteEntry> addedBgpRouteEntries,
251 Collection<BgpRouteEntry> deletedBgpRouteEntries) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800252 Collection<RouteUpdate> routeUpdates = new LinkedList<>();
253 RouteUpdate routeUpdate;
254
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800255 if (isShutdown) {
256 return; // Ignore any leftover updates if shutdown
257 }
Jonathan Hartab63aac2014-10-16 08:52:55 -0700258 // Process the deleted route entries
259 for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800260 routeUpdate = processDeletedRoute(bgpSession, bgpRouteEntry);
261 if (routeUpdate != null) {
262 routeUpdates.add(routeUpdate);
263 }
Jonathan Hartab63aac2014-10-16 08:52:55 -0700264 }
265
266 // Process the added/updated route entries
267 for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800268 routeUpdate = processAddedRoute(bgpSession, bgpRouteEntry);
269 if (routeUpdate != null) {
270 routeUpdates.add(routeUpdate);
271 }
Jonathan Hartab63aac2014-10-16 08:52:55 -0700272 }
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800273 routeListener.update(routeUpdates);
Jonathan Hartab63aac2014-10-16 08:52:55 -0700274 }
275
276 /**
277 * Processes an added/updated route entry.
278 *
279 * @param bgpSession the BGP session the route entry update was
280 * received on
281 * @param bgpRouteEntry the added/updated route entry
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800282 * @return the result route update that should be forwarded to the
283 * Route Listener, or null if no route update should be forwarded
Jonathan Hartab63aac2014-10-16 08:52:55 -0700284 */
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800285 private RouteUpdate processAddedRoute(BgpSession bgpSession,
286 BgpRouteEntry bgpRouteEntry) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700287 RouteUpdate routeUpdate;
288 BgpRouteEntry bestBgpRouteEntry =
289 bgpRoutes.get(bgpRouteEntry.prefix());
290
291 //
292 // Install the new route entry if it is better than the
293 // current best route.
294 //
295 if ((bestBgpRouteEntry == null) ||
296 bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
297 bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
298 routeUpdate =
299 new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800300 return routeUpdate;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700301 }
302
303 //
304 // If the route entry arrived on the same BGP Session as
305 // the current best route, then elect the next best route
306 // and install it.
307 //
308 if (bestBgpRouteEntry.getBgpSession() !=
309 bgpRouteEntry.getBgpSession()) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800310 return null; // Nothing to do
Jonathan Hartab63aac2014-10-16 08:52:55 -0700311 }
312
313 // Find the next best route
314 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
315 if (bestBgpRouteEntry == null) {
316 //
317 // TODO: Shouldn't happen. Install the new route as a
318 // pre-caution.
319 //
320 log.debug("BGP next best route for prefix {} is missing. " +
321 "Adding the route that is currently processed.",
322 bgpRouteEntry.prefix());
323 bestBgpRouteEntry = bgpRouteEntry;
324 }
325 // Install the next best route
326 bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
327 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
328 bestBgpRouteEntry);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800329 return routeUpdate;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700330 }
331
332 /**
333 * Processes a deleted route entry.
334 *
335 * @param bgpSession the BGP session the route entry update was
336 * received on
337 * @param bgpRouteEntry the deleted route entry
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800338 * @return the result route update that should be forwarded to the
339 * Route Listener, or null if no route update should be forwarded
Jonathan Hartab63aac2014-10-16 08:52:55 -0700340 */
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800341 private RouteUpdate processDeletedRoute(BgpSession bgpSession,
342 BgpRouteEntry bgpRouteEntry) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700343 RouteUpdate routeUpdate;
344 BgpRouteEntry bestBgpRouteEntry =
345 bgpRoutes.get(bgpRouteEntry.prefix());
346
347 //
348 // Remove the route entry only if it was the best one.
349 // Install the the next best route if it exists.
350 //
351 // NOTE: We intentionally use "==" instead of method equals(),
352 // because we need to check whether this is same object.
353 //
354 if (bgpRouteEntry != bestBgpRouteEntry) {
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800355 return null; // Nothing to do
Jonathan Hartab63aac2014-10-16 08:52:55 -0700356 }
357
358 //
359 // Find the next best route
360 //
361 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
362 if (bestBgpRouteEntry != null) {
363 // Install the next best route
364 bgpRoutes.put(bestBgpRouteEntry.prefix(),
365 bestBgpRouteEntry);
366 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
367 bestBgpRouteEntry);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800368 return routeUpdate;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700369 }
370
371 //
372 // No route found. Remove the route entry
373 //
374 bgpRoutes.remove(bgpRouteEntry.prefix());
375 routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
376 bgpRouteEntry);
Pavlin Radoslavov248c2ae2014-12-02 09:51:25 -0800377 return routeUpdate;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700378 }
379
380 /**
381 * Finds the best route entry among all BGP Sessions.
382 *
383 * @param prefix the prefix of the route
384 * @return the best route if found, otherwise null
385 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800386 private BgpRouteEntry findBestBgpRoute(Ip4Prefix prefix) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700387 BgpRouteEntry bestRoute = null;
388
389 // Iterate across all BGP Sessions and select the best route
390 for (BgpSession bgpSession : bgpSessions.values()) {
391 BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
392 if (route == null) {
393 continue;
394 }
395 if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
396 bestRoute = route;
397 }
398 }
399 return bestRoute;
400 }
401 }
402}