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