blob: ca25a761c2d1781684293040502097c69782245e [file] [log] [blame]
Jonathan Hart335ef462014-10-16 08:20:46 -07001package org.onlab.onos.sdnip;
2
Thomas Vachuskab97cf282014-10-20 23:31:12 -07003import com.google.common.base.Objects;
4import com.google.common.collect.HashMultimap;
5import com.google.common.collect.Multimaps;
6import com.google.common.collect.SetMultimap;
7import com.google.common.util.concurrent.ThreadFactoryBuilder;
8import com.googlecode.concurrenttrees.common.KeyValuePair;
9import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
10import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
11import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
12import org.apache.commons.lang3.tuple.Pair;
13import org.onlab.onos.ApplicationId;
14import org.onlab.onos.net.ConnectPoint;
15import org.onlab.onos.net.Host;
16import org.onlab.onos.net.flow.DefaultTrafficSelector;
17import org.onlab.onos.net.flow.DefaultTrafficTreatment;
18import org.onlab.onos.net.flow.TrafficSelector;
19import org.onlab.onos.net.flow.TrafficTreatment;
20import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
21import org.onlab.onos.net.flow.criteria.Criterion;
22import org.onlab.onos.net.flow.criteria.Criterion.Type;
23import org.onlab.onos.net.host.HostEvent;
24import org.onlab.onos.net.host.HostListener;
25import org.onlab.onos.net.host.HostService;
26import org.onlab.onos.net.intent.Intent;
27import org.onlab.onos.net.intent.IntentService;
28import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
29import org.onlab.onos.sdnip.config.BgpPeer;
30import org.onlab.onos.sdnip.config.Interface;
31import org.onlab.onos.sdnip.config.SdnIpConfigService;
32import org.onlab.packet.Ethernet;
33import org.onlab.packet.IpAddress;
34import org.onlab.packet.IpPrefix;
35import org.onlab.packet.MacAddress;
36import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
Jonathan Hart335ef462014-10-16 08:20:46 -070039import java.util.Collection;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.Iterator;
43import java.util.LinkedList;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.BlockingQueue;
48import java.util.concurrent.ConcurrentHashMap;
49import java.util.concurrent.ExecutorService;
50import java.util.concurrent.Executors;
51import java.util.concurrent.LinkedBlockingQueue;
52import java.util.concurrent.Semaphore;
53
Jonathan Hart335ef462014-10-16 08:20:46 -070054/**
55 * This class processes BGP route update, translates each update into a intent
56 * and submits the intent.
Thomas Vachuskab97cf282014-10-20 23:31:12 -070057 * <p/>
Jonathan Hart335ef462014-10-16 08:20:46 -070058 * TODO: Make it thread-safe.
59 */
60public class Router implements RouteListener {
61
62 private static final Logger log = LoggerFactory.getLogger(Router.class);
63
Jonathan Hart0b04bed2014-10-16 16:39:19 -070064 // Store all route updates in a radix tree.
65 // The key in this tree is the binary string of prefix of the route.
Jonathan Hart335ef462014-10-16 08:20:46 -070066 private InvertedRadixTree<RouteEntry> bgpRoutes;
67
68 // Stores all incoming route updates in a queue.
69 private BlockingQueue<RouteUpdate> routeUpdates;
70
71 // The Ip4Address is the next hop address of each route update.
72 private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
73 private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
74
75 private IntentService intentService;
76 //private IProxyArpService proxyArp;
77 private HostService hostService;
78 private SdnIpConfigService configInfoService;
79 private InterfaceService interfaceService;
80
81 private ExecutorService bgpUpdatesExecutor;
82 private ExecutorService bgpIntentsSynchronizerExecutor;
83
Thomas Vachuskab97cf282014-10-20 23:31:12 -070084 private final ApplicationId appId;
Jonathan Hart335ef462014-10-16 08:20:46 -070085
86 //
87 // State to deal with SDN-IP Leader election and pushing Intents
88 //
89 private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
90 private volatile boolean isElectedLeader = false;
91 private volatile boolean isActivatedLeader = false;
92
Jonathan Hartbcae7bd2014-10-16 10:24:41 -070093 // For routes announced by local BGP daemon in SDN network,
Jonathan Hart335ef462014-10-16 08:20:46 -070094 // the next hop will be 0.0.0.0.
95 public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
96
97 /**
98 * Class constructor.
99 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700100 * @param intentService the intent service
101 * @param hostService the host service
Jonathan Hart335ef462014-10-16 08:20:46 -0700102 * @param configInfoService the configuration service
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700103 * @param interfaceService the interface service
Jonathan Hart335ef462014-10-16 08:20:46 -0700104 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700105 public Router(ApplicationId appId, IntentService intentService,
106 HostService hostService, SdnIpConfigService configInfoService,
107 InterfaceService interfaceService) {
108 this.appId = appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700109 this.intentService = intentService;
110 this.hostService = hostService;
111 this.configInfoService = configInfoService;
112 this.interfaceService = interfaceService;
113
114 bgpRoutes = new ConcurrentInvertedRadixTree<>(
115 new DefaultByteArrayNodeFactory());
116 routeUpdates = new LinkedBlockingQueue<>();
117 routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
118 HashMultimap.<IpAddress, RouteEntry>create());
119 pushedRouteIntents = new ConcurrentHashMap<>();
120
121 bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
122 new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
123 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
124 new ThreadFactoryBuilder()
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700125 .setNameFormat("bgp-intents-synchronizer-%d").build());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700126
127 this.hostService.addListener(new InternalHostListener());
Jonathan Hart335ef462014-10-16 08:20:46 -0700128 }
129
130 /**
131 * Starts the Router.
132 */
133 public void start() {
134
Jonathan Hart0e12fad2014-10-17 14:54:58 -0700135 // TODO hack to enable SDN-IP now for testing
136 isElectedLeader = true;
137 isActivatedLeader = true;
138
Jonathan Hart335ef462014-10-16 08:20:46 -0700139 bgpUpdatesExecutor.execute(new Runnable() {
140 @Override
141 public void run() {
142 doUpdatesThread();
143 }
144 });
145
146 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
147 @Override
148 public void run() {
149 doIntentSynchronizationThread();
150 }
151 });
152 }
153
154 //@Override TODO hook this up to something
155 public void leaderChanged(boolean isLeader) {
156 log.debug("Leader changed: {}", isLeader);
157
158 if (!isLeader) {
159 this.isElectedLeader = false;
160 this.isActivatedLeader = false;
161 return; // Nothing to do
162 }
163 this.isActivatedLeader = false;
164 this.isElectedLeader = true;
165
166 //
167 // Tell the Intents Synchronizer thread to start the synchronization
168 //
169 intentsSynchronizerSemaphore.release();
170 }
171
172 @Override
173 public void update(RouteUpdate routeUpdate) {
174 log.debug("Received new route Update: {}", routeUpdate);
175
176 try {
177 routeUpdates.put(routeUpdate);
178 } catch (InterruptedException e) {
179 log.debug("Interrupted while putting on routeUpdates queue", e);
180 Thread.currentThread().interrupt();
181 }
182 }
183
184 /**
185 * Thread for Intent Synchronization.
186 */
187 private void doIntentSynchronizationThread() {
188 boolean interrupted = false;
189 try {
190 while (!interrupted) {
191 try {
192 intentsSynchronizerSemaphore.acquire();
193 //
194 // Drain all permits, because a single synchronization is
195 // sufficient.
196 //
197 intentsSynchronizerSemaphore.drainPermits();
198 } catch (InterruptedException e) {
199 log.debug("Interrupted while waiting to become " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700200 "Intent Synchronization leader");
Jonathan Hart335ef462014-10-16 08:20:46 -0700201 interrupted = true;
202 break;
203 }
204 syncIntents();
205 }
206 } finally {
207 if (interrupted) {
208 Thread.currentThread().interrupt();
209 }
210 }
211 }
212
213 /**
214 * Thread for handling route updates.
215 */
216 private void doUpdatesThread() {
217 boolean interrupted = false;
218 try {
219 while (!interrupted) {
220 try {
221 RouteUpdate update = routeUpdates.take();
222 switch (update.type()) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700223 case UPDATE:
224 processRouteAdd(update.routeEntry());
225 break;
226 case DELETE:
227 processRouteDelete(update.routeEntry());
228 break;
229 default:
230 log.error("Unknown update Type: {}", update.type());
231 break;
Jonathan Hart335ef462014-10-16 08:20:46 -0700232 }
233 } catch (InterruptedException e) {
234 log.debug("Interrupted while taking from updates queue", e);
235 interrupted = true;
236 } catch (Exception e) {
237 log.debug("exception", e);
238 }
239 }
240 } finally {
241 if (interrupted) {
242 Thread.currentThread().interrupt();
243 }
244 }
245 }
246
247 /**
248 * Performs Intents Synchronization between the internally stored Route
249 * Intents and the installed Route Intents.
250 */
251 private void syncIntents() {
252 synchronized (this) {
253 if (!isElectedLeader) {
254 return; // Nothing to do: not the leader anymore
255 }
256 log.debug("Syncing SDN-IP Route Intents...");
257
258 Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700259 new HashMap<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700260
261 //
262 // Fetch all intents, and classify the Multi-Point-to-Point Intents
263 // based on the matching prefix.
264 //
265 for (Intent intent : intentService.getIntents()) {
266 //
267 // TODO: Ignore all intents that are not installed by
268 // the SDN-IP application.
269 //
270 if (!(intent instanceof MultiPointToSinglePointIntent)) {
271 continue;
272 }
273 MultiPointToSinglePointIntent mp2pIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700274 (MultiPointToSinglePointIntent) intent;
Jonathan Hart335ef462014-10-16 08:20:46 -0700275 /*Match match = mp2pIntent.getMatch();
276 if (!(match instanceof PacketMatch)) {
277 continue;
278 }
279 PacketMatch packetMatch = (PacketMatch) match;
280 Ip4Prefix prefix = packetMatch.getDstIpAddress();
281 if (prefix == null) {
282 continue;
283 }
284 fetchedIntents.put(prefix, mp2pIntent);*/
285 for (Criterion criterion : mp2pIntent.selector().criteria()) {
286 if (criterion.type() == Type.IPV4_DST) {
287 IPCriterion ipCriterion = (IPCriterion) criterion;
288 fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
289 }
290 }
291
292 }
293
294 //
295 // Compare for each prefix the local IN-MEMORY Intents with the
296 // FETCHED Intents:
297 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
298 // the FETCHED Intent in the local memory (i.e., override the
299 // IN-MEMORY Intent) to preserve the original Intent ID
300 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
301 // delete the FETCHED Intent, and push/install the IN-MEMORY
302 // Intent.
303 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
304 // Intent for same prefix, then push/install the IN-MEMORY
305 // Intent.
306 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
307 // Intent for same prefix, then delete/withdraw the FETCHED
308 // Intent.
309 //
310 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700311 storeInMemoryIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700312 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700313 addIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700314 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700315 deleteIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700316 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700317 pushedRouteIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700318 IpPrefix prefix = entry.getKey();
319 MultiPointToSinglePointIntent inMemoryIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700320 entry.getValue();
Jonathan Hart335ef462014-10-16 08:20:46 -0700321 MultiPointToSinglePointIntent fetchedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700322 fetchedIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700323
324 if (fetchedIntent == null) {
325 //
326 // No FETCHED Intent for same prefix: push the IN-MEMORY
327 // Intent.
328 //
329 addIntents.add(Pair.of(prefix, inMemoryIntent));
330 continue;
331 }
332
333 //
334 // If IN-MEMORY Intent is same as the FETCHED Intent,
335 // store the FETCHED Intent in the local memory.
336 //
337 if (compareMultiPointToSinglePointIntents(inMemoryIntent,
338 fetchedIntent)) {
339 storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
340 } else {
341 //
342 // The IN-MEMORY Intent is not same as the FETCHED Intent,
343 // hence delete the FETCHED Intent, and install the
344 // IN-MEMORY Intent.
345 //
346 deleteIntents.add(Pair.of(prefix, fetchedIntent));
347 addIntents.add(Pair.of(prefix, inMemoryIntent));
348 }
349 fetchedIntents.remove(prefix);
350 }
351
352 //
353 // Any remaining FETCHED Intents have to be deleted/withdrawn
354 //
355 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700356 fetchedIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700357 IpPrefix prefix = entry.getKey();
358 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
359 deleteIntents.add(Pair.of(prefix, fetchedIntent));
360 }
361
362 //
363 // Perform the actions:
364 // 1. Store in memory fetched intents that are same. Can be done
365 // even if we are not the leader anymore
366 // 2. Delete intents: check if the leader before each operation
367 // 3. Add intents: check if the leader before each operation
368 //
369 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700370 storeInMemoryIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700371 IpPrefix prefix = pair.getLeft();
372 MultiPointToSinglePointIntent intent = pair.getRight();
373 log.debug("Intent synchronization: updating in-memory " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700374 "Intent for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700375 pushedRouteIntents.put(prefix, intent);
376 }
377 //
378 isActivatedLeader = true; // Allow push of Intents
379 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700380 deleteIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700381 IpPrefix prefix = pair.getLeft();
382 MultiPointToSinglePointIntent intent = pair.getRight();
383 if (!isElectedLeader) {
384 isActivatedLeader = false;
385 return;
386 }
387 log.debug("Intent synchronization: deleting Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700388 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700389 intentService.withdraw(intent);
390 }
391 //
392 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700393 addIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700394 IpPrefix prefix = pair.getLeft();
395 MultiPointToSinglePointIntent intent = pair.getRight();
396 if (!isElectedLeader) {
397 isActivatedLeader = false;
398 return;
399 }
400 log.debug("Intent synchronization: adding Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700401 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700402 intentService.submit(intent);
403 }
404 if (!isElectedLeader) {
405 isActivatedLeader = false;
406 }
407 log.debug("Syncing SDN-IP routes completed.");
408 }
409 }
410
411 /**
412 * Compares two Multi-point to Single Point Intents whether they represent
413 * same logical intention.
414 *
415 * @param intent1 the first Intent to compare
416 * @param intent2 the second Intent to compare
417 * @return true if both Intents represent same logical intention, otherwise
418 * false
419 */
420 private boolean compareMultiPointToSinglePointIntents(
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700421 MultiPointToSinglePointIntent intent1,
422 MultiPointToSinglePointIntent intent2) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700423 /*Match match1 = intent1.getMatch();
424 Match match2 = intent2.getMatch();
425 Action action1 = intent1.getAction();
426 Action action2 = intent2.getAction();
427 Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
428 Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
429 SwitchPort egressPort1 = intent1.getEgressPort();
430 SwitchPort egressPort2 = intent2.getEgressPort();
431
432 return Objects.equal(match1, match2) &&
433 Objects.equal(action1, action2) &&
434 Objects.equal(egressPort1, egressPort2) &&
435 Objects.equal(ingressPorts1, ingressPorts2);*/
436 return Objects.equal(intent1.selector(), intent2.selector()) &&
437 Objects.equal(intent1.treatment(), intent2.treatment()) &&
438 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
439 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
440 }
441
442 /**
443 * Processes adding a route entry.
444 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700445 * Put new route entry into the radix tree. If there was an existing
446 * next hop for this prefix, but the next hop was different, then execute
Jonathan Hart335ef462014-10-16 08:20:46 -0700447 * deleting old route entry. If the next hop is the SDN domain, we do not
448 * handle it at the moment. Otherwise, execute adding a route.
449 *
450 * @param routeEntry the route entry to add
451 */
452 protected void processRouteAdd(RouteEntry routeEntry) {
453 synchronized (this) {
454 log.debug("Processing route add: {}", routeEntry);
455
456 IpPrefix prefix = routeEntry.prefix();
457 IpAddress nextHop = null;
458 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700459 bgpRoutes.put(RouteEntry.createBinaryString(prefix),
460 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700461 if (foundRouteEntry != null) {
462 nextHop = foundRouteEntry.nextHop();
463 }
464
465 if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
466 // There was an existing nexthop for this prefix. This update
467 // supersedes that, so we need to remove the old flows for this
468 // prefix from the switches
469 executeRouteDelete(routeEntry);
470 }
471 if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
472 return;
473 }
474
475 if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
476 // Route originated by SDN domain
477 // We don't handle these at the moment
478 log.debug("Own route {} to {}",
479 routeEntry.prefix(), routeEntry.nextHop());
480 return;
481 }
482
483 executeRouteAdd(routeEntry);
484 }
485 }
486
487 /**
488 * Executes adding a route entry.
489 * <p/>
490 * Find out the egress Interface and MAC address of next hop router for
491 * this route entry. If the MAC address can not be found in ARP cache,
492 * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
493 * new route intent will be created and installed.
494 *
495 * @param routeEntry the route entry to add
496 */
497 private void executeRouteAdd(RouteEntry routeEntry) {
498 log.debug("Executing route add: {}", routeEntry);
499
500 // See if we know the MAC address of the next hop
501 //MacAddress nextHopMacAddress =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700502 //proxyArp.getMacAddress(routeEntry.getNextHop());
Jonathan Hart335ef462014-10-16 08:20:46 -0700503 MacAddress nextHopMacAddress = null;
504 Set<Host> hosts = hostService.getHostsByIp(
505 routeEntry.nextHop().toPrefix());
506 if (!hosts.isEmpty()) {
507 // TODO how to handle if multiple hosts are returned?
508 nextHopMacAddress = hosts.iterator().next().mac();
509 }
510
511 if (nextHopMacAddress == null) {
512 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
513 //proxyArp.sendArpRequest(routeEntry.getNextHop(), this, true);
514 // TODO maybe just do this for every prefix anyway
515 hostService.startMonitoringIp(routeEntry.nextHop());
516 return;
517 }
518
519 addRouteIntentToNextHop(routeEntry.prefix(),
520 routeEntry.nextHop(),
521 nextHopMacAddress);
522 }
523
524 /**
525 * Adds a route intent given a prefix and a next hop IP address. This
526 * method will find the egress interface for the intent.
527 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700528 * @param prefix IP prefix of the route to add
529 * @param nextHopIpAddress IP address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700530 * @param nextHopMacAddress MAC address of the next hop
531 */
532 private void addRouteIntentToNextHop(IpPrefix prefix,
533 IpAddress nextHopIpAddress,
534 MacAddress nextHopMacAddress) {
535
536 // Find the attachment point (egress interface) of the next hop
537 Interface egressInterface;
538 if (configInfoService.getBgpPeers().containsKey(nextHopIpAddress)) {
539 // Route to a peer
540 log.debug("Route to peer {}", nextHopIpAddress);
541 BgpPeer peer =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700542 configInfoService.getBgpPeers().get(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700543 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700544 interfaceService.getInterface(peer.connectPoint());
Jonathan Hart335ef462014-10-16 08:20:46 -0700545 } else {
546 // Route to non-peer
547 log.debug("Route to non-peer {}", nextHopIpAddress);
548 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700549 interfaceService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700550 if (egressInterface == null) {
551 log.warn("No outgoing interface found for {}",
552 nextHopIpAddress);
553 return;
554 }
555 }
556
557 doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
558 }
559
560 /**
561 * Installs a route intent for a prefix.
562 * <p/>
563 * Intent will match dst IP prefix and rewrite dst MAC address at all other
564 * border switches, then forward packets according to dst MAC address.
565 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700566 * @param prefix IP prefix from route
567 * @param egressInterface egress Interface connected to next hop router
Jonathan Hart335ef462014-10-16 08:20:46 -0700568 * @param nextHopMacAddress MAC address of next hop router
569 */
570 private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700571 MacAddress nextHopMacAddress) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700572 log.debug("Adding intent for prefix {}, next hop mac {}",
573 prefix, nextHopMacAddress);
574
575 MultiPointToSinglePointIntent pushedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700576 pushedRouteIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700577
578 // Just for testing.
579 if (pushedIntent != null) {
580 log.error("There should not be a pushed intent: {}", pushedIntent);
581 }
582
583 ConnectPoint egressPort = egressInterface.connectPoint();
584
585 Set<ConnectPoint> ingressPorts = new HashSet<>();
586
587 for (Interface intf : interfaceService.getInterfaces()) {
588 if (!intf.equals(egressInterface)) {
589 ConnectPoint srcPort = intf.connectPoint();
590 ingressPorts.add(srcPort);
591 }
592 }
593
594 // Match the destination IP prefix at the first hop
595 //PacketMatchBuilder builder = new PacketMatchBuilder();
596 //builder.setEtherType(Ethernet.TYPE_IPV4).setDstIpNet(prefix);
597 //PacketMatch packetMatch = builder.build();
598 TrafficSelector selector = DefaultTrafficSelector.builder()
599 .matchEthType(Ethernet.TYPE_IPV4)
600 .matchIPDst(prefix)
601 .build();
602
603 // Rewrite the destination MAC address
604 //ModifyDstMacAction modifyDstMacAction =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700605 //new ModifyDstMacAction(nextHopMacAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700606 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
607 .setEthDst(nextHopMacAddress)
608 .build();
609
610 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700611 new MultiPointToSinglePointIntent(appId, selector, treatment,
612 ingressPorts, egressPort);
Jonathan Hart335ef462014-10-16 08:20:46 -0700613
614 if (isElectedLeader && isActivatedLeader) {
615 log.debug("Intent installation: adding Intent for prefix: {}",
616 prefix);
617 intentService.submit(intent);
618 }
619
620 // Maintain the Intent
621 pushedRouteIntents.put(prefix, intent);
622 }
623
624 /**
625 * Executes deleting a route entry.
626 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700627 * Removes prefix from radix tree, and if successful, then try to delete
628 * the related intent.
Jonathan Hart335ef462014-10-16 08:20:46 -0700629 *
630 * @param routeEntry the route entry to delete
631 */
632 protected void processRouteDelete(RouteEntry routeEntry) {
633 synchronized (this) {
634 log.debug("Processing route delete: {}", routeEntry);
635 IpPrefix prefix = routeEntry.prefix();
636
637 // TODO check the change of logic here - remove doesn't check that
638 // the route entry was what we expected (and we can't do this
639 // concurrently)
640
641 if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
642 //
643 // Only delete flows if an entry was actually removed from the
644 // tree. If no entry was removed, the <prefix, nexthop> wasn't
645 // there so it's probably already been removed and we don't
646 // need to do anything.
647 //
648 executeRouteDelete(routeEntry);
649 }
650
651 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
652 // TODO cancel the request in the ARP manager as well
653 }
654 }
655
656 /**
657 * Executed deleting a route entry.
658 *
659 * @param routeEntry the route entry to delete
660 */
661 private void executeRouteDelete(RouteEntry routeEntry) {
662 log.debug("Executing route delete: {}", routeEntry);
663
664 IpPrefix prefix = routeEntry.prefix();
665
666 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700667 pushedRouteIntents.remove(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700668
669 if (intent == null) {
670 log.debug("There is no intent in pushedRouteIntents to delete " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700671 "for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700672 } else {
673 if (isElectedLeader && isActivatedLeader) {
674 log.debug("Intent installation: deleting Intent for prefix: {}",
675 prefix);
676 intentService.withdraw(intent);
677 }
678 }
679 }
680
681 /**
682 * This method handles the prefixes which are waiting for ARP replies for
683 * MAC addresses of next hops.
684 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700685 * @param ipAddress next hop router IP address, for which we sent ARP
686 * request out
Jonathan Hart335ef462014-10-16 08:20:46 -0700687 * @param macAddress MAC address which is relative to the ipAddress
688 */
689 //@Override
690 // TODO change name
691 public void arpResponse(IpAddress ipAddress, MacAddress macAddress) {
692 log.debug("Received ARP response: {} => {}", ipAddress, macAddress);
693
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700694 // We synchronize on this to prevent changes to the radix tree
695 // while we're pushing intents. If the tree changes, the
696 // tree and intents could get out of sync.
Jonathan Hart335ef462014-10-16 08:20:46 -0700697 synchronized (this) {
698
699 Set<RouteEntry> routesToPush =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700700 routesWaitingOnArp.removeAll(ipAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700701
702 for (RouteEntry routeEntry : routesToPush) {
703 // These will always be adds
704 IpPrefix prefix = routeEntry.prefix();
705 String binaryString = RouteEntry.createBinaryString(prefix);
706 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700707 bgpRoutes.getValueForExactKey(binaryString);
Jonathan Hart335ef462014-10-16 08:20:46 -0700708 if (foundRouteEntry != null &&
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700709 foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700710 log.debug("Pushing prefix {} next hop {}",
711 routeEntry.prefix(), routeEntry.nextHop());
712 // We only push prefix flows if the prefix is still in the
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700713 // radix tree and the next hop is the same as our
Jonathan Hart335ef462014-10-16 08:20:46 -0700714 // update.
715 // The prefix could have been removed while we were waiting
716 // for the ARP, or the next hop could have changed.
717 addRouteIntentToNextHop(prefix, ipAddress, macAddress);
718 } else {
719 log.debug("Received ARP response, but {}/{} is no longer in"
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700720 + " the radix tree", routeEntry.prefix(),
721 routeEntry.nextHop());
Jonathan Hart335ef462014-10-16 08:20:46 -0700722 }
723 }
724 }
725 }
726
727 /**
728 * Gets the SDN-IP routes.
729 *
730 * @return the SDN-IP routes
731 */
732 public Collection<RouteEntry> getRoutes() {
733 Iterator<KeyValuePair<RouteEntry>> it =
734 bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
735
736 List<RouteEntry> routes = new LinkedList<>();
737
738 while (it.hasNext()) {
739 KeyValuePair<RouteEntry> entry = it.next();
740 routes.add(entry.getValue());
741 }
742
743 return routes;
744 }
745
746 /**
Jonathan Hart335ef462014-10-16 08:20:46 -0700747 * Listener for host events.
748 */
749 class InternalHostListener implements HostListener {
750 @Override
751 public void event(HostEvent event) {
752 if (event.type() == HostEvent.Type.HOST_ADDED ||
753 event.type() == HostEvent.Type.HOST_UPDATED) {
754 Host host = event.subject();
755 for (IpPrefix ip : host.ipAddresses()) {
756 arpResponse(ip.toIpAddress(), host.mac());
757 }
758 }
759 }
760 }
761}