blob: bf0908dc8c22cf4d75a8fd4eb2084cdf660e3f89 [file] [log] [blame]
Jonathan Hart1ad75f22016-04-13 21:24:13 -07001/*
2 * Copyright 2014-2015 Open Networking Laboratory
3 *
4 * 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
7 *
8 * 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.
15 */
16package org.onosproject.routing.impl;
17
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimaps;
20import com.google.common.collect.SetMultimap;
21import com.google.common.util.concurrent.ThreadFactoryBuilder;
22import com.googlecode.concurrenttrees.common.KeyValuePair;
23import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
24import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
25import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
26import org.apache.felix.scr.annotations.Activate;
27import org.apache.felix.scr.annotations.Component;
28import org.apache.felix.scr.annotations.Deactivate;
29import org.apache.felix.scr.annotations.Reference;
30import org.apache.felix.scr.annotations.ReferenceCardinality;
31import org.apache.felix.scr.annotations.Service;
32import org.onlab.packet.Ip4Address;
33import org.onlab.packet.Ip6Address;
34import org.onlab.packet.IpAddress;
35import org.onlab.packet.IpPrefix;
36import org.onlab.packet.MacAddress;
37import org.onosproject.core.CoreService;
38import org.onosproject.net.Host;
39import org.onosproject.net.host.HostEvent;
40import org.onosproject.net.host.HostListener;
41import org.onosproject.net.host.HostService;
42import org.onosproject.routing.RouteSourceService;
43import org.onosproject.routing.FibEntry;
44import org.onosproject.routing.FibListener;
45import org.onosproject.routing.FibUpdate;
46import org.onosproject.routing.RouteEntry;
47import org.onosproject.routing.RouteListener;
48import org.onosproject.routing.RouteUpdate;
49import org.onosproject.routing.RoutingService;
50import org.onosproject.routing.config.RoutingConfigurationService;
51import org.slf4j.Logger;
52import org.slf4j.LoggerFactory;
53
54import java.util.Collection;
55import java.util.Collections;
56import java.util.Iterator;
57import java.util.LinkedList;
58import java.util.List;
59import java.util.Map;
60import java.util.Set;
61import java.util.concurrent.BlockingQueue;
62import java.util.concurrent.ConcurrentHashMap;
63import java.util.concurrent.ExecutorService;
64import java.util.concurrent.Executors;
65import java.util.concurrent.LinkedBlockingQueue;
66
67import static com.google.common.base.Preconditions.checkNotNull;
68import static org.onosproject.routing.RouteEntry.createBinaryString;
69
70/**
71 * This class processes route updates and maintains a Routing Information Base
72 * (RIB). After route updates have been processed and next hops have been
73 * resolved, FIB updates are sent to any listening FIB components.
74 * <p>
75 * This implementation has been superseded by the RouteService and will be
76 * removed soon.
77 * </p>
78 */
79@Deprecated
80@Component(immediate = true, enabled = false)
81@Service
82public class DefaultRouter implements RoutingService {
83
84 private static final Logger log = LoggerFactory.getLogger(DefaultRouter.class);
85
86 // Route entries are stored in a radix tree.
87 // The key in this tree is the binary string of prefix of the route.
88 private InvertedRadixTree<RouteEntry> ribTable4;
89 private InvertedRadixTree<RouteEntry> ribTable6;
90
91 // Stores all incoming route updates in a queue.
92 private final BlockingQueue<Collection<RouteUpdate>> routeUpdatesQueue =
93 new LinkedBlockingQueue<>();
94
95 // Next-hop IP address to route entry mapping for next hops pending MAC
96 // resolution
97 private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
98
99 // The IPv4 address to MAC address mapping
100 private final Map<IpAddress, MacAddress> ip2Mac = new ConcurrentHashMap<>();
101
102 private FibListener fibComponent;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected CoreService coreService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected HostService hostService;
109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected RouteSourceService routeSourceService;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected RoutingConfigurationService routingConfigurationService;
115
116 private ExecutorService bgpUpdatesExecutor;
117 private final HostListener hostListener = new InternalHostListener();
118
119 @Activate
120 public void activate() {
121 ribTable4 = new ConcurrentInvertedRadixTree<>(
122 new DefaultByteArrayNodeFactory());
123 ribTable6 = new ConcurrentInvertedRadixTree<>(
124 new DefaultByteArrayNodeFactory());
125
126 routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
127 HashMultimap.create());
128
129 coreService.registerApplication(ROUTER_APP_ID);
130
131 bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
132 new ThreadFactoryBuilder()
133 .setNameFormat("rib-updates-%d").build());
134 }
135
136 @Deactivate
137 public void deactivate() {
138 log.debug("Stopped");
139 }
140
141 @Override
142 public void addFibListener(FibListener fibListener) {
143 this.fibComponent = checkNotNull(fibListener);
144 }
145
146 @Override
147 public void start() {
148 this.hostService.addListener(hostListener);
149
150 routeSourceService.start(new InternalRouteListener());
151
152 bgpUpdatesExecutor.execute(this::doUpdatesThread);
153 }
154
155 @Override
156 public void stop() {
157 routeSourceService.stop();
158
159 this.hostService.removeListener(hostListener);
160
161 // Stop the thread(s)
162 bgpUpdatesExecutor.shutdownNow();
163
164 synchronized (this) {
165 // Cleanup all local state
166 ribTable4 = new ConcurrentInvertedRadixTree<>(
167 new DefaultByteArrayNodeFactory());
168 ribTable6 = new ConcurrentInvertedRadixTree<>(
169 new DefaultByteArrayNodeFactory());
170 routeUpdatesQueue.clear();
171 routesWaitingOnArp.clear();
172 ip2Mac.clear();
173 }
174 }
175
176 /**
177 * Entry point for route updates.
178 *
179 * @param routeUpdates collection of route updates to process
180 */
181 private void update(Collection<RouteUpdate> routeUpdates) {
182 try {
183 routeUpdatesQueue.put(routeUpdates);
184 } catch (InterruptedException e) {
185 log.error("Interrupted while putting on routeUpdatesQueue", e);
186 Thread.currentThread().interrupt();
187 }
188 }
189
190 /**
191 * Thread for handling route updates.
192 */
193 private void doUpdatesThread() {
194 boolean interrupted = false;
195 try {
196 while (!interrupted) {
197 try {
198 Collection<RouteUpdate> routeUpdates =
199 routeUpdatesQueue.take();
200 processRouteUpdates(routeUpdates);
201 } catch (InterruptedException e) {
202 log.error("Interrupted while taking from updates queue", e);
203 interrupted = true;
204 } catch (Exception e) {
205 log.error("exception", e);
206 }
207 }
208 } finally {
209 if (interrupted) {
210 Thread.currentThread().interrupt();
211 }
212 }
213 }
214
215 /**
216 * Gets all IPv4 routes from the RIB.
217 *
218 * @return all IPv4 routes from the RIB
219 */
220 @Override
221 public Collection<RouteEntry> getRoutes4() {
222 Iterator<KeyValuePair<RouteEntry>> it =
223 ribTable4.getKeyValuePairsForKeysStartingWith("").iterator();
224
225 List<RouteEntry> routes = new LinkedList<>();
226
227 while (it.hasNext()) {
228 KeyValuePair<RouteEntry> entry = it.next();
229 routes.add(entry.getValue());
230 }
231
232 return routes;
233 }
234
235 /**
236 * Gets all IPv6 routes from the RIB.
237 *
238 * @return all IPv6 routes from the RIB
239 */
240 @Override
241 public Collection<RouteEntry> getRoutes6() {
242 Iterator<KeyValuePair<RouteEntry>> it =
243 ribTable6.getKeyValuePairsForKeysStartingWith("").iterator();
244
245 List<RouteEntry> routes = new LinkedList<>();
246
247 while (it.hasNext()) {
248 KeyValuePair<RouteEntry> entry = it.next();
249 routes.add(entry.getValue());
250 }
251
252 return routes;
253 }
254
255 /**
256 * Finds a route in the RIB for a prefix. The prefix can be either IPv4 or
257 * IPv6.
258 *
259 * @param prefix the prefix to use
260 * @return the route if found, otherwise null
261 */
262 RouteEntry findRibRoute(IpPrefix prefix) {
263 String binaryString = createBinaryString(prefix);
264 if (prefix.isIp4()) {
265 // IPv4
266 return ribTable4.getValueForExactKey(binaryString);
267 }
268 // IPv6
269 return ribTable6.getValueForExactKey(binaryString);
270 }
271
272 /**
273 * Adds a route to the RIB. The route can be either IPv4 or IPv6.
274 *
275 * @param routeEntry the route entry to use
276 */
277 void addRibRoute(RouteEntry routeEntry) {
278 if (routeEntry.isIp4()) {
279 // IPv4
280 ribTable4.put(createBinaryString(routeEntry.prefix()), routeEntry);
281 } else {
282 // IPv6
283 ribTable6.put(createBinaryString(routeEntry.prefix()), routeEntry);
284 }
285 }
286
287 /**
288 * Removes a route for a prefix from the RIB. The prefix can be either IPv4
289 * or IPv6.
290 *
291 * @param prefix the prefix to use
292 * @return true if the route was found and removed, otherwise false
293 */
294 boolean removeRibRoute(IpPrefix prefix) {
295 if (prefix.isIp4()) {
296 // IPv4
297 return ribTable4.remove(createBinaryString(prefix));
298 }
299 // IPv6
300 return ribTable6.remove(createBinaryString(prefix));
301 }
302
303 /**
304 * Processes route updates.
305 *
306 * @param routeUpdates the route updates to process
307 */
308 void processRouteUpdates(Collection<RouteUpdate> routeUpdates) {
309 synchronized (this) {
310 Collection<IpPrefix> withdrawPrefixes = new LinkedList<>();
311 Collection<FibUpdate> fibUpdates = new LinkedList<>();
312 Collection<FibUpdate> fibWithdraws = new LinkedList<>();
313
314 for (RouteUpdate update : routeUpdates) {
315 switch (update.type()) {
316 case UPDATE:
317
318 FibEntry fib = processRouteAdd(update.routeEntry(),
319 withdrawPrefixes);
320 if (fib != null) {
321 fibUpdates.add(new FibUpdate(FibUpdate.Type.UPDATE, fib));
322 }
323
324 break;
325 case DELETE:
326 processRouteDelete(update.routeEntry(), withdrawPrefixes);
327
328 break;
329 default:
330 log.error("Unknown update Type: {}", update.type());
331 break;
332 }
333 }
334
335 withdrawPrefixes.forEach(p -> fibWithdraws.add(new FibUpdate(
336 FibUpdate.Type.DELETE, new FibEntry(p, null, null))));
337
338 if (!fibUpdates.isEmpty() || !fibWithdraws.isEmpty()) {
339 fibComponent.update(fibUpdates, fibWithdraws);
340 }
341 }
342 }
343
344 /**
345 * Processes adding a route entry.
346 * <p>
347 * The route entry is added to the radix tree. If there was an existing
348 * next hop for this prefix, but the next hop was different, then the
349 * old route entry is deleted.
350 * </p>
351 * <p>
352 * NOTE: Currently, we don't handle routes if the next hop is within the
353 * SDN domain.
354 * </p>
355 *
356 * @param routeEntry the route entry to add
357 * @param withdrawPrefixes the collection of accumulated prefixes whose
358 * intents will be withdrawn
359 * @return the corresponding FIB entry change, or null
360 */
361 private FibEntry processRouteAdd(RouteEntry routeEntry,
362 Collection<IpPrefix> withdrawPrefixes) {
363 log.debug("Processing route add: {}", routeEntry);
364
365 // Find the old next-hop if we are updating an old route entry
366 IpAddress oldNextHop = null;
367 RouteEntry oldRouteEntry = findRibRoute(routeEntry.prefix());
368 if (oldRouteEntry != null) {
369 oldNextHop = oldRouteEntry.nextHop();
370 }
371
372 // Add the new route to the RIB
373 addRibRoute(routeEntry);
374
375 if (oldNextHop != null) {
376 if (oldNextHop.equals(routeEntry.nextHop())) {
377 return null; // No change
378 }
379 //
380 // Update an existing nexthop for the prefix.
381 // We need to remove the old flows for this prefix from the
382 // switches before the new flows are added.
383 //
384 withdrawPrefixes.add(routeEntry.prefix());
385 }
386
387 if (routingConfigurationService.isIpPrefixLocal(routeEntry.prefix())) {
388 // Route originated by local SDN domain
389 // We don't handle these here, reactive routing APP will handle
390 // these
391 log.debug("Own route {} to {}",
392 routeEntry.prefix(), routeEntry.nextHop());
393 return null;
394 }
395
396 //
397 // Find the MAC address of next hop router for this route entry.
398 // If the MAC address can not be found in ARP cache, then this prefix
399 // will be put in routesWaitingOnArp queue. Otherwise, generate
400 // a new route intent.
401 //
402
403 // Monitor the IP address for updates of the MAC address
404 hostService.startMonitoringIp(routeEntry.nextHop());
405
406 // Check if we know the MAC address of the next hop
407 MacAddress nextHopMacAddress = ip2Mac.get(routeEntry.nextHop());
408 if (nextHopMacAddress == null) {
409 Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
410 if (!hosts.isEmpty()) {
411 nextHopMacAddress = hosts.iterator().next().mac();
412 }
413 if (nextHopMacAddress != null) {
414 ip2Mac.put(routeEntry.nextHop(), nextHopMacAddress);
415 }
416 }
417 if (nextHopMacAddress == null) {
418 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
419 return null;
420 }
421 return new FibEntry(routeEntry.prefix(), routeEntry.nextHop(),
422 nextHopMacAddress);
423 }
424
425 /**
426 * Processes the deletion of a route entry.
427 * <p>
428 * The prefix for the routing entry is removed from radix tree.
429 * If the operation is successful, the prefix is added to the collection
430 * of prefixes whose intents that will be withdrawn.
431 * </p>
432 *
433 * @param routeEntry the route entry to delete
434 * @param withdrawPrefixes the collection of accumulated prefixes whose
435 * intents will be withdrawn
436 */
437 private void processRouteDelete(RouteEntry routeEntry,
438 Collection<IpPrefix> withdrawPrefixes) {
439 log.debug("Processing route delete: {}", routeEntry);
440 boolean isRemoved = removeRibRoute(routeEntry.prefix());
441
442 if (isRemoved) {
443 //
444 // Only withdraw intents if an entry was actually removed from the
445 // tree. If no entry was removed, the <prefix, nexthop> wasn't
446 // there so it's probably already been removed and we don't
447 // need to do anything.
448 //
449 withdrawPrefixes.add(routeEntry.prefix());
450 }
451
452 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
453 }
454
455 /**
456 * Signals the Router that the MAC to IP mapping has potentially been
457 * updated. This has the effect of updating the MAC address for any
458 * installed prefixes if it has changed, as well as installing any pending
459 * prefixes that were waiting for MAC resolution.
460 *
461 * @param ipAddress the IP address that an event was received for
462 * @param macAddress the most recently known MAC address for the IP address
463 */
464 private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
465 log.debug("Received updated MAC info: {} => {}", ipAddress,
466 macAddress);
467
468 //
469 // We synchronize on "this" to prevent changes to the Radix tree
470 // while we're pushing intents. If the tree changes, the
471 // tree and the intents could get out of sync.
472 //
473 synchronized (this) {
474 Collection<FibUpdate> submitFibEntries = new LinkedList<>();
475
476 Set<RouteEntry> routesToPush =
477 routesWaitingOnArp.removeAll(ipAddress);
478
479 for (RouteEntry routeEntry : routesToPush) {
480 // These will always be adds
481 RouteEntry foundRouteEntry = findRibRoute(routeEntry.prefix());
482 if (foundRouteEntry != null &&
483 foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
484 // We only push FIB updates if the prefix is still in the
485 // radix tree and the next hop is the same as our entry.
486 // The prefix could have been removed while we were waiting
487 // for the ARP, or the next hop could have changed.
488 submitFibEntries.add(new FibUpdate(FibUpdate.Type.UPDATE,
489 new FibEntry(routeEntry.prefix(),
490 ipAddress, macAddress)));
491 } else {
492 log.debug("{} has been revoked before the MAC was resolved",
493 routeEntry);
494 }
495 }
496
497 if (!submitFibEntries.isEmpty()) {
498 fibComponent.update(submitFibEntries, Collections.emptyList());
499 }
500
501 ip2Mac.put(ipAddress, macAddress);
502 }
503 }
504
505 /**
506 * Listener for host events.
507 */
508 class InternalHostListener implements HostListener {
509 @Override
510 public void event(HostEvent event) {
511 log.debug("Received HostEvent {}", event);
512
513 Host host = event.subject();
514 switch (event.type()) {
515 case HOST_ADDED:
516 // FALLTHROUGH
517 case HOST_UPDATED:
518 for (IpAddress ipAddress : host.ipAddresses()) {
519 updateMac(ipAddress, host.mac());
520 }
521 break;
522 case HOST_REMOVED:
523 for (IpAddress ipAddress : host.ipAddresses()) {
524 ip2Mac.remove(ipAddress);
525 }
526 break;
527 default:
528 break;
529 }
530 }
531 }
532
533 /**
534 * Listener for route events.
535 */
536 private class InternalRouteListener implements RouteListener {
537 @Override
538 public void update(Collection<RouteUpdate> routeUpdates) {
539 DefaultRouter.this.update(routeUpdates);
540 }
541 }
542
543 @Override
544 public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
545 RouteEntry routeEntry = null;
546 Iterable<RouteEntry> routeEntries;
547
548 if (ipAddress.isIp4()) {
549 routeEntries = ribTable4.getValuesForKeysPrefixing(
550 createBinaryString(
551 IpPrefix.valueOf(ipAddress, Ip4Address.BIT_LENGTH)));
552 } else {
553 routeEntries = ribTable6.getValuesForKeysPrefixing(
554 createBinaryString(
555 IpPrefix.valueOf(ipAddress, Ip6Address.BIT_LENGTH)));
556 }
557 if (routeEntries == null) {
558 return null;
559 }
560 Iterator<RouteEntry> it = routeEntries.iterator();
561 while (it.hasNext()) {
562 routeEntry = it.next();
563 }
564 return routeEntry;
565 }
566
567}