blob: 247c95fe2bdb96620add4b9735c4e09482d52d7a [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();
Pavlin Radoslavovd0e32d72014-10-31 18:11:43 -0700108 IpAddress ip4Address = IpAddress.valueOf(IpAddress.Version.INET,
109 inetAddr.getAddress());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700110 updateMyBgpId(ip4Address);
111 }
112 return true;
113 }
114
115 /**
116 * Processes the disconnection from a BGP peer.
117 *
118 * @param bgpSession the BGP session for the peer
119 */
120 void peerDisconnected(BgpSession bgpSession) {
121 bgpSessions.remove(bgpSession.getRemoteAddress());
122 }
123
124 /**
125 * Conditionally updates the local BGP ID if it wasn't set already.
126 * <p/>
127 * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
128 *
129 * @param ip4Address the IPv4 address to use as BGP ID
130 */
131 private synchronized void updateMyBgpId(IpAddress ip4Address) {
132 if (myBgpId == null) {
133 myBgpId = ip4Address;
134 log.debug("BGP: My BGP ID is {}", myBgpId);
135 }
136 }
137
138 /**
139 * Gets the local BGP Identifier as an IPv4 address.
140 *
141 * @return the local BGP Identifier as an IPv4 address
142 */
143 IpAddress getMyBgpId() {
144 return myBgpId;
145 }
146
147 /**
148 * Gets the BGP Route Selector.
149 *
150 * @return the BGP Route Selector
151 */
152 BgpRouteSelector getBgpRouteSelector() {
153 return bgpRouteSelector;
154 }
155
156 /**
157 * Starts up BGP Session Manager operation.
158 *
159 * @param listenPortNumber the port number to listen on. By default
160 * it should be BgpConstants.BGP_PORT (179)
161 */
162 public void startUp(int listenPortNumber) {
163 log.debug("BGP Session Manager startUp()");
164
165 ChannelFactory channelFactory =
166 new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
167 Executors.newCachedThreadPool());
168 ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
169 @Override
170 public ChannelPipeline getPipeline() throws Exception {
171 // Allocate a new session per connection
172 BgpSession bgpSessionHandler =
173 new BgpSession(BgpSessionManager.this);
174 BgpFrameDecoder bgpFrameDecoder =
175 new BgpFrameDecoder(bgpSessionHandler);
176
177 // Setup the processing pipeline
178 ChannelPipeline pipeline = Channels.pipeline();
179 pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
180 pipeline.addLast("BgpSession", bgpSessionHandler);
181 return pipeline;
182 }
183 };
184 InetSocketAddress listenAddress =
185 new InetSocketAddress(listenPortNumber);
186
187 ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
188 // serverBootstrap.setOptions("reuseAddr", true);
189 serverBootstrap.setOption("child.keepAlive", true);
190 serverBootstrap.setOption("child.tcpNoDelay", true);
191 serverBootstrap.setPipelineFactory(pipelineFactory);
192 try {
193 serverChannel = serverBootstrap.bind(listenAddress);
194 } catch (ChannelException e) {
195 log.debug("Exception binding to BGP port {}: ",
196 listenAddress.getPort(), e);
197 }
198 }
199
200 /**
201 * Shuts down the BGP Session Manager operation.
202 */
203 public void shutDown() {
204 // TODO: Complete the implementation: remove routes, etc.
205 if (serverChannel != null) {
206 serverChannel.close();
207 }
208 }
209
210 /**
211 * Class to receive and process the BGP routes from each BGP Session/Peer.
212 */
213 class BgpRouteSelector {
214 /**
215 * Processes route entry updates: added/updated and deleted route
216 * entries.
217 *
218 * @param bgpSession the BGP session the route entry updates were
219 * received on
220 * @param addedBgpRouteEntries the added/updated route entries to
221 * process
222 * @param deletedBgpRouteEntries the deleted route entries to process
223 */
224 synchronized void routeUpdates(BgpSession bgpSession,
225 Collection<BgpRouteEntry> addedBgpRouteEntries,
226 Collection<BgpRouteEntry> deletedBgpRouteEntries) {
227 //
228 // TODO: Merge the updates from different BGP Peers,
229 // by choosing the best route.
230 //
231
232 // Process the deleted route entries
233 for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
234 processDeletedRoute(bgpSession, bgpRouteEntry);
235 }
236
237 // Process the added/updated route entries
238 for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
239 processAddedRoute(bgpSession, bgpRouteEntry);
240 }
241 }
242
243 /**
244 * Processes an added/updated route entry.
245 *
246 * @param bgpSession the BGP session the route entry update was
247 * received on
248 * @param bgpRouteEntry the added/updated route entry
249 */
250 private void processAddedRoute(BgpSession bgpSession,
251 BgpRouteEntry bgpRouteEntry) {
252 RouteUpdate routeUpdate;
253 BgpRouteEntry bestBgpRouteEntry =
254 bgpRoutes.get(bgpRouteEntry.prefix());
255
256 //
257 // Install the new route entry if it is better than the
258 // current best route.
259 //
260 if ((bestBgpRouteEntry == null) ||
261 bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
262 bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
263 routeUpdate =
264 new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
265 // Forward the result route updates to the Route Listener
266 routeListener.update(routeUpdate);
267 return;
268 }
269
270 //
271 // If the route entry arrived on the same BGP Session as
272 // the current best route, then elect the next best route
273 // and install it.
274 //
275 if (bestBgpRouteEntry.getBgpSession() !=
276 bgpRouteEntry.getBgpSession()) {
277 return;
278 }
279
280 // Find the next best route
281 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
282 if (bestBgpRouteEntry == null) {
283 //
284 // TODO: Shouldn't happen. Install the new route as a
285 // pre-caution.
286 //
287 log.debug("BGP next best route for prefix {} is missing. " +
288 "Adding the route that is currently processed.",
289 bgpRouteEntry.prefix());
290 bestBgpRouteEntry = bgpRouteEntry;
291 }
292 // Install the next best route
293 bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
294 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
295 bestBgpRouteEntry);
296 // Forward the result route updates to the Route Listener
297 routeListener.update(routeUpdate);
298 }
299
300 /**
301 * Processes a deleted route entry.
302 *
303 * @param bgpSession the BGP session the route entry update was
304 * received on
305 * @param bgpRouteEntry the deleted route entry
306 */
307 private void processDeletedRoute(BgpSession bgpSession,
308 BgpRouteEntry bgpRouteEntry) {
309 RouteUpdate routeUpdate;
310 BgpRouteEntry bestBgpRouteEntry =
311 bgpRoutes.get(bgpRouteEntry.prefix());
312
313 //
314 // Remove the route entry only if it was the best one.
315 // Install the the next best route if it exists.
316 //
317 // NOTE: We intentionally use "==" instead of method equals(),
318 // because we need to check whether this is same object.
319 //
320 if (bgpRouteEntry != bestBgpRouteEntry) {
321 return; // Nothing to do
322 }
323
324 //
325 // Find the next best route
326 //
327 bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
328 if (bestBgpRouteEntry != null) {
329 // Install the next best route
330 bgpRoutes.put(bestBgpRouteEntry.prefix(),
331 bestBgpRouteEntry);
332 routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
333 bestBgpRouteEntry);
334 // Forward the result route updates to the Route Listener
335 routeListener.update(routeUpdate);
336 return;
337 }
338
339 //
340 // No route found. Remove the route entry
341 //
342 bgpRoutes.remove(bgpRouteEntry.prefix());
343 routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
344 bgpRouteEntry);
345 // Forward the result route updates to the Route Listener
346 routeListener.update(routeUpdate);
347 }
348
349 /**
350 * Finds the best route entry among all BGP Sessions.
351 *
352 * @param prefix the prefix of the route
353 * @return the best route if found, otherwise null
354 */
355 private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
356 BgpRouteEntry bestRoute = null;
357
358 // Iterate across all BGP Sessions and select the best route
359 for (BgpSession bgpSession : bgpSessions.values()) {
360 BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
361 if (route == null) {
362 continue;
363 }
364 if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
365 bestRoute = route;
366 }
367 }
368 return bestRoute;
369 }
370 }
371}