blob: faf31f6f8fea9d8deb1cde604cec4a8114de32d4 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
Jonathan Hart335ef462014-10-16 08:20:46 -070019package org.onlab.onos.sdnip;
20
Pingping3855f312014-10-22 12:50:37 -070021import java.util.Collection;
22import java.util.HashMap;
23import java.util.HashSet;
24import java.util.Iterator;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29import java.util.concurrent.BlockingQueue;
30import java.util.concurrent.ConcurrentHashMap;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.Executors;
33import java.util.concurrent.LinkedBlockingQueue;
34import java.util.concurrent.Semaphore;
35
Thomas Vachuskab97cf282014-10-20 23:31:12 -070036import org.apache.commons.lang3.tuple.Pair;
Thomas Vachuskae0f804a2014-10-27 23:40:48 -070037import org.onlab.onos.core.ApplicationId;
Thomas Vachuskab97cf282014-10-20 23:31:12 -070038import org.onlab.onos.net.ConnectPoint;
39import org.onlab.onos.net.Host;
40import org.onlab.onos.net.flow.DefaultTrafficSelector;
41import org.onlab.onos.net.flow.DefaultTrafficTreatment;
42import org.onlab.onos.net.flow.TrafficSelector;
43import org.onlab.onos.net.flow.TrafficTreatment;
44import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
45import org.onlab.onos.net.flow.criteria.Criterion;
46import org.onlab.onos.net.flow.criteria.Criterion.Type;
47import org.onlab.onos.net.host.HostEvent;
48import org.onlab.onos.net.host.HostListener;
49import org.onlab.onos.net.host.HostService;
50import org.onlab.onos.net.intent.Intent;
51import org.onlab.onos.net.intent.IntentService;
52import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
53import org.onlab.onos.sdnip.config.BgpPeer;
54import org.onlab.onos.sdnip.config.Interface;
55import org.onlab.onos.sdnip.config.SdnIpConfigService;
56import org.onlab.packet.Ethernet;
57import org.onlab.packet.IpAddress;
58import org.onlab.packet.IpPrefix;
59import org.onlab.packet.MacAddress;
60import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
Pingping3855f312014-10-22 12:50:37 -070063import com.google.common.base.Objects;
64import com.google.common.collect.HashMultimap;
65import com.google.common.collect.Multimaps;
66import com.google.common.collect.SetMultimap;
67import com.google.common.util.concurrent.ThreadFactoryBuilder;
68import com.googlecode.concurrenttrees.common.KeyValuePair;
69import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
70import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
71import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
Jonathan Hart335ef462014-10-16 08:20:46 -070072
Jonathan Hart335ef462014-10-16 08:20:46 -070073/**
74 * This class processes BGP route update, translates each update into a intent
75 * and submits the intent.
Jonathan Hart335ef462014-10-16 08:20:46 -070076 */
77public class Router implements RouteListener {
78
79 private static final Logger log = LoggerFactory.getLogger(Router.class);
80
Jonathan Hart0b04bed2014-10-16 16:39:19 -070081 // Store all route updates in a radix tree.
82 // The key in this tree is the binary string of prefix of the route.
Jonathan Hart335ef462014-10-16 08:20:46 -070083 private InvertedRadixTree<RouteEntry> bgpRoutes;
84
85 // Stores all incoming route updates in a queue.
86 private BlockingQueue<RouteUpdate> routeUpdates;
87
Jonathan Hart31582d12014-10-22 13:52:41 -070088 // The IpAddress is the next hop address of each route update.
Jonathan Hart335ef462014-10-16 08:20:46 -070089 private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
90 private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
91
92 private IntentService intentService;
Jonathan Hart335ef462014-10-16 08:20:46 -070093 private HostService hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -070094 private SdnIpConfigService configService;
Jonathan Hart335ef462014-10-16 08:20:46 -070095 private InterfaceService interfaceService;
96
97 private ExecutorService bgpUpdatesExecutor;
98 private ExecutorService bgpIntentsSynchronizerExecutor;
99
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700100 private final ApplicationId appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700101
102 //
103 // State to deal with SDN-IP Leader election and pushing Intents
104 //
105 private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
106 private volatile boolean isElectedLeader = false;
107 private volatile boolean isActivatedLeader = false;
108
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700109 // For routes announced by local BGP daemon in SDN network,
Jonathan Hart335ef462014-10-16 08:20:46 -0700110 // the next hop will be 0.0.0.0.
111 public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
112
113 /**
114 * Class constructor.
115 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700116 * @param appId the application ID
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700117 * @param intentService the intent service
118 * @param hostService the host service
Jonathan Hart31582d12014-10-22 13:52:41 -0700119 * @param configService the configuration service
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700120 * @param interfaceService the interface service
Jonathan Hart335ef462014-10-16 08:20:46 -0700121 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700122 public Router(ApplicationId appId, IntentService intentService,
Jonathan Hart31582d12014-10-22 13:52:41 -0700123 HostService hostService, SdnIpConfigService configService,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700124 InterfaceService interfaceService) {
125 this.appId = appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700126 this.intentService = intentService;
127 this.hostService = hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -0700128 this.configService = configService;
Jonathan Hart335ef462014-10-16 08:20:46 -0700129 this.interfaceService = interfaceService;
130
131 bgpRoutes = new ConcurrentInvertedRadixTree<>(
132 new DefaultByteArrayNodeFactory());
133 routeUpdates = new LinkedBlockingQueue<>();
134 routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
135 HashMultimap.<IpAddress, RouteEntry>create());
136 pushedRouteIntents = new ConcurrentHashMap<>();
137
138 bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
139 new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
140 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
141 new ThreadFactoryBuilder()
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700142 .setNameFormat("bgp-intents-synchronizer-%d").build());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700143
144 this.hostService.addListener(new InternalHostListener());
Jonathan Hart335ef462014-10-16 08:20:46 -0700145 }
146
147 /**
148 * Starts the Router.
149 */
150 public void start() {
151
Jonathan Hart0e12fad2014-10-17 14:54:58 -0700152 // TODO hack to enable SDN-IP now for testing
153 isElectedLeader = true;
154 isActivatedLeader = true;
155
Jonathan Hart335ef462014-10-16 08:20:46 -0700156 bgpUpdatesExecutor.execute(new Runnable() {
157 @Override
158 public void run() {
159 doUpdatesThread();
160 }
161 });
162
163 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
164 @Override
165 public void run() {
166 doIntentSynchronizationThread();
167 }
168 });
169 }
170
171 //@Override TODO hook this up to something
172 public void leaderChanged(boolean isLeader) {
173 log.debug("Leader changed: {}", isLeader);
174
175 if (!isLeader) {
176 this.isElectedLeader = false;
177 this.isActivatedLeader = false;
178 return; // Nothing to do
179 }
180 this.isActivatedLeader = false;
181 this.isElectedLeader = true;
182
183 //
184 // Tell the Intents Synchronizer thread to start the synchronization
185 //
186 intentsSynchronizerSemaphore.release();
187 }
188
189 @Override
190 public void update(RouteUpdate routeUpdate) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700191 log.debug("Received new route update: {}", routeUpdate);
Jonathan Hart335ef462014-10-16 08:20:46 -0700192
193 try {
194 routeUpdates.put(routeUpdate);
195 } catch (InterruptedException e) {
196 log.debug("Interrupted while putting on routeUpdates queue", e);
197 Thread.currentThread().interrupt();
198 }
199 }
200
201 /**
202 * Thread for Intent Synchronization.
203 */
204 private void doIntentSynchronizationThread() {
205 boolean interrupted = false;
206 try {
207 while (!interrupted) {
208 try {
209 intentsSynchronizerSemaphore.acquire();
210 //
211 // Drain all permits, because a single synchronization is
212 // sufficient.
213 //
214 intentsSynchronizerSemaphore.drainPermits();
215 } catch (InterruptedException e) {
216 log.debug("Interrupted while waiting to become " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700217 "Intent Synchronization leader");
Jonathan Hart335ef462014-10-16 08:20:46 -0700218 interrupted = true;
219 break;
220 }
221 syncIntents();
222 }
223 } finally {
224 if (interrupted) {
225 Thread.currentThread().interrupt();
226 }
227 }
228 }
229
230 /**
231 * Thread for handling route updates.
232 */
233 private void doUpdatesThread() {
234 boolean interrupted = false;
235 try {
236 while (!interrupted) {
237 try {
238 RouteUpdate update = routeUpdates.take();
239 switch (update.type()) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700240 case UPDATE:
241 processRouteAdd(update.routeEntry());
242 break;
243 case DELETE:
244 processRouteDelete(update.routeEntry());
245 break;
246 default:
247 log.error("Unknown update Type: {}", update.type());
248 break;
Jonathan Hart335ef462014-10-16 08:20:46 -0700249 }
250 } catch (InterruptedException e) {
251 log.debug("Interrupted while taking from updates queue", e);
252 interrupted = true;
253 } catch (Exception e) {
254 log.debug("exception", e);
255 }
256 }
257 } finally {
258 if (interrupted) {
259 Thread.currentThread().interrupt();
260 }
261 }
262 }
263
264 /**
265 * Performs Intents Synchronization between the internally stored Route
266 * Intents and the installed Route Intents.
267 */
268 private void syncIntents() {
269 synchronized (this) {
270 if (!isElectedLeader) {
271 return; // Nothing to do: not the leader anymore
272 }
273 log.debug("Syncing SDN-IP Route Intents...");
274
275 Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700276 new HashMap<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700277
278 //
279 // Fetch all intents, and classify the Multi-Point-to-Point Intents
280 // based on the matching prefix.
281 //
282 for (Intent intent : intentService.getIntents()) {
283 //
284 // TODO: Ignore all intents that are not installed by
285 // the SDN-IP application.
286 //
287 if (!(intent instanceof MultiPointToSinglePointIntent)) {
288 continue;
289 }
290 MultiPointToSinglePointIntent mp2pIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700291 (MultiPointToSinglePointIntent) intent;
Jonathan Hart335ef462014-10-16 08:20:46 -0700292 /*Match match = mp2pIntent.getMatch();
293 if (!(match instanceof PacketMatch)) {
294 continue;
295 }
296 PacketMatch packetMatch = (PacketMatch) match;
297 Ip4Prefix prefix = packetMatch.getDstIpAddress();
298 if (prefix == null) {
299 continue;
300 }
301 fetchedIntents.put(prefix, mp2pIntent);*/
302 for (Criterion criterion : mp2pIntent.selector().criteria()) {
303 if (criterion.type() == Type.IPV4_DST) {
304 IPCriterion ipCriterion = (IPCriterion) criterion;
305 fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
306 }
307 }
308
309 }
310
311 //
312 // Compare for each prefix the local IN-MEMORY Intents with the
313 // FETCHED Intents:
314 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
315 // the FETCHED Intent in the local memory (i.e., override the
316 // IN-MEMORY Intent) to preserve the original Intent ID
317 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
318 // delete the FETCHED Intent, and push/install the IN-MEMORY
319 // Intent.
320 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
321 // Intent for same prefix, then push/install the IN-MEMORY
322 // Intent.
323 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
324 // Intent for same prefix, then delete/withdraw the FETCHED
325 // Intent.
326 //
327 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700328 storeInMemoryIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700329 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700330 addIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700331 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700332 deleteIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700333 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700334 pushedRouteIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700335 IpPrefix prefix = entry.getKey();
336 MultiPointToSinglePointIntent inMemoryIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700337 entry.getValue();
Jonathan Hart335ef462014-10-16 08:20:46 -0700338 MultiPointToSinglePointIntent fetchedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700339 fetchedIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700340
341 if (fetchedIntent == null) {
342 //
343 // No FETCHED Intent for same prefix: push the IN-MEMORY
344 // Intent.
345 //
346 addIntents.add(Pair.of(prefix, inMemoryIntent));
347 continue;
348 }
349
350 //
351 // If IN-MEMORY Intent is same as the FETCHED Intent,
352 // store the FETCHED Intent in the local memory.
353 //
354 if (compareMultiPointToSinglePointIntents(inMemoryIntent,
355 fetchedIntent)) {
356 storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
357 } else {
358 //
359 // The IN-MEMORY Intent is not same as the FETCHED Intent,
360 // hence delete the FETCHED Intent, and install the
361 // IN-MEMORY Intent.
362 //
363 deleteIntents.add(Pair.of(prefix, fetchedIntent));
364 addIntents.add(Pair.of(prefix, inMemoryIntent));
365 }
366 fetchedIntents.remove(prefix);
367 }
368
369 //
370 // Any remaining FETCHED Intents have to be deleted/withdrawn
371 //
372 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700373 fetchedIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700374 IpPrefix prefix = entry.getKey();
375 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
376 deleteIntents.add(Pair.of(prefix, fetchedIntent));
377 }
378
379 //
380 // Perform the actions:
381 // 1. Store in memory fetched intents that are same. Can be done
382 // even if we are not the leader anymore
383 // 2. Delete intents: check if the leader before each operation
384 // 3. Add intents: check if the leader before each operation
385 //
386 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700387 storeInMemoryIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700388 IpPrefix prefix = pair.getLeft();
389 MultiPointToSinglePointIntent intent = pair.getRight();
390 log.debug("Intent synchronization: updating in-memory " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700391 "Intent for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700392 pushedRouteIntents.put(prefix, intent);
393 }
394 //
395 isActivatedLeader = true; // Allow push of Intents
396 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700397 deleteIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700398 IpPrefix prefix = pair.getLeft();
399 MultiPointToSinglePointIntent intent = pair.getRight();
400 if (!isElectedLeader) {
401 isActivatedLeader = false;
402 return;
403 }
404 log.debug("Intent synchronization: deleting Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700405 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700406 intentService.withdraw(intent);
407 }
408 //
409 for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700410 addIntents) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700411 IpPrefix prefix = pair.getLeft();
412 MultiPointToSinglePointIntent intent = pair.getRight();
413 if (!isElectedLeader) {
414 isActivatedLeader = false;
415 return;
416 }
417 log.debug("Intent synchronization: adding Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700418 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700419 intentService.submit(intent);
420 }
421 if (!isElectedLeader) {
422 isActivatedLeader = false;
423 }
424 log.debug("Syncing SDN-IP routes completed.");
425 }
426 }
427
428 /**
429 * Compares two Multi-point to Single Point Intents whether they represent
430 * same logical intention.
431 *
432 * @param intent1 the first Intent to compare
433 * @param intent2 the second Intent to compare
434 * @return true if both Intents represent same logical intention, otherwise
435 * false
436 */
437 private boolean compareMultiPointToSinglePointIntents(
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700438 MultiPointToSinglePointIntent intent1,
439 MultiPointToSinglePointIntent intent2) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700440 /*Match match1 = intent1.getMatch();
441 Match match2 = intent2.getMatch();
442 Action action1 = intent1.getAction();
443 Action action2 = intent2.getAction();
444 Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
445 Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
446 SwitchPort egressPort1 = intent1.getEgressPort();
447 SwitchPort egressPort2 = intent2.getEgressPort();
448
449 return Objects.equal(match1, match2) &&
450 Objects.equal(action1, action2) &&
451 Objects.equal(egressPort1, egressPort2) &&
452 Objects.equal(ingressPorts1, ingressPorts2);*/
453 return Objects.equal(intent1.selector(), intent2.selector()) &&
454 Objects.equal(intent1.treatment(), intent2.treatment()) &&
455 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
456 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
457 }
458
459 /**
460 * Processes adding a route entry.
461 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700462 * Put new route entry into the radix tree. If there was an existing
463 * next hop for this prefix, but the next hop was different, then execute
Jonathan Hart335ef462014-10-16 08:20:46 -0700464 * deleting old route entry. If the next hop is the SDN domain, we do not
465 * handle it at the moment. Otherwise, execute adding a route.
466 *
467 * @param routeEntry the route entry to add
468 */
469 protected void processRouteAdd(RouteEntry routeEntry) {
470 synchronized (this) {
471 log.debug("Processing route add: {}", routeEntry);
472
473 IpPrefix prefix = routeEntry.prefix();
474 IpAddress nextHop = null;
475 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700476 bgpRoutes.put(RouteEntry.createBinaryString(prefix),
477 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700478 if (foundRouteEntry != null) {
479 nextHop = foundRouteEntry.nextHop();
480 }
481
482 if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
483 // There was an existing nexthop for this prefix. This update
484 // supersedes that, so we need to remove the old flows for this
485 // prefix from the switches
486 executeRouteDelete(routeEntry);
487 }
488 if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
489 return;
490 }
491
492 if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
493 // Route originated by SDN domain
494 // We don't handle these at the moment
495 log.debug("Own route {} to {}",
496 routeEntry.prefix(), routeEntry.nextHop());
497 return;
498 }
499
500 executeRouteAdd(routeEntry);
501 }
502 }
503
504 /**
505 * Executes adding a route entry.
506 * <p/>
507 * Find out the egress Interface and MAC address of next hop router for
508 * this route entry. If the MAC address can not be found in ARP cache,
509 * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
510 * new route intent will be created and installed.
511 *
512 * @param routeEntry the route entry to add
513 */
514 private void executeRouteAdd(RouteEntry routeEntry) {
515 log.debug("Executing route add: {}", routeEntry);
516
Jonathan Hart31582d12014-10-22 13:52:41 -0700517 // Monitor the IP address so we'll get notified of updates to the MAC
518 // address.
519 hostService.startMonitoringIp(routeEntry.nextHop());
520
Jonathan Hart335ef462014-10-16 08:20:46 -0700521 // See if we know the MAC address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700522 MacAddress nextHopMacAddress = null;
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700523 Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
Jonathan Hart335ef462014-10-16 08:20:46 -0700524 if (!hosts.isEmpty()) {
525 // TODO how to handle if multiple hosts are returned?
526 nextHopMacAddress = hosts.iterator().next().mac();
527 }
528
529 if (nextHopMacAddress == null) {
530 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700531 return;
532 }
533
534 addRouteIntentToNextHop(routeEntry.prefix(),
535 routeEntry.nextHop(),
536 nextHopMacAddress);
537 }
538
539 /**
540 * Adds a route intent given a prefix and a next hop IP address. This
541 * method will find the egress interface for the intent.
542 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700543 * @param prefix IP prefix of the route to add
544 * @param nextHopIpAddress IP address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700545 * @param nextHopMacAddress MAC address of the next hop
546 */
547 private void addRouteIntentToNextHop(IpPrefix prefix,
548 IpAddress nextHopIpAddress,
549 MacAddress nextHopMacAddress) {
550
551 // Find the attachment point (egress interface) of the next hop
552 Interface egressInterface;
Jonathan Hart31582d12014-10-22 13:52:41 -0700553 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700554 // Route to a peer
555 log.debug("Route to peer {}", nextHopIpAddress);
556 BgpPeer peer =
Jonathan Hart31582d12014-10-22 13:52:41 -0700557 configService.getBgpPeers().get(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700558 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700559 interfaceService.getInterface(peer.connectPoint());
Jonathan Hart335ef462014-10-16 08:20:46 -0700560 } else {
561 // Route to non-peer
562 log.debug("Route to non-peer {}", nextHopIpAddress);
563 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700564 interfaceService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700565 if (egressInterface == null) {
566 log.warn("No outgoing interface found for {}",
567 nextHopIpAddress);
568 return;
569 }
570 }
571
572 doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
573 }
574
575 /**
576 * Installs a route intent for a prefix.
577 * <p/>
578 * Intent will match dst IP prefix and rewrite dst MAC address at all other
579 * border switches, then forward packets according to dst MAC address.
580 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700581 * @param prefix IP prefix from route
582 * @param egressInterface egress Interface connected to next hop router
Jonathan Hart335ef462014-10-16 08:20:46 -0700583 * @param nextHopMacAddress MAC address of next hop router
584 */
585 private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700586 MacAddress nextHopMacAddress) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700587 log.debug("Adding intent for prefix {}, next hop mac {}",
588 prefix, nextHopMacAddress);
589
590 MultiPointToSinglePointIntent pushedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700591 pushedRouteIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700592
593 // Just for testing.
594 if (pushedIntent != null) {
595 log.error("There should not be a pushed intent: {}", pushedIntent);
596 }
597
598 ConnectPoint egressPort = egressInterface.connectPoint();
599
600 Set<ConnectPoint> ingressPorts = new HashSet<>();
601
602 for (Interface intf : interfaceService.getInterfaces()) {
603 if (!intf.equals(egressInterface)) {
604 ConnectPoint srcPort = intf.connectPoint();
605 ingressPorts.add(srcPort);
606 }
607 }
608
609 // Match the destination IP prefix at the first hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700610 TrafficSelector selector = DefaultTrafficSelector.builder()
611 .matchEthType(Ethernet.TYPE_IPV4)
612 .matchIPDst(prefix)
613 .build();
614
615 // Rewrite the destination MAC address
Jonathan Hart335ef462014-10-16 08:20:46 -0700616 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
617 .setEthDst(nextHopMacAddress)
618 .build();
619
620 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700621 new MultiPointToSinglePointIntent(appId, selector, treatment,
622 ingressPorts, egressPort);
Jonathan Hart335ef462014-10-16 08:20:46 -0700623
624 if (isElectedLeader && isActivatedLeader) {
625 log.debug("Intent installation: adding Intent for prefix: {}",
626 prefix);
627 intentService.submit(intent);
628 }
629
630 // Maintain the Intent
631 pushedRouteIntents.put(prefix, intent);
632 }
633
634 /**
635 * Executes deleting a route entry.
636 * <p/>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700637 * Removes prefix from radix tree, and if successful, then try to delete
638 * the related intent.
Jonathan Hart335ef462014-10-16 08:20:46 -0700639 *
640 * @param routeEntry the route entry to delete
641 */
642 protected void processRouteDelete(RouteEntry routeEntry) {
643 synchronized (this) {
644 log.debug("Processing route delete: {}", routeEntry);
645 IpPrefix prefix = routeEntry.prefix();
646
Jonathan Hart335ef462014-10-16 08:20:46 -0700647 if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
648 //
649 // Only delete flows if an entry was actually removed from the
650 // tree. If no entry was removed, the <prefix, nexthop> wasn't
651 // there so it's probably already been removed and we don't
652 // need to do anything.
653 //
654 executeRouteDelete(routeEntry);
655 }
656
657 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
658 // TODO cancel the request in the ARP manager as well
659 }
660 }
661
662 /**
663 * Executed deleting a route entry.
664 *
665 * @param routeEntry the route entry to delete
666 */
667 private void executeRouteDelete(RouteEntry routeEntry) {
668 log.debug("Executing route delete: {}", routeEntry);
669
670 IpPrefix prefix = routeEntry.prefix();
671
672 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700673 pushedRouteIntents.remove(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700674
675 if (intent == null) {
676 log.debug("There is no intent in pushedRouteIntents to delete " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700677 "for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700678 } else {
679 if (isElectedLeader && isActivatedLeader) {
680 log.debug("Intent installation: deleting Intent for prefix: {}",
681 prefix);
682 intentService.withdraw(intent);
683 }
684 }
685 }
686
687 /**
Jonathan Hart31582d12014-10-22 13:52:41 -0700688 * Signals the Router that the MAC to IP mapping has potentially been
689 * updated. This has the effect of updating the MAC address for any
690 * installed prefixes if it has changed, as well as installing any pending
691 * prefixes that were waiting for MAC resolution.
Jonathan Hart335ef462014-10-16 08:20:46 -0700692 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700693 * @param ipAddress the IP address that an event was received for
694 * @param macAddress the most recently known MAC address for the IP address
Jonathan Hart335ef462014-10-16 08:20:46 -0700695 */
Jonathan Hart31582d12014-10-22 13:52:41 -0700696 private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
697 log.debug("Received updated MAC info: {} => {}", ipAddress, macAddress);
698
699 // TODO here we should check whether the next hop for any of our
700 // installed prefixes has changed, not just prefixes pending installation.
Jonathan Hart335ef462014-10-16 08:20:46 -0700701
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700702 // We synchronize on this to prevent changes to the radix tree
703 // while we're pushing intents. If the tree changes, the
704 // tree and intents could get out of sync.
Jonathan Hart335ef462014-10-16 08:20:46 -0700705 synchronized (this) {
706
707 Set<RouteEntry> routesToPush =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700708 routesWaitingOnArp.removeAll(ipAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700709
710 for (RouteEntry routeEntry : routesToPush) {
711 // These will always be adds
712 IpPrefix prefix = routeEntry.prefix();
713 String binaryString = RouteEntry.createBinaryString(prefix);
714 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700715 bgpRoutes.getValueForExactKey(binaryString);
Jonathan Hart335ef462014-10-16 08:20:46 -0700716 if (foundRouteEntry != null &&
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700717 foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700718 // We only push prefix flows if the prefix is still in the
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700719 // radix tree and the next hop is the same as our
Jonathan Hart335ef462014-10-16 08:20:46 -0700720 // update.
721 // The prefix could have been removed while we were waiting
722 // for the ARP, or the next hop could have changed.
723 addRouteIntentToNextHop(prefix, ipAddress, macAddress);
724 } else {
Jonathan Hart31582d12014-10-22 13:52:41 -0700725 log.debug("{} has been revoked before the MAC was resolved",
726 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700727 }
728 }
729 }
730 }
731
732 /**
733 * Gets the SDN-IP routes.
734 *
735 * @return the SDN-IP routes
736 */
737 public Collection<RouteEntry> getRoutes() {
738 Iterator<KeyValuePair<RouteEntry>> it =
739 bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
740
741 List<RouteEntry> routes = new LinkedList<>();
742
743 while (it.hasNext()) {
744 KeyValuePair<RouteEntry> entry = it.next();
745 routes.add(entry.getValue());
746 }
747
748 return routes;
749 }
750
751 /**
Pingping3855f312014-10-22 12:50:37 -0700752 * Gets the pushed route intents.
753 *
754 * @return the pushed route intents
755 */
756 public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
757 List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
758
759 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
760 pushedRouteIntents.entrySet()) {
761 pushedIntents.add(entry.getValue());
762 }
763 return pushedIntents;
764 }
765
766 /**
Jonathan Hart335ef462014-10-16 08:20:46 -0700767 * Listener for host events.
768 */
769 class InternalHostListener implements HostListener {
770 @Override
771 public void event(HostEvent event) {
772 if (event.type() == HostEvent.Type.HOST_ADDED ||
773 event.type() == HostEvent.Type.HOST_UPDATED) {
774 Host host = event.subject();
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700775 for (IpAddress ip : host.ipAddresses()) {
776 updateMac(ip, host.mac());
Jonathan Hart335ef462014-10-16 08:20:46 -0700777 }
778 }
779 }
780 }
781}