blob: 1d8ef1696558d8ac8d0157d65d06a458430d2c5c [file] [log] [blame]
Jonathan Hart335ef462014-10-16 08:20:46 -07001package org.onlab.onos.sdnip;
2
Pingping3855f312014-10-22 12:50:37 -07003import java.util.Collection;
4import java.util.HashMap;
5import java.util.HashSet;
6import java.util.Iterator;
7import java.util.LinkedList;
8import java.util.List;
9import java.util.Map;
10import java.util.Set;
11import java.util.concurrent.BlockingQueue;
12import java.util.concurrent.ConcurrentHashMap;
13import java.util.concurrent.ExecutorService;
14import java.util.concurrent.Executors;
15import java.util.concurrent.LinkedBlockingQueue;
16import java.util.concurrent.Semaphore;
17
Thomas Vachuskab97cf282014-10-20 23:31:12 -070018import org.apache.commons.lang3.tuple.Pair;
19import org.onlab.onos.ApplicationId;
20import org.onlab.onos.net.ConnectPoint;
21import org.onlab.onos.net.Host;
22import org.onlab.onos.net.flow.DefaultTrafficSelector;
23import org.onlab.onos.net.flow.DefaultTrafficTreatment;
24import org.onlab.onos.net.flow.TrafficSelector;
25import org.onlab.onos.net.flow.TrafficTreatment;
26import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
27import org.onlab.onos.net.flow.criteria.Criterion;
28import org.onlab.onos.net.flow.criteria.Criterion.Type;
29import org.onlab.onos.net.host.HostEvent;
30import org.onlab.onos.net.host.HostListener;
31import org.onlab.onos.net.host.HostService;
32import org.onlab.onos.net.intent.Intent;
33import org.onlab.onos.net.intent.IntentService;
34import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
35import org.onlab.onos.sdnip.config.BgpPeer;
36import org.onlab.onos.sdnip.config.Interface;
37import org.onlab.onos.sdnip.config.SdnIpConfigService;
38import org.onlab.packet.Ethernet;
39import org.onlab.packet.IpAddress;
40import org.onlab.packet.IpPrefix;
41import org.onlab.packet.MacAddress;
42import org.slf4j.Logger;
43import org.slf4j.LoggerFactory;
44
Pingping3855f312014-10-22 12:50:37 -070045import com.google.common.base.Objects;
46import com.google.common.collect.HashMultimap;
47import com.google.common.collect.Multimaps;
48import com.google.common.collect.SetMultimap;
49import com.google.common.util.concurrent.ThreadFactoryBuilder;
50import com.googlecode.concurrenttrees.common.KeyValuePair;
51import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
52import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
53import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
Jonathan Hart335ef462014-10-16 08:20:46 -070054
Jonathan Hart335ef462014-10-16 08:20:46 -070055/**
56 * This class processes BGP route update, translates each update into a intent
57 * and submits the intent.
Jonathan Hart335ef462014-10-16 08:20:46 -070058 */
59public class Router implements RouteListener {
60
61 private static final Logger log = LoggerFactory.getLogger(Router.class);
62
Jonathan Hart0b04bed2014-10-16 16:39:19 -070063 // Store all route updates in a radix tree.
64 // The key in this tree is the binary string of prefix of the route.
Jonathan Hart335ef462014-10-16 08:20:46 -070065 private InvertedRadixTree<RouteEntry> bgpRoutes;
66
67 // Stores all incoming route updates in a queue.
68 private BlockingQueue<RouteUpdate> routeUpdates;
69
Jonathan Hart31582d12014-10-22 13:52:41 -070070 // The IpAddress is the next hop address of each route update.
Jonathan Hart335ef462014-10-16 08:20:46 -070071 private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
72 private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
73
74 private IntentService intentService;
Jonathan Hart335ef462014-10-16 08:20:46 -070075 private HostService hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -070076 private SdnIpConfigService configService;
Jonathan Hart335ef462014-10-16 08:20:46 -070077 private InterfaceService interfaceService;
78
79 private ExecutorService bgpUpdatesExecutor;
80 private ExecutorService bgpIntentsSynchronizerExecutor;
81
Thomas Vachuskab97cf282014-10-20 23:31:12 -070082 private final ApplicationId appId;
Jonathan Hart335ef462014-10-16 08:20:46 -070083
84 //
85 // State to deal with SDN-IP Leader election and pushing Intents
86 //
87 private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
88 private volatile boolean isElectedLeader = false;
89 private volatile boolean isActivatedLeader = false;
90
Jonathan Hartbcae7bd2014-10-16 10:24:41 -070091 // For routes announced by local BGP daemon in SDN network,
Jonathan Hart335ef462014-10-16 08:20:46 -070092 // the next hop will be 0.0.0.0.
93 public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
94
95 /**
96 * Class constructor.
97 *
Jonathan Hart31582d12014-10-22 13:52:41 -070098 * @param appId the application ID
Thomas Vachuskab97cf282014-10-20 23:31:12 -070099 * @param intentService the intent service
100 * @param hostService the host service
Jonathan Hart31582d12014-10-22 13:52:41 -0700101 * @param configService the configuration service
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700102 * @param interfaceService the interface service
Jonathan Hart335ef462014-10-16 08:20:46 -0700103 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700104 public Router(ApplicationId appId, IntentService intentService,
Jonathan Hart31582d12014-10-22 13:52:41 -0700105 HostService hostService, SdnIpConfigService configService,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700106 InterfaceService interfaceService) {
107 this.appId = appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700108 this.intentService = intentService;
109 this.hostService = hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -0700110 this.configService = configService;
Jonathan Hart335ef462014-10-16 08:20:46 -0700111 this.interfaceService = interfaceService;
112
113 bgpRoutes = new ConcurrentInvertedRadixTree<>(
114 new DefaultByteArrayNodeFactory());
115 routeUpdates = new LinkedBlockingQueue<>();
116 routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
117 HashMultimap.<IpAddress, RouteEntry>create());
118 pushedRouteIntents = new ConcurrentHashMap<>();
119
120 bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
121 new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
122 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
123 new ThreadFactoryBuilder()
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700124 .setNameFormat("bgp-intents-synchronizer-%d").build());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700125
126 this.hostService.addListener(new InternalHostListener());
Jonathan Hart335ef462014-10-16 08:20:46 -0700127 }
128
129 /**
130 * Starts the Router.
131 */
132 public void start() {
133
Jonathan Hart0e12fad2014-10-17 14:54:58 -0700134 // TODO hack to enable SDN-IP now for testing
135 isElectedLeader = true;
136 isActivatedLeader = true;
137
Jonathan Hart335ef462014-10-16 08:20:46 -0700138 bgpUpdatesExecutor.execute(new Runnable() {
139 @Override
140 public void run() {
141 doUpdatesThread();
142 }
143 });
144
145 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
146 @Override
147 public void run() {
148 doIntentSynchronizationThread();
149 }
150 });
151 }
152
153 //@Override TODO hook this up to something
154 public void leaderChanged(boolean isLeader) {
155 log.debug("Leader changed: {}", isLeader);
156
157 if (!isLeader) {
158 this.isElectedLeader = false;
159 this.isActivatedLeader = false;
160 return; // Nothing to do
161 }
162 this.isActivatedLeader = false;
163 this.isElectedLeader = true;
164
165 //
166 // Tell the Intents Synchronizer thread to start the synchronization
167 //
168 intentsSynchronizerSemaphore.release();
169 }
170
171 @Override
172 public void update(RouteUpdate routeUpdate) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700173 log.debug("Received new route update: {}", routeUpdate);
Jonathan Hart335ef462014-10-16 08:20:46 -0700174
175 try {
176 routeUpdates.put(routeUpdate);
177 } catch (InterruptedException e) {
178 log.debug("Interrupted while putting on routeUpdates queue", e);
179 Thread.currentThread().interrupt();
180 }
181 }
182
183 /**
184 * Thread for Intent Synchronization.
185 */
186 private void doIntentSynchronizationThread() {
187 boolean interrupted = false;
188 try {
189 while (!interrupted) {
190 try {
191 intentsSynchronizerSemaphore.acquire();
192 //
193 // Drain all permits, because a single synchronization is
194 // sufficient.
195 //
196 intentsSynchronizerSemaphore.drainPermits();
197 } catch (InterruptedException e) {
198 log.debug("Interrupted while waiting to become " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700199 "Intent Synchronization leader");
Jonathan Hart335ef462014-10-16 08:20:46 -0700200 interrupted = true;
201 break;
202 }
203 syncIntents();
204 }
205 } finally {
206 if (interrupted) {
207 Thread.currentThread().interrupt();
208 }
209 }
210 }
211
212 /**
213 * Thread for handling route updates.
214 */
215 private void doUpdatesThread() {
216 boolean interrupted = false;
217 try {
218 while (!interrupted) {
219 try {
220 RouteUpdate update = routeUpdates.take();
221 switch (update.type()) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700222 case UPDATE:
223 processRouteAdd(update.routeEntry());
224 break;
225 case DELETE:
226 processRouteDelete(update.routeEntry());
227 break;
228 default:
229 log.error("Unknown update Type: {}", update.type());
230 break;
Jonathan Hart335ef462014-10-16 08:20:46 -0700231 }
232 } catch (InterruptedException e) {
233 log.debug("Interrupted while taking from updates queue", e);
234 interrupted = true;
235 } catch (Exception e) {
236 log.debug("exception", e);
237 }
238 }
239 } finally {
240 if (interrupted) {
241 Thread.currentThread().interrupt();
242 }
243 }
244 }
245
246 /**
247 * Performs Intents Synchronization between the internally stored Route
248 * Intents and the installed Route Intents.
249 */
250 private void syncIntents() {
251 synchronized (this) {
252 if (!isElectedLeader) {
253 return; // Nothing to do: not the leader anymore
254 }
255 log.debug("Syncing SDN-IP Route Intents...");
256
257 Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700258 new HashMap<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700259
260 //
261 // Fetch all intents, and classify the Multi-Point-to-Point Intents
262 // based on the matching prefix.
263 //
264 for (Intent intent : intentService.getIntents()) {
265 //
266 // TODO: Ignore all intents that are not installed by
267 // the SDN-IP application.
268 //
269 if (!(intent instanceof MultiPointToSinglePointIntent)) {
270 continue;
271 }
272 MultiPointToSinglePointIntent mp2pIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700273 (MultiPointToSinglePointIntent) intent;
Jonathan Hart335ef462014-10-16 08:20:46 -0700274 /*Match match = mp2pIntent.getMatch();
275 if (!(match instanceof PacketMatch)) {
276 continue;
277 }
278 PacketMatch packetMatch = (PacketMatch) match;
279 Ip4Prefix prefix = packetMatch.getDstIpAddress();
280 if (prefix == null) {
281 continue;
282 }
283 fetchedIntents.put(prefix, mp2pIntent);*/
284 for (Criterion criterion : mp2pIntent.selector().criteria()) {
285 if (criterion.type() == Type.IPV4_DST) {
286 IPCriterion ipCriterion = (IPCriterion) criterion;
287 fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
288 }
289 }
290
291 }
292
293 //
294 // Compare for each prefix the local IN-MEMORY Intents with the
295 // FETCHED Intents:
296 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
297 // the FETCHED Intent in the local memory (i.e., override the
298 // IN-MEMORY Intent) to preserve the original Intent ID
299 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
300 // delete the FETCHED Intent, and push/install the IN-MEMORY
301 // Intent.
302 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
303 // Intent for same prefix, then push/install the IN-MEMORY
304 // Intent.
305 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
306 // Intent for same prefix, then delete/withdraw the FETCHED
307 // Intent.
308 //
309 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700310 storeInMemoryIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700311 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700312 addIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700313 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700314 deleteIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700315 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700316 pushedRouteIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700317 IpPrefix prefix = entry.getKey();
318 MultiPointToSinglePointIntent inMemoryIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700319 entry.getValue();
Jonathan Hart335ef462014-10-16 08:20:46 -0700320 MultiPointToSinglePointIntent fetchedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700321 fetchedIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700322
323 if (fetchedIntent == null) {
324 //
325 // No FETCHED Intent for same prefix: push the IN-MEMORY
326 // Intent.
327 //
328 addIntents.add(Pair.of(prefix, inMemoryIntent));
329 continue;
330 }
331
332 //
333 // If IN-MEMORY Intent is same as the FETCHED Intent,
334 // store the FETCHED Intent in the local memory.
335 //
336 if (compareMultiPointToSinglePointIntents(inMemoryIntent,
337 fetchedIntent)) {
338 storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
339 } else {
340 //
341 // The IN-MEMORY Intent is not same as the FETCHED Intent,
342 // hence delete the FETCHED Intent, and install the
343 // IN-MEMORY Intent.
344 //
345 deleteIntents.add(Pair.of(prefix, fetchedIntent));
346 addIntents.add(Pair.of(prefix, inMemoryIntent));
347 }
348 fetchedIntents.remove(prefix);
349 }
350
351 //
352 // Any remaining FETCHED Intents have to be deleted/withdrawn
353 //
354 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700355 fetchedIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700356 IpPrefix prefix = entry.getKey();
357 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
358 deleteIntents.add(Pair.of(prefix, fetchedIntent));
359 }
360
361 //
362 // Perform the actions:
363 // 1. Store in memory fetched intents that are same. Can be done
364 // even if we are not the leader anymore
365 // 2. Delete intents: check if the leader before each operation
366 // 3. Add intents: check if the leader before each operation
367 //
368 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700369 storeInMemoryIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700370 IpPrefix prefix = pair.getLeft();
371 MultiPointToSinglePointIntent intent = pair.getRight();
372 log.debug("Intent synchronization: updating in-memory " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700373 "Intent for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700374 pushedRouteIntents.put(prefix, intent);
375 }
376 //
377 isActivatedLeader = true; // Allow push of Intents
378 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700379 deleteIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700380 IpPrefix prefix = pair.getLeft();
381 MultiPointToSinglePointIntent intent = pair.getRight();
382 if (!isElectedLeader) {
383 isActivatedLeader = false;
384 return;
385 }
386 log.debug("Intent synchronization: deleting Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700387 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700388 intentService.withdraw(intent);
389 }
390 //
391 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700392 addIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700393 IpPrefix prefix = pair.getLeft();
394 MultiPointToSinglePointIntent intent = pair.getRight();
395 if (!isElectedLeader) {
396 isActivatedLeader = false;
397 return;
398 }
399 log.debug("Intent synchronization: adding Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700400 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700401 intentService.submit(intent);
402 }
403 if (!isElectedLeader) {
404 isActivatedLeader = false;
405 }
406 log.debug("Syncing SDN-IP routes completed.");
407 }
408 }
409
410 /**
411 * Compares two Multi-point to Single Point Intents whether they represent
412 * same logical intention.
413 *
414 * @param intent1 the first Intent to compare
415 * @param intent2 the second Intent to compare
416 * @return true if both Intents represent same logical intention, otherwise
417 * false
418 */
419 private boolean compareMultiPointToSinglePointIntents(
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700420 MultiPointToSinglePointIntent intent1,
421 MultiPointToSinglePointIntent intent2) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700422 /*Match match1 = intent1.getMatch();
423 Match match2 = intent2.getMatch();
424 Action action1 = intent1.getAction();
425 Action action2 = intent2.getAction();
426 Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
427 Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
428 SwitchPort egressPort1 = intent1.getEgressPort();
429 SwitchPort egressPort2 = intent2.getEgressPort();
430
431 return Objects.equal(match1, match2) &&
432 Objects.equal(action1, action2) &&
433 Objects.equal(egressPort1, egressPort2) &&
434 Objects.equal(ingressPorts1, ingressPorts2);*/
435 return Objects.equal(intent1.selector(), intent2.selector()) &&
436 Objects.equal(intent1.treatment(), intent2.treatment()) &&
437 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
438 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
439 }
440
441 /**
442 * Processes adding a route entry.
443 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700444 * Put new route entry into the radix tree. If there was an existing
445 * next hop for this prefix, but the next hop was different, then execute
Jonathan Hart335ef462014-10-16 08:20:46 -0700446 * deleting old route entry. If the next hop is the SDN domain, we do not
447 * handle it at the moment. Otherwise, execute adding a route.
448 *
449 * @param routeEntry the route entry to add
450 */
451 protected void processRouteAdd(RouteEntry routeEntry) {
452 synchronized (this) {
453 log.debug("Processing route add: {}", routeEntry);
454
455 IpPrefix prefix = routeEntry.prefix();
456 IpAddress nextHop = null;
457 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700458 bgpRoutes.put(RouteEntry.createBinaryString(prefix),
459 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700460 if (foundRouteEntry != null) {
461 nextHop = foundRouteEntry.nextHop();
462 }
463
464 if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
465 // There was an existing nexthop for this prefix. This update
466 // supersedes that, so we need to remove the old flows for this
467 // prefix from the switches
468 executeRouteDelete(routeEntry);
469 }
470 if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
471 return;
472 }
473
474 if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
475 // Route originated by SDN domain
476 // We don't handle these at the moment
477 log.debug("Own route {} to {}",
478 routeEntry.prefix(), routeEntry.nextHop());
479 return;
480 }
481
482 executeRouteAdd(routeEntry);
483 }
484 }
485
486 /**
487 * Executes adding a route entry.
488 * <p/>
489 * Find out the egress Interface and MAC address of next hop router for
490 * this route entry. If the MAC address can not be found in ARP cache,
491 * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
492 * new route intent will be created and installed.
493 *
494 * @param routeEntry the route entry to add
495 */
496 private void executeRouteAdd(RouteEntry routeEntry) {
497 log.debug("Executing route add: {}", routeEntry);
498
Jonathan Hart31582d12014-10-22 13:52:41 -0700499 // Monitor the IP address so we'll get notified of updates to the MAC
500 // address.
501 hostService.startMonitoringIp(routeEntry.nextHop());
502
Jonathan Hart335ef462014-10-16 08:20:46 -0700503 // See if we know the MAC address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700504 MacAddress nextHopMacAddress = null;
505 Set<Host> hosts = hostService.getHostsByIp(
506 routeEntry.nextHop().toPrefix());
507 if (!hosts.isEmpty()) {
508 // TODO how to handle if multiple hosts are returned?
509 nextHopMacAddress = hosts.iterator().next().mac();
510 }
511
512 if (nextHopMacAddress == null) {
513 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700514 return;
515 }
516
517 addRouteIntentToNextHop(routeEntry.prefix(),
518 routeEntry.nextHop(),
519 nextHopMacAddress);
520 }
521
522 /**
523 * Adds a route intent given a prefix and a next hop IP address. This
524 * method will find the egress interface for the intent.
525 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700526 * @param prefix IP prefix of the route to add
527 * @param nextHopIpAddress IP address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700528 * @param nextHopMacAddress MAC address of the next hop
529 */
530 private void addRouteIntentToNextHop(IpPrefix prefix,
531 IpAddress nextHopIpAddress,
532 MacAddress nextHopMacAddress) {
533
534 // Find the attachment point (egress interface) of the next hop
535 Interface egressInterface;
Jonathan Hart31582d12014-10-22 13:52:41 -0700536 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700537 // Route to a peer
538 log.debug("Route to peer {}", nextHopIpAddress);
539 BgpPeer peer =
Jonathan Hart31582d12014-10-22 13:52:41 -0700540 configService.getBgpPeers().get(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700541 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700542 interfaceService.getInterface(peer.connectPoint());
Jonathan Hart335ef462014-10-16 08:20:46 -0700543 } else {
544 // Route to non-peer
545 log.debug("Route to non-peer {}", nextHopIpAddress);
546 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700547 interfaceService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700548 if (egressInterface == null) {
549 log.warn("No outgoing interface found for {}",
550 nextHopIpAddress);
551 return;
552 }
553 }
554
555 doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
556 }
557
558 /**
559 * Installs a route intent for a prefix.
560 * <p/>
561 * Intent will match dst IP prefix and rewrite dst MAC address at all other
562 * border switches, then forward packets according to dst MAC address.
563 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700564 * @param prefix IP prefix from route
565 * @param egressInterface egress Interface connected to next hop router
Jonathan Hart335ef462014-10-16 08:20:46 -0700566 * @param nextHopMacAddress MAC address of next hop router
567 */
568 private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700569 MacAddress nextHopMacAddress) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700570 log.debug("Adding intent for prefix {}, next hop mac {}",
571 prefix, nextHopMacAddress);
572
573 MultiPointToSinglePointIntent pushedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700574 pushedRouteIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700575
576 // Just for testing.
577 if (pushedIntent != null) {
578 log.error("There should not be a pushed intent: {}", pushedIntent);
579 }
580
581 ConnectPoint egressPort = egressInterface.connectPoint();
582
583 Set<ConnectPoint> ingressPorts = new HashSet<>();
584
585 for (Interface intf : interfaceService.getInterfaces()) {
586 if (!intf.equals(egressInterface)) {
587 ConnectPoint srcPort = intf.connectPoint();
588 ingressPorts.add(srcPort);
589 }
590 }
591
592 // Match the destination IP prefix at the first hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700593 TrafficSelector selector = DefaultTrafficSelector.builder()
594 .matchEthType(Ethernet.TYPE_IPV4)
595 .matchIPDst(prefix)
596 .build();
597
598 // Rewrite the destination MAC address
Jonathan Hart335ef462014-10-16 08:20:46 -0700599 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
600 .setEthDst(nextHopMacAddress)
601 .build();
602
603 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700604 new MultiPointToSinglePointIntent(appId, selector, treatment,
605 ingressPorts, egressPort);
Jonathan Hart335ef462014-10-16 08:20:46 -0700606
607 if (isElectedLeader && isActivatedLeader) {
608 log.debug("Intent installation: adding Intent for prefix: {}",
609 prefix);
610 intentService.submit(intent);
611 }
612
613 // Maintain the Intent
614 pushedRouteIntents.put(prefix, intent);
615 }
616
617 /**
618 * Executes deleting a route entry.
619 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700620 * Removes prefix from radix tree, and if successful, then try to delete
621 * the related intent.
Jonathan Hart335ef462014-10-16 08:20:46 -0700622 *
623 * @param routeEntry the route entry to delete
624 */
625 protected void processRouteDelete(RouteEntry routeEntry) {
626 synchronized (this) {
627 log.debug("Processing route delete: {}", routeEntry);
628 IpPrefix prefix = routeEntry.prefix();
629
Jonathan Hart335ef462014-10-16 08:20:46 -0700630 if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
631 //
632 // Only delete flows if an entry was actually removed from the
633 // tree. If no entry was removed, the <prefix, nexthop> wasn't
634 // there so it's probably already been removed and we don't
635 // need to do anything.
636 //
637 executeRouteDelete(routeEntry);
638 }
639
640 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
641 // TODO cancel the request in the ARP manager as well
642 }
643 }
644
645 /**
646 * Executed deleting a route entry.
647 *
648 * @param routeEntry the route entry to delete
649 */
650 private void executeRouteDelete(RouteEntry routeEntry) {
651 log.debug("Executing route delete: {}", routeEntry);
652
653 IpPrefix prefix = routeEntry.prefix();
654
655 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700656 pushedRouteIntents.remove(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700657
658 if (intent == null) {
659 log.debug("There is no intent in pushedRouteIntents to delete " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700660 "for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700661 } else {
662 if (isElectedLeader && isActivatedLeader) {
663 log.debug("Intent installation: deleting Intent for prefix: {}",
664 prefix);
665 intentService.withdraw(intent);
666 }
667 }
668 }
669
670 /**
Jonathan Hart31582d12014-10-22 13:52:41 -0700671 * Signals the Router that the MAC to IP mapping has potentially been
672 * updated. This has the effect of updating the MAC address for any
673 * installed prefixes if it has changed, as well as installing any pending
674 * prefixes that were waiting for MAC resolution.
Jonathan Hart335ef462014-10-16 08:20:46 -0700675 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700676 * @param ipAddress the IP address that an event was received for
677 * @param macAddress the most recently known MAC address for the IP address
Jonathan Hart335ef462014-10-16 08:20:46 -0700678 */
Jonathan Hart31582d12014-10-22 13:52:41 -0700679 private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
680 log.debug("Received updated MAC info: {} => {}", ipAddress, macAddress);
681
682 // TODO here we should check whether the next hop for any of our
683 // installed prefixes has changed, not just prefixes pending installation.
Jonathan Hart335ef462014-10-16 08:20:46 -0700684
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700685 // We synchronize on this to prevent changes to the radix tree
686 // while we're pushing intents. If the tree changes, the
687 // tree and intents could get out of sync.
Jonathan Hart335ef462014-10-16 08:20:46 -0700688 synchronized (this) {
689
690 Set<RouteEntry> routesToPush =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700691 routesWaitingOnArp.removeAll(ipAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700692
693 for (RouteEntry routeEntry : routesToPush) {
694 // These will always be adds
695 IpPrefix prefix = routeEntry.prefix();
696 String binaryString = RouteEntry.createBinaryString(prefix);
697 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700698 bgpRoutes.getValueForExactKey(binaryString);
Jonathan Hart335ef462014-10-16 08:20:46 -0700699 if (foundRouteEntry != null &&
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700700 foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700701 // We only push prefix flows if the prefix is still in the
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700702 // radix tree and the next hop is the same as our
Jonathan Hart335ef462014-10-16 08:20:46 -0700703 // update.
704 // The prefix could have been removed while we were waiting
705 // for the ARP, or the next hop could have changed.
706 addRouteIntentToNextHop(prefix, ipAddress, macAddress);
707 } else {
Jonathan Hart31582d12014-10-22 13:52:41 -0700708 log.debug("{} has been revoked before the MAC was resolved",
709 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700710 }
711 }
712 }
713 }
714
715 /**
716 * Gets the SDN-IP routes.
717 *
718 * @return the SDN-IP routes
719 */
720 public Collection<RouteEntry> getRoutes() {
721 Iterator<KeyValuePair<RouteEntry>> it =
722 bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
723
724 List<RouteEntry> routes = new LinkedList<>();
725
726 while (it.hasNext()) {
727 KeyValuePair<RouteEntry> entry = it.next();
728 routes.add(entry.getValue());
729 }
730
731 return routes;
732 }
733
734 /**
Pingping3855f312014-10-22 12:50:37 -0700735 * Gets the pushed route intents.
736 *
737 * @return the pushed route intents
738 */
739 public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
740 List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
741
742 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
743 pushedRouteIntents.entrySet()) {
744 pushedIntents.add(entry.getValue());
745 }
746 return pushedIntents;
747 }
748
749 /**
Jonathan Hart335ef462014-10-16 08:20:46 -0700750 * Listener for host events.
751 */
752 class InternalHostListener implements HostListener {
753 @Override
754 public void event(HostEvent event) {
755 if (event.type() == HostEvent.Type.HOST_ADDED ||
756 event.type() == HostEvent.Type.HOST_UPDATED) {
757 Host host = event.subject();
758 for (IpPrefix ip : host.ipAddresses()) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700759 updateMac(ip.toIpAddress(), host.mac());
Jonathan Hart335ef462014-10-16 08:20:46 -0700760 }
761 }
762 }
763 }
764}