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