blob: 38fad6c77f1e0e23a29f6b6656fbe82e2787b27b [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;
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080038import org.onlab.packet.Ip4Address;
39import org.onlab.packet.Ip4Prefix;
Jonathan Hartab63aac2014-10-16 08:52:55 -070040import 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<>();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080052 private Ip4Address myBgpId; // Same BGP ID for all peers
Jonathan Hartab63aac2014-10-16 08:52:55 -070053
54 private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080055 private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRoutes =
Jonathan Hartab63aac2014-10-16 08:52:55 -070056 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();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800108 Ip4Address ip4Address = Ip4Address.valueOf(inetAddr.getAddress());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700109 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 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800130 private synchronized void updateMyBgpId(Ip4Address ip4Address) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700131 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 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800142 Ip4Address getMyBgpId() {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700143 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) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700226 // Process the deleted route entries
227 for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
228 processDeletedRoute(bgpSession, bgpRouteEntry);
229 }
230
231 // Process the added/updated route entries
232 for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
233 processAddedRoute(bgpSession, bgpRouteEntry);
234 }
235 }
236
237 /**
238 * Processes an added/updated route entry.
239 *
240 * @param bgpSession the BGP session the route entry update was
241 * received on
242 * @param bgpRouteEntry the added/updated route entry
243 */
244 private void processAddedRoute(BgpSession bgpSession,
245 BgpRouteEntry bgpRouteEntry) {
246 RouteUpdate routeUpdate;
247 BgpRouteEntry bestBgpRouteEntry =
248 bgpRoutes.get(bgpRouteEntry.prefix());
249
250 //
251 // Install the new route entry if it is better than the
252 // current best route.
253 //
254 if ((bestBgpRouteEntry == null) ||
255 bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
256 bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
257 routeUpdate =
258 new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
259 // Forward the result route updates to the Route Listener
260 routeListener.update(routeUpdate);
261 return;
262 }
263
264 //
265 // If the route entry arrived on the same BGP Session as
266 // the current best route, then elect the next best route
267 // and install it.
268 //
269 if (bestBgpRouteEntry.getBgpSession() !=
270 bgpRouteEntry.getBgpSession()) {
271 return;
272 }
273
274 // Find the next best route
275 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
276 if (bestBgpRouteEntry == null) {
277 //
278 // TODO: Shouldn't happen. Install the new route as a
279 // pre-caution.
280 //
281 log.debug("BGP next best route for prefix {} is missing. " +
282 "Adding the route that is currently processed.",
283 bgpRouteEntry.prefix());
284 bestBgpRouteEntry = bgpRouteEntry;
285 }
286 // Install the next best route
287 bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
288 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
289 bestBgpRouteEntry);
290 // Forward the result route updates to the Route Listener
291 routeListener.update(routeUpdate);
292 }
293
294 /**
295 * Processes a deleted route entry.
296 *
297 * @param bgpSession the BGP session the route entry update was
298 * received on
299 * @param bgpRouteEntry the deleted route entry
300 */
301 private void processDeletedRoute(BgpSession bgpSession,
302 BgpRouteEntry bgpRouteEntry) {
303 RouteUpdate routeUpdate;
304 BgpRouteEntry bestBgpRouteEntry =
305 bgpRoutes.get(bgpRouteEntry.prefix());
306
307 //
308 // Remove the route entry only if it was the best one.
309 // Install the the next best route if it exists.
310 //
311 // NOTE: We intentionally use "==" instead of method equals(),
312 // because we need to check whether this is same object.
313 //
314 if (bgpRouteEntry != bestBgpRouteEntry) {
315 return; // Nothing to do
316 }
317
318 //
319 // Find the next best route
320 //
321 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
322 if (bestBgpRouteEntry != null) {
323 // Install the next best route
324 bgpRoutes.put(bestBgpRouteEntry.prefix(),
325 bestBgpRouteEntry);
326 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
327 bestBgpRouteEntry);
328 // Forward the result route updates to the Route Listener
329 routeListener.update(routeUpdate);
330 return;
331 }
332
333 //
334 // No route found. Remove the route entry
335 //
336 bgpRoutes.remove(bgpRouteEntry.prefix());
337 routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
338 bgpRouteEntry);
339 // Forward the result route updates to the Route Listener
340 routeListener.update(routeUpdate);
341 }
342
343 /**
344 * Finds the best route entry among all BGP Sessions.
345 *
346 * @param prefix the prefix of the route
347 * @return the best route if found, otherwise null
348 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800349 private BgpRouteEntry findBestBgpRoute(Ip4Prefix prefix) {
Jonathan Hartab63aac2014-10-16 08:52:55 -0700350 BgpRouteEntry bestRoute = null;
351
352 // Iterate across all BGP Sessions and select the best route
353 for (BgpSession bgpSession : bgpSessions.values()) {
354 BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
355 if (route == null) {
356 continue;
357 }
358 if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
359 bestRoute = route;
360 }
361 }
362 return bestRoute;
363 }
364 }
365}