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