blob: aaf26fae34ec8a40741ac4ade921354a43caa9f5 [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;
19
20import java.net.InetAddress;
21import java.net.InetSocketAddress;
22import java.net.SocketAddress;
23import java.util.Collection;
24import java.util.concurrent.ConcurrentHashMap;
25import java.util.concurrent.ConcurrentMap;
26import java.util.concurrent.Executors;
27
28import org.jboss.netty.bootstrap.ServerBootstrap;
29import org.jboss.netty.channel.Channel;
30import org.jboss.netty.channel.ChannelException;
31import org.jboss.netty.channel.ChannelFactory;
32import org.jboss.netty.channel.ChannelPipeline;
33import org.jboss.netty.channel.ChannelPipelineFactory;
34import org.jboss.netty.channel.Channels;
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080035import org.jboss.netty.channel.group.ChannelGroup;
36import org.jboss.netty.channel.group.DefaultChannelGroup;
Jonathan Hartab63aac2014-10-16 08:52:55 -070037import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
38import org.onlab.onos.sdnip.RouteListener;
39import org.onlab.onos.sdnip.RouteUpdate;
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080040import org.onlab.packet.Ip4Address;
41import org.onlab.packet.Ip4Prefix;
Jonathan Hartab63aac2014-10-16 08:52:55 -070042import org.slf4j.Logger;
43import org.slf4j.LoggerFactory;
44
45/**
46 * BGP Session Manager class.
47 */
48public class BgpSessionManager {
49 private static final Logger log =
50 LoggerFactory.getLogger(BgpSessionManager.class);
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080051 boolean isShutdown = true;
Jonathan Hartab63aac2014-10-16 08:52:55 -070052 private Channel serverChannel; // Listener for incoming BGP connections
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080053 private ServerBootstrap serverBootstrap;
54 private ChannelGroup allChannels = new DefaultChannelGroup();
Jonathan Hartab63aac2014-10-16 08:52:55 -070055 private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
56 new ConcurrentHashMap<>();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080057 private Ip4Address myBgpId; // Same BGP ID for all peers
Jonathan Hartab63aac2014-10-16 08:52:55 -070058
59 private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080060 private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRoutes =
Jonathan Hartab63aac2014-10-16 08:52:55 -070061 new ConcurrentHashMap<>();
62
63 private final RouteListener routeListener;
64
65 /**
66 * Constructor for given route listener.
67 *
68 * @param routeListener the route listener to use
69 */
70 public BgpSessionManager(RouteListener routeListener) {
71 this.routeListener = checkNotNull(routeListener);
72 }
73
74 /**
75 * Gets the BGP sessions.
76 *
77 * @return the BGP sessions
78 */
79 public Collection<BgpSession> getBgpSessions() {
80 return bgpSessions.values();
81 }
82
83 /**
84 * Gets the BGP routes.
85 *
86 * @return the BGP routes
87 */
88 public Collection<BgpRouteEntry> getBgpRoutes() {
89 return bgpRoutes.values();
90 }
91
92 /**
Pavlin Radoslavov7e190942014-11-13 18:50:59 -080093 * Adds the channel for a BGP session.
94 *
95 * @param channel the channel to add
96 */
97 void addSessionChannel(Channel channel) {
98 allChannels.add(channel);
99 }
100
101 /**
102 * Removes the channel for a BGP session.
103 *
104 * @param channel the channel to remove
105 */
106 void removeSessionChannel(Channel channel) {
107 allChannels.remove(channel);
108 }
109
110 /**
Jonathan Hartab63aac2014-10-16 08:52:55 -0700111 * Processes the connection from a BGP peer.
112 *
113 * @param bgpSession the BGP session for the peer
114 * @return true if the connection can be established, otherwise false
115 */
116 boolean peerConnected(BgpSession bgpSession) {
117
118 // Test whether there is already a session from the same remote
119 if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
120 return false; // Duplicate BGP session
121 }
122 bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
123
124 //
125 // If the first connection, set my BGP ID to the local address
126 // of the socket.
127 //
128 if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
129 InetAddress inetAddr =
130 ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800131 Ip4Address ip4Address = Ip4Address.valueOf(inetAddr.getAddress());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700132 updateMyBgpId(ip4Address);
133 }
134 return true;
135 }
136
137 /**
138 * Processes the disconnection from a BGP peer.
139 *
140 * @param bgpSession the BGP session for the peer
141 */
142 void peerDisconnected(BgpSession bgpSession) {
143 bgpSessions.remove(bgpSession.getRemoteAddress());
144 }
145
146 /**
147 * Conditionally updates the local BGP ID if it wasn't set already.
148 * <p/>
149 * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
150 *
151 * @param ip4Address the IPv4 address to use as BGP ID
152 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800153 private synchronized void updateMyBgpId(Ip4Address ip4Address) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700154 if (myBgpId == null) {
155 myBgpId = ip4Address;
156 log.debug("BGP: My BGP ID is {}", myBgpId);
157 }
158 }
159
160 /**
161 * Gets the local BGP Identifier as an IPv4 address.
162 *
163 * @return the local BGP Identifier as an IPv4 address
164 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800165 Ip4Address getMyBgpId() {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700166 return myBgpId;
167 }
168
169 /**
170 * Gets the BGP Route Selector.
171 *
172 * @return the BGP Route Selector
173 */
174 BgpRouteSelector getBgpRouteSelector() {
175 return bgpRouteSelector;
176 }
177
178 /**
179 * Starts up BGP Session Manager operation.
180 *
181 * @param listenPortNumber the port number to listen on. By default
182 * it should be BgpConstants.BGP_PORT (179)
183 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800184 public void start(int listenPortNumber) {
185 log.debug("BGP Session Manager start.");
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800186 isShutdown = false;
Jonathan Hartab63aac2014-10-16 08:52:55 -0700187
188 ChannelFactory channelFactory =
189 new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
190 Executors.newCachedThreadPool());
191 ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
192 @Override
193 public ChannelPipeline getPipeline() throws Exception {
194 // Allocate a new session per connection
195 BgpSession bgpSessionHandler =
196 new BgpSession(BgpSessionManager.this);
197 BgpFrameDecoder bgpFrameDecoder =
198 new BgpFrameDecoder(bgpSessionHandler);
199
200 // Setup the processing pipeline
201 ChannelPipeline pipeline = Channels.pipeline();
202 pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
203 pipeline.addLast("BgpSession", bgpSessionHandler);
204 return pipeline;
205 }
206 };
207 InetSocketAddress listenAddress =
208 new InetSocketAddress(listenPortNumber);
209
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800210 serverBootstrap = new ServerBootstrap(channelFactory);
Jonathan Hartab63aac2014-10-16 08:52:55 -0700211 // serverBootstrap.setOptions("reuseAddr", true);
212 serverBootstrap.setOption("child.keepAlive", true);
213 serverBootstrap.setOption("child.tcpNoDelay", true);
214 serverBootstrap.setPipelineFactory(pipelineFactory);
215 try {
216 serverChannel = serverBootstrap.bind(listenAddress);
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800217 allChannels.add(serverChannel);
Jonathan Hartab63aac2014-10-16 08:52:55 -0700218 } catch (ChannelException e) {
219 log.debug("Exception binding to BGP port {}: ",
220 listenAddress.getPort(), e);
221 }
222 }
223
224 /**
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800225 * Stops the BGP Session Manager operation.
Jonathan Hartab63aac2014-10-16 08:52:55 -0700226 */
Pavlin Radoslavova071b1e2014-11-17 13:37:57 -0800227 public void stop() {
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800228 isShutdown = true;
229 allChannels.close().awaitUninterruptibly();
230 serverBootstrap.releaseExternalResources();
Jonathan Hartab63aac2014-10-16 08:52:55 -0700231 }
232
233 /**
234 * Class to receive and process the BGP routes from each BGP Session/Peer.
235 */
236 class BgpRouteSelector {
237 /**
238 * Processes route entry updates: added/updated and deleted route
239 * entries.
240 *
241 * @param bgpSession the BGP session the route entry updates were
242 * received on
243 * @param addedBgpRouteEntries the added/updated route entries to
244 * process
245 * @param deletedBgpRouteEntries the deleted route entries to process
246 */
247 synchronized void routeUpdates(BgpSession bgpSession,
248 Collection<BgpRouteEntry> addedBgpRouteEntries,
249 Collection<BgpRouteEntry> deletedBgpRouteEntries) {
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800250 if (isShutdown) {
251 return; // Ignore any leftover updates if shutdown
252 }
Jonathan Hartab63aac2014-10-16 08:52:55 -0700253 // Process the deleted route entries
254 for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
255 processDeletedRoute(bgpSession, bgpRouteEntry);
256 }
257
258 // Process the added/updated route entries
259 for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
260 processAddedRoute(bgpSession, bgpRouteEntry);
261 }
262 }
263
264 /**
265 * Processes an added/updated route entry.
266 *
267 * @param bgpSession the BGP session the route entry update was
268 * received on
269 * @param bgpRouteEntry the added/updated route entry
270 */
271 private void processAddedRoute(BgpSession bgpSession,
272 BgpRouteEntry bgpRouteEntry) {
273 RouteUpdate routeUpdate;
274 BgpRouteEntry bestBgpRouteEntry =
275 bgpRoutes.get(bgpRouteEntry.prefix());
276
277 //
278 // Install the new route entry if it is better than the
279 // current best route.
280 //
281 if ((bestBgpRouteEntry == null) ||
282 bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
283 bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
284 routeUpdate =
285 new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
286 // Forward the result route updates to the Route Listener
287 routeListener.update(routeUpdate);
288 return;
289 }
290
291 //
292 // If the route entry arrived on the same BGP Session as
293 // the current best route, then elect the next best route
294 // and install it.
295 //
296 if (bestBgpRouteEntry.getBgpSession() !=
297 bgpRouteEntry.getBgpSession()) {
298 return;
299 }
300
301 // Find the next best route
302 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
303 if (bestBgpRouteEntry == null) {
304 //
305 // TODO: Shouldn't happen. Install the new route as a
306 // pre-caution.
307 //
308 log.debug("BGP next best route for prefix {} is missing. " +
309 "Adding the route that is currently processed.",
310 bgpRouteEntry.prefix());
311 bestBgpRouteEntry = bgpRouteEntry;
312 }
313 // Install the next best route
314 bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
315 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
316 bestBgpRouteEntry);
317 // Forward the result route updates to the Route Listener
318 routeListener.update(routeUpdate);
319 }
320
321 /**
322 * Processes a deleted route entry.
323 *
324 * @param bgpSession the BGP session the route entry update was
325 * received on
326 * @param bgpRouteEntry the deleted route entry
327 */
328 private void processDeletedRoute(BgpSession bgpSession,
329 BgpRouteEntry bgpRouteEntry) {
330 RouteUpdate routeUpdate;
331 BgpRouteEntry bestBgpRouteEntry =
332 bgpRoutes.get(bgpRouteEntry.prefix());
333
334 //
335 // Remove the route entry only if it was the best one.
336 // Install the the next best route if it exists.
337 //
338 // NOTE: We intentionally use "==" instead of method equals(),
339 // because we need to check whether this is same object.
340 //
341 if (bgpRouteEntry != bestBgpRouteEntry) {
342 return; // Nothing to do
343 }
344
345 //
346 // Find the next best route
347 //
348 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
349 if (bestBgpRouteEntry != null) {
350 // Install the next best route
351 bgpRoutes.put(bestBgpRouteEntry.prefix(),
352 bestBgpRouteEntry);
353 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
354 bestBgpRouteEntry);
355 // Forward the result route updates to the Route Listener
356 routeListener.update(routeUpdate);
357 return;
358 }
359
360 //
361 // No route found. Remove the route entry
362 //
363 bgpRoutes.remove(bgpRouteEntry.prefix());
364 routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
365 bgpRouteEntry);
366 // Forward the result route updates to the Route Listener
367 routeListener.update(routeUpdate);
368 }
369
370 /**
371 * Finds the best route entry among all BGP Sessions.
372 *
373 * @param prefix the prefix of the route
374 * @return the best route if found, otherwise null
375 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800376 private BgpRouteEntry findBestBgpRoute(Ip4Prefix prefix) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700377 BgpRouteEntry bestRoute = null;
378
379 // Iterate across all BGP Sessions and select the best route
380 for (BgpSession bgpSession : bgpSessions.values()) {
381 BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
382 if (route == null) {
383 continue;
384 }
385 if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
386 bestRoute = route;
387 }
388 }
389 return bestRoute;
390 }
391 }
392}