blob: b483f14fa596d6285e9204f9788cc8b0fee88622 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
Jonathan Hartab63aac2014-10-16 08:52:55 -070019package org.onlab.onos.sdnip.bgp;
20
21import static com.google.common.base.Preconditions.checkNotNull;
22
23import java.net.InetAddress;
24import java.net.InetSocketAddress;
25import java.net.SocketAddress;
26import java.util.Collection;
27import java.util.concurrent.ConcurrentHashMap;
28import java.util.concurrent.ConcurrentMap;
29import java.util.concurrent.Executors;
30
31import org.jboss.netty.bootstrap.ServerBootstrap;
32import org.jboss.netty.channel.Channel;
33import org.jboss.netty.channel.ChannelException;
34import org.jboss.netty.channel.ChannelFactory;
35import org.jboss.netty.channel.ChannelPipeline;
36import org.jboss.netty.channel.ChannelPipelineFactory;
37import org.jboss.netty.channel.Channels;
38import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
39import org.onlab.onos.sdnip.RouteListener;
40import org.onlab.onos.sdnip.RouteUpdate;
41import org.onlab.packet.IpAddress;
42import org.onlab.packet.IpPrefix;
43import 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);
52 private Channel serverChannel; // Listener for incoming BGP connections
53 private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
54 new ConcurrentHashMap<>();
55 private IpAddress myBgpId; // Same BGP ID for all peers
56
57 private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
58 private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
59 new ConcurrentHashMap<>();
60
61 private final RouteListener routeListener;
62
63 /**
64 * Constructor for given route listener.
65 *
66 * @param routeListener the route listener to use
67 */
68 public BgpSessionManager(RouteListener routeListener) {
69 this.routeListener = checkNotNull(routeListener);
70 }
71
72 /**
73 * Gets the BGP sessions.
74 *
75 * @return the BGP sessions
76 */
77 public Collection<BgpSession> getBgpSessions() {
78 return bgpSessions.values();
79 }
80
81 /**
82 * Gets the BGP routes.
83 *
84 * @return the BGP routes
85 */
86 public Collection<BgpRouteEntry> getBgpRoutes() {
87 return bgpRoutes.values();
88 }
89
90 /**
91 * Processes the connection from a BGP peer.
92 *
93 * @param bgpSession the BGP session for the peer
94 * @return true if the connection can be established, otherwise false
95 */
96 boolean peerConnected(BgpSession bgpSession) {
97
98 // Test whether there is already a session from the same remote
99 if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
100 return false; // Duplicate BGP session
101 }
102 bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
103
104 //
105 // If the first connection, set my BGP ID to the local address
106 // of the socket.
107 //
108 if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
109 InetAddress inetAddr =
110 ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
111 IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
112 updateMyBgpId(ip4Address);
113 }
114 return true;
115 }
116
117 /**
118 * Processes the disconnection from a BGP peer.
119 *
120 * @param bgpSession the BGP session for the peer
121 */
122 void peerDisconnected(BgpSession bgpSession) {
123 bgpSessions.remove(bgpSession.getRemoteAddress());
124 }
125
126 /**
127 * Conditionally updates the local BGP ID if it wasn't set already.
128 * <p/>
129 * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
130 *
131 * @param ip4Address the IPv4 address to use as BGP ID
132 */
133 private synchronized void updateMyBgpId(IpAddress ip4Address) {
134 if (myBgpId == null) {
135 myBgpId = ip4Address;
136 log.debug("BGP: My BGP ID is {}", myBgpId);
137 }
138 }
139
140 /**
141 * Gets the local BGP Identifier as an IPv4 address.
142 *
143 * @return the local BGP Identifier as an IPv4 address
144 */
145 IpAddress getMyBgpId() {
146 return myBgpId;
147 }
148
149 /**
150 * Gets the BGP Route Selector.
151 *
152 * @return the BGP Route Selector
153 */
154 BgpRouteSelector getBgpRouteSelector() {
155 return bgpRouteSelector;
156 }
157
158 /**
159 * Starts up BGP Session Manager operation.
160 *
161 * @param listenPortNumber the port number to listen on. By default
162 * it should be BgpConstants.BGP_PORT (179)
163 */
164 public void startUp(int listenPortNumber) {
165 log.debug("BGP Session Manager startUp()");
166
167 ChannelFactory channelFactory =
168 new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
169 Executors.newCachedThreadPool());
170 ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
171 @Override
172 public ChannelPipeline getPipeline() throws Exception {
173 // Allocate a new session per connection
174 BgpSession bgpSessionHandler =
175 new BgpSession(BgpSessionManager.this);
176 BgpFrameDecoder bgpFrameDecoder =
177 new BgpFrameDecoder(bgpSessionHandler);
178
179 // Setup the processing pipeline
180 ChannelPipeline pipeline = Channels.pipeline();
181 pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
182 pipeline.addLast("BgpSession", bgpSessionHandler);
183 return pipeline;
184 }
185 };
186 InetSocketAddress listenAddress =
187 new InetSocketAddress(listenPortNumber);
188
189 ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
190 // serverBootstrap.setOptions("reuseAddr", true);
191 serverBootstrap.setOption("child.keepAlive", true);
192 serverBootstrap.setOption("child.tcpNoDelay", true);
193 serverBootstrap.setPipelineFactory(pipelineFactory);
194 try {
195 serverChannel = serverBootstrap.bind(listenAddress);
196 } catch (ChannelException e) {
197 log.debug("Exception binding to BGP port {}: ",
198 listenAddress.getPort(), e);
199 }
200 }
201
202 /**
203 * Shuts down the BGP Session Manager operation.
204 */
205 public void shutDown() {
206 // TODO: Complete the implementation: remove routes, etc.
207 if (serverChannel != null) {
208 serverChannel.close();
209 }
210 }
211
212 /**
213 * Class to receive and process the BGP routes from each BGP Session/Peer.
214 */
215 class BgpRouteSelector {
216 /**
217 * Processes route entry updates: added/updated and deleted route
218 * entries.
219 *
220 * @param bgpSession the BGP session the route entry updates were
221 * received on
222 * @param addedBgpRouteEntries the added/updated route entries to
223 * process
224 * @param deletedBgpRouteEntries the deleted route entries to process
225 */
226 synchronized void routeUpdates(BgpSession bgpSession,
227 Collection<BgpRouteEntry> addedBgpRouteEntries,
228 Collection<BgpRouteEntry> deletedBgpRouteEntries) {
229 //
230 // TODO: Merge the updates from different BGP Peers,
231 // by choosing the best route.
232 //
233
234 // Process the deleted route entries
235 for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
236 processDeletedRoute(bgpSession, bgpRouteEntry);
237 }
238
239 // Process the added/updated route entries
240 for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
241 processAddedRoute(bgpSession, bgpRouteEntry);
242 }
243 }
244
245 /**
246 * Processes an added/updated route entry.
247 *
248 * @param bgpSession the BGP session the route entry update was
249 * received on
250 * @param bgpRouteEntry the added/updated route entry
251 */
252 private void processAddedRoute(BgpSession bgpSession,
253 BgpRouteEntry bgpRouteEntry) {
254 RouteUpdate routeUpdate;
255 BgpRouteEntry bestBgpRouteEntry =
256 bgpRoutes.get(bgpRouteEntry.prefix());
257
258 //
259 // Install the new route entry if it is better than the
260 // current best route.
261 //
262 if ((bestBgpRouteEntry == null) ||
263 bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
264 bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
265 routeUpdate =
266 new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
267 // Forward the result route updates to the Route Listener
268 routeListener.update(routeUpdate);
269 return;
270 }
271
272 //
273 // If the route entry arrived on the same BGP Session as
274 // the current best route, then elect the next best route
275 // and install it.
276 //
277 if (bestBgpRouteEntry.getBgpSession() !=
278 bgpRouteEntry.getBgpSession()) {
279 return;
280 }
281
282 // Find the next best route
283 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
284 if (bestBgpRouteEntry == null) {
285 //
286 // TODO: Shouldn't happen. Install the new route as a
287 // pre-caution.
288 //
289 log.debug("BGP next best route for prefix {} is missing. " +
290 "Adding the route that is currently processed.",
291 bgpRouteEntry.prefix());
292 bestBgpRouteEntry = bgpRouteEntry;
293 }
294 // Install the next best route
295 bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
296 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
297 bestBgpRouteEntry);
298 // Forward the result route updates to the Route Listener
299 routeListener.update(routeUpdate);
300 }
301
302 /**
303 * Processes a deleted route entry.
304 *
305 * @param bgpSession the BGP session the route entry update was
306 * received on
307 * @param bgpRouteEntry the deleted route entry
308 */
309 private void processDeletedRoute(BgpSession bgpSession,
310 BgpRouteEntry bgpRouteEntry) {
311 RouteUpdate routeUpdate;
312 BgpRouteEntry bestBgpRouteEntry =
313 bgpRoutes.get(bgpRouteEntry.prefix());
314
315 //
316 // Remove the route entry only if it was the best one.
317 // Install the the next best route if it exists.
318 //
319 // NOTE: We intentionally use "==" instead of method equals(),
320 // because we need to check whether this is same object.
321 //
322 if (bgpRouteEntry != bestBgpRouteEntry) {
323 return; // Nothing to do
324 }
325
326 //
327 // Find the next best route
328 //
329 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
330 if (bestBgpRouteEntry != null) {
331 // Install the next best route
332 bgpRoutes.put(bestBgpRouteEntry.prefix(),
333 bestBgpRouteEntry);
334 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
335 bestBgpRouteEntry);
336 // Forward the result route updates to the Route Listener
337 routeListener.update(routeUpdate);
338 return;
339 }
340
341 //
342 // No route found. Remove the route entry
343 //
344 bgpRoutes.remove(bgpRouteEntry.prefix());
345 routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
346 bgpRouteEntry);
347 // Forward the result route updates to the Route Listener
348 routeListener.update(routeUpdate);
349 }
350
351 /**
352 * Finds the best route entry among all BGP Sessions.
353 *
354 * @param prefix the prefix of the route
355 * @return the best route if found, otherwise null
356 */
357 private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
358 BgpRouteEntry bestRoute = null;
359
360 // Iterate across all BGP Sessions and select the best route
361 for (BgpSession bgpSession : bgpSessions.values()) {
362 BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
363 if (route == null) {
364 continue;
365 }
366 if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
367 bestRoute = route;
368 }
369 }
370 return bestRoute;
371 }
372 }
373}