blob: f1a14e79771cbf2f90618c1b1bcec2692921e11e [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
Jonathan Hart335ef462014-10-16 08:20:46 -070016package org.onlab.onos.sdnip;
17
Pingping3855f312014-10-22 12:50:37 -070018import java.util.Collection;
19import java.util.HashMap;
20import java.util.HashSet;
21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26import java.util.concurrent.BlockingQueue;
27import java.util.concurrent.ConcurrentHashMap;
28import java.util.concurrent.ExecutorService;
29import java.util.concurrent.Executors;
30import java.util.concurrent.LinkedBlockingQueue;
31import java.util.concurrent.Semaphore;
32
Thomas Vachuskab97cf282014-10-20 23:31:12 -070033import org.apache.commons.lang3.tuple.Pair;
Thomas Vachuskae0f804a2014-10-27 23:40:48 -070034import org.onlab.onos.core.ApplicationId;
Thomas Vachuskab97cf282014-10-20 23:31:12 -070035import org.onlab.onos.net.ConnectPoint;
36import org.onlab.onos.net.Host;
37import org.onlab.onos.net.flow.DefaultTrafficSelector;
38import org.onlab.onos.net.flow.DefaultTrafficTreatment;
39import org.onlab.onos.net.flow.TrafficSelector;
40import org.onlab.onos.net.flow.TrafficTreatment;
41import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
42import org.onlab.onos.net.flow.criteria.Criterion;
43import org.onlab.onos.net.flow.criteria.Criterion.Type;
44import org.onlab.onos.net.host.HostEvent;
45import org.onlab.onos.net.host.HostListener;
46import org.onlab.onos.net.host.HostService;
47import org.onlab.onos.net.intent.Intent;
48import org.onlab.onos.net.intent.IntentService;
Jonathan Hartec2df012014-10-23 16:40:24 -070049import org.onlab.onos.net.intent.IntentState;
Thomas Vachuskab97cf282014-10-20 23:31:12 -070050import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
51import org.onlab.onos.sdnip.config.BgpPeer;
52import org.onlab.onos.sdnip.config.Interface;
53import org.onlab.onos.sdnip.config.SdnIpConfigService;
54import org.onlab.packet.Ethernet;
55import org.onlab.packet.IpAddress;
56import org.onlab.packet.IpPrefix;
57import org.onlab.packet.MacAddress;
58import org.slf4j.Logger;
59import org.slf4j.LoggerFactory;
60
Pingping3855f312014-10-22 12:50:37 -070061import com.google.common.base.Objects;
62import com.google.common.collect.HashMultimap;
63import com.google.common.collect.Multimaps;
64import com.google.common.collect.SetMultimap;
65import com.google.common.util.concurrent.ThreadFactoryBuilder;
66import com.googlecode.concurrenttrees.common.KeyValuePair;
67import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
68import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
69import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
Jonathan Hart335ef462014-10-16 08:20:46 -070070
Jonathan Hart335ef462014-10-16 08:20:46 -070071/**
72 * This class processes BGP route update, translates each update into a intent
73 * and submits the intent.
Jonathan Hart335ef462014-10-16 08:20:46 -070074 */
75public class Router implements RouteListener {
76
77 private static final Logger log = LoggerFactory.getLogger(Router.class);
78
Jonathan Hart0b04bed2014-10-16 16:39:19 -070079 // Store all route updates in a radix tree.
80 // The key in this tree is the binary string of prefix of the route.
Jonathan Hart335ef462014-10-16 08:20:46 -070081 private InvertedRadixTree<RouteEntry> bgpRoutes;
82
83 // Stores all incoming route updates in a queue.
84 private BlockingQueue<RouteUpdate> routeUpdates;
85
Jonathan Hart31582d12014-10-22 13:52:41 -070086 // The IpAddress is the next hop address of each route update.
Jonathan Hart335ef462014-10-16 08:20:46 -070087 private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
88 private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
89
90 private IntentService intentService;
Jonathan Hart335ef462014-10-16 08:20:46 -070091 private HostService hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -070092 private SdnIpConfigService configService;
Jonathan Hart335ef462014-10-16 08:20:46 -070093 private InterfaceService interfaceService;
94
95 private ExecutorService bgpUpdatesExecutor;
96 private ExecutorService bgpIntentsSynchronizerExecutor;
97
Thomas Vachuskab97cf282014-10-20 23:31:12 -070098 private final ApplicationId appId;
Jonathan Hart335ef462014-10-16 08:20:46 -070099
100 //
101 // State to deal with SDN-IP Leader election and pushing Intents
102 //
103 private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
104 private volatile boolean isElectedLeader = false;
105 private volatile boolean isActivatedLeader = false;
106
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700107 // For routes announced by local BGP daemon in SDN network,
Jonathan Hart335ef462014-10-16 08:20:46 -0700108 // the next hop will be 0.0.0.0.
109 public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
110
111 /**
112 * Class constructor.
113 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700114 * @param appId the application ID
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700115 * @param intentService the intent service
116 * @param hostService the host service
Jonathan Hart31582d12014-10-22 13:52:41 -0700117 * @param configService the configuration service
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700118 * @param interfaceService the interface service
Jonathan Hart335ef462014-10-16 08:20:46 -0700119 */
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700120 public Router(ApplicationId appId, IntentService intentService,
Jonathan Hart31582d12014-10-22 13:52:41 -0700121 HostService hostService, SdnIpConfigService configService,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700122 InterfaceService interfaceService) {
123 this.appId = appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700124 this.intentService = intentService;
125 this.hostService = hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -0700126 this.configService = configService;
Jonathan Hart335ef462014-10-16 08:20:46 -0700127 this.interfaceService = interfaceService;
128
129 bgpRoutes = new ConcurrentInvertedRadixTree<>(
130 new DefaultByteArrayNodeFactory());
131 routeUpdates = new LinkedBlockingQueue<>();
132 routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
133 HashMultimap.<IpAddress, RouteEntry>create());
134 pushedRouteIntents = new ConcurrentHashMap<>();
135
136 bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
137 new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
138 bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
139 new ThreadFactoryBuilder()
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700140 .setNameFormat("bgp-intents-synchronizer-%d").build());
Jonathan Hartab63aac2014-10-16 08:52:55 -0700141
142 this.hostService.addListener(new InternalHostListener());
Jonathan Hart335ef462014-10-16 08:20:46 -0700143 }
144
145 /**
Jonathan Hart739c8352014-10-29 17:49:26 -0700146 * Starts the router.
Jonathan Hart335ef462014-10-16 08:20:46 -0700147 */
148 public void start() {
Jonathan Hart335ef462014-10-16 08:20:46 -0700149 bgpUpdatesExecutor.execute(new Runnable() {
150 @Override
151 public void run() {
152 doUpdatesThread();
153 }
154 });
155
156 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
157 @Override
158 public void run() {
159 doIntentSynchronizationThread();
160 }
161 });
162 }
163
Jonathan Hart739c8352014-10-29 17:49:26 -0700164 /**
165 * Shuts the router down.
166 */
167 public void shutdown() {
168 bgpUpdatesExecutor.shutdownNow();
169 bgpIntentsSynchronizerExecutor.shutdownNow();
170 }
171
Jonathan Hart335ef462014-10-16 08:20:46 -0700172 //@Override TODO hook this up to something
173 public void leaderChanged(boolean isLeader) {
174 log.debug("Leader changed: {}", isLeader);
175
176 if (!isLeader) {
177 this.isElectedLeader = false;
178 this.isActivatedLeader = false;
179 return; // Nothing to do
180 }
181 this.isActivatedLeader = false;
182 this.isElectedLeader = true;
183
184 //
185 // Tell the Intents Synchronizer thread to start the synchronization
186 //
187 intentsSynchronizerSemaphore.release();
188 }
189
190 @Override
191 public void update(RouteUpdate routeUpdate) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700192 log.debug("Received new route update: {}", routeUpdate);
Jonathan Hart335ef462014-10-16 08:20:46 -0700193
194 try {
195 routeUpdates.put(routeUpdate);
196 } catch (InterruptedException e) {
197 log.debug("Interrupted while putting on routeUpdates queue", e);
198 Thread.currentThread().interrupt();
199 }
200 }
201
202 /**
203 * Thread for Intent Synchronization.
204 */
205 private void doIntentSynchronizationThread() {
206 boolean interrupted = false;
207 try {
208 while (!interrupted) {
209 try {
210 intentsSynchronizerSemaphore.acquire();
211 //
212 // Drain all permits, because a single synchronization is
213 // sufficient.
214 //
215 intentsSynchronizerSemaphore.drainPermits();
216 } catch (InterruptedException e) {
217 log.debug("Interrupted while waiting to become " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700218 "Intent Synchronization leader");
Jonathan Hart335ef462014-10-16 08:20:46 -0700219 interrupted = true;
220 break;
221 }
222 syncIntents();
223 }
224 } finally {
225 if (interrupted) {
226 Thread.currentThread().interrupt();
227 }
228 }
229 }
230
231 /**
232 * Thread for handling route updates.
233 */
234 private void doUpdatesThread() {
235 boolean interrupted = false;
236 try {
237 while (!interrupted) {
238 try {
239 RouteUpdate update = routeUpdates.take();
240 switch (update.type()) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700241 case UPDATE:
242 processRouteAdd(update.routeEntry());
243 break;
244 case DELETE:
245 processRouteDelete(update.routeEntry());
246 break;
247 default:
248 log.error("Unknown update Type: {}", update.type());
249 break;
Jonathan Hart335ef462014-10-16 08:20:46 -0700250 }
251 } catch (InterruptedException e) {
252 log.debug("Interrupted while taking from updates queue", e);
253 interrupted = true;
254 } catch (Exception e) {
255 log.debug("exception", e);
256 }
257 }
258 } finally {
259 if (interrupted) {
260 Thread.currentThread().interrupt();
261 }
262 }
263 }
264
265 /**
266 * Performs Intents Synchronization between the internally stored Route
267 * Intents and the installed Route Intents.
268 */
269 private void syncIntents() {
270 synchronized (this) {
271 if (!isElectedLeader) {
272 return; // Nothing to do: not the leader anymore
273 }
274 log.debug("Syncing SDN-IP Route Intents...");
275
276 Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700277 new HashMap<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700278
279 //
280 // Fetch all intents, and classify the Multi-Point-to-Point Intents
281 // based on the matching prefix.
282 //
283 for (Intent intent : intentService.getIntents()) {
Jonathan Hartec2df012014-10-23 16:40:24 -0700284
285 if (!(intent instanceof MultiPointToSinglePointIntent)
286 || !intent.appId().equals(appId)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700287 continue;
288 }
289 MultiPointToSinglePointIntent mp2pIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700290 (MultiPointToSinglePointIntent) intent;
Jonathan Hartec2df012014-10-23 16:40:24 -0700291
292 Criterion c = mp2pIntent.selector().getCriterion(Type.IPV4_DST);
293 if (c != null && c instanceof IPCriterion) {
294 IPCriterion ipCriterion = (IPCriterion) c;
295 fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
296 } else {
297 log.warn("No IPV4_DST criterion found for intent {}",
298 mp2pIntent.id());
Jonathan Hart335ef462014-10-16 08:20:46 -0700299 }
300
301 }
302
303 //
304 // Compare for each prefix the local IN-MEMORY Intents with the
305 // FETCHED Intents:
306 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
307 // the FETCHED Intent in the local memory (i.e., override the
308 // IN-MEMORY Intent) to preserve the original Intent ID
309 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
310 // delete the FETCHED Intent, and push/install the IN-MEMORY
311 // Intent.
312 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
313 // Intent for same prefix, then push/install the IN-MEMORY
314 // Intent.
315 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
316 // Intent for same prefix, then delete/withdraw the FETCHED
317 // Intent.
318 //
319 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700320 storeInMemoryIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700321 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700322 addIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700323 Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700324 deleteIntents = new LinkedList<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700325 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700326 pushedRouteIntents.entrySet()) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700327 IpPrefix prefix = entry.getKey();
328 MultiPointToSinglePointIntent inMemoryIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700329 entry.getValue();
Jonathan Hart335ef462014-10-16 08:20:46 -0700330 MultiPointToSinglePointIntent fetchedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700331 fetchedIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700332
333 if (fetchedIntent == null) {
334 //
335 // No FETCHED Intent for same prefix: push the IN-MEMORY
336 // Intent.
337 //
338 addIntents.add(Pair.of(prefix, inMemoryIntent));
339 continue;
340 }
341
Jonathan Hartec2df012014-10-23 16:40:24 -0700342 IntentState state = intentService.getIntentState(fetchedIntent.id());
343 if (state == IntentState.WITHDRAWING ||
344 state == IntentState.WITHDRAWN) {
345 // The intent has been withdrawn but according to our route
346 // table it should be installed. We'll reinstall it.
347 addIntents.add(Pair.of(prefix, inMemoryIntent));
348 }
349
Jonathan Hart335ef462014-10-16 08:20:46 -0700350 //
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
Pingpingf5d90932014-10-27 10:50:04 -0700441 return Objects.equal(intent1.appId(), intent2.appId()) &&
442 Objects.equal(intent1.selector(), intent2.selector()) &&
Jonathan Hart335ef462014-10-16 08:20:46 -0700443 Objects.equal(intent1.treatment(), intent2.treatment()) &&
444 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
445 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
446 }
447
448 /**
449 * Processes adding a route entry.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700450 * <p>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700451 * Put new route entry into the radix tree. If there was an existing
452 * next hop for this prefix, but the next hop was different, then execute
Jonathan Hart335ef462014-10-16 08:20:46 -0700453 * deleting old route entry. If the next hop is the SDN domain, we do not
454 * handle it at the moment. Otherwise, execute adding a route.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700455 * </p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700456 *
457 * @param routeEntry the route entry to add
458 */
459 protected void processRouteAdd(RouteEntry routeEntry) {
460 synchronized (this) {
461 log.debug("Processing route add: {}", routeEntry);
462
463 IpPrefix prefix = routeEntry.prefix();
464 IpAddress nextHop = null;
465 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700466 bgpRoutes.put(RouteEntry.createBinaryString(prefix),
467 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700468 if (foundRouteEntry != null) {
469 nextHop = foundRouteEntry.nextHop();
470 }
471
472 if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
473 // There was an existing nexthop for this prefix. This update
474 // supersedes that, so we need to remove the old flows for this
475 // prefix from the switches
476 executeRouteDelete(routeEntry);
477 }
478 if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
479 return;
480 }
481
482 if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
483 // Route originated by SDN domain
484 // We don't handle these at the moment
485 log.debug("Own route {} to {}",
486 routeEntry.prefix(), routeEntry.nextHop());
487 return;
488 }
489
490 executeRouteAdd(routeEntry);
491 }
492 }
493
494 /**
495 * Executes adding a route entry.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700496 * <p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700497 * Find out the egress Interface and MAC address of next hop router for
498 * this route entry. If the MAC address can not be found in ARP cache,
499 * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
500 * new route intent will be created and installed.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700501 * </p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700502 *
503 * @param routeEntry the route entry to add
504 */
505 private void executeRouteAdd(RouteEntry routeEntry) {
506 log.debug("Executing route add: {}", routeEntry);
507
Jonathan Hart31582d12014-10-22 13:52:41 -0700508 // Monitor the IP address so we'll get notified of updates to the MAC
509 // address.
510 hostService.startMonitoringIp(routeEntry.nextHop());
511
Jonathan Hart335ef462014-10-16 08:20:46 -0700512 // See if we know the MAC address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700513 MacAddress nextHopMacAddress = null;
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700514 Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
Jonathan Hart335ef462014-10-16 08:20:46 -0700515 if (!hosts.isEmpty()) {
516 // TODO how to handle if multiple hosts are returned?
517 nextHopMacAddress = hosts.iterator().next().mac();
518 }
519
520 if (nextHopMacAddress == null) {
521 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700522 return;
523 }
524
525 addRouteIntentToNextHop(routeEntry.prefix(),
526 routeEntry.nextHop(),
527 nextHopMacAddress);
528 }
529
530 /**
531 * Adds a route intent given a prefix and a next hop IP address. This
532 * method will find the egress interface for the intent.
533 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700534 * @param prefix IP prefix of the route to add
535 * @param nextHopIpAddress IP address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700536 * @param nextHopMacAddress MAC address of the next hop
537 */
538 private void addRouteIntentToNextHop(IpPrefix prefix,
539 IpAddress nextHopIpAddress,
540 MacAddress nextHopMacAddress) {
541
542 // Find the attachment point (egress interface) of the next hop
543 Interface egressInterface;
Jonathan Hart31582d12014-10-22 13:52:41 -0700544 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700545 // Route to a peer
546 log.debug("Route to peer {}", nextHopIpAddress);
547 BgpPeer peer =
Jonathan Hart31582d12014-10-22 13:52:41 -0700548 configService.getBgpPeers().get(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700549 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700550 interfaceService.getInterface(peer.connectPoint());
Jonathan Hart335ef462014-10-16 08:20:46 -0700551 } else {
552 // Route to non-peer
553 log.debug("Route to non-peer {}", nextHopIpAddress);
554 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700555 interfaceService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700556 if (egressInterface == null) {
557 log.warn("No outgoing interface found for {}",
558 nextHopIpAddress);
559 return;
560 }
561 }
562
563 doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
564 }
565
566 /**
567 * Installs a route intent for a prefix.
568 * <p/>
569 * Intent will match dst IP prefix and rewrite dst MAC address at all other
570 * border switches, then forward packets according to dst MAC address.
571 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700572 * @param prefix IP prefix from route
573 * @param egressInterface egress Interface connected to next hop router
Jonathan Hart335ef462014-10-16 08:20:46 -0700574 * @param nextHopMacAddress MAC address of next hop router
575 */
576 private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700577 MacAddress nextHopMacAddress) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700578 log.debug("Adding intent for prefix {}, next hop mac {}",
579 prefix, nextHopMacAddress);
580
581 MultiPointToSinglePointIntent pushedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700582 pushedRouteIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700583
584 // Just for testing.
585 if (pushedIntent != null) {
586 log.error("There should not be a pushed intent: {}", pushedIntent);
587 }
588
589 ConnectPoint egressPort = egressInterface.connectPoint();
590
591 Set<ConnectPoint> ingressPorts = new HashSet<>();
592
593 for (Interface intf : interfaceService.getInterfaces()) {
594 if (!intf.equals(egressInterface)) {
595 ConnectPoint srcPort = intf.connectPoint();
596 ingressPorts.add(srcPort);
597 }
598 }
599
600 // Match the destination IP prefix at the first hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700601 TrafficSelector selector = DefaultTrafficSelector.builder()
602 .matchEthType(Ethernet.TYPE_IPV4)
603 .matchIPDst(prefix)
604 .build();
605
606 // Rewrite the destination MAC address
Jonathan Hart335ef462014-10-16 08:20:46 -0700607 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
608 .setEthDst(nextHopMacAddress)
609 .build();
610
611 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700612 new MultiPointToSinglePointIntent(appId, selector, treatment,
613 ingressPorts, egressPort);
Jonathan Hart335ef462014-10-16 08:20:46 -0700614
615 if (isElectedLeader && isActivatedLeader) {
616 log.debug("Intent installation: adding Intent for prefix: {}",
617 prefix);
618 intentService.submit(intent);
619 }
620
621 // Maintain the Intent
622 pushedRouteIntents.put(prefix, intent);
623 }
624
625 /**
626 * Executes deleting a route entry.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700627 * <p>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700628 * Removes prefix from radix tree, and if successful, then try to delete
629 * the related intent.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700630 * </p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700631 *
632 * @param routeEntry the route entry to delete
633 */
634 protected void processRouteDelete(RouteEntry routeEntry) {
635 synchronized (this) {
636 log.debug("Processing route delete: {}", routeEntry);
637 IpPrefix prefix = routeEntry.prefix();
638
Jonathan Hart335ef462014-10-16 08:20:46 -0700639 if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
640 //
641 // Only delete flows if an entry was actually removed from the
642 // tree. If no entry was removed, the <prefix, nexthop> wasn't
643 // there so it's probably already been removed and we don't
644 // need to do anything.
645 //
646 executeRouteDelete(routeEntry);
647 }
648
649 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
650 // TODO cancel the request in the ARP manager as well
651 }
652 }
653
654 /**
655 * Executed deleting a route entry.
656 *
657 * @param routeEntry the route entry to delete
658 */
659 private void executeRouteDelete(RouteEntry routeEntry) {
660 log.debug("Executing route delete: {}", routeEntry);
661
662 IpPrefix prefix = routeEntry.prefix();
663
664 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700665 pushedRouteIntents.remove(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700666
667 if (intent == null) {
668 log.debug("There is no intent in pushedRouteIntents to delete " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700669 "for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700670 } else {
671 if (isElectedLeader && isActivatedLeader) {
672 log.debug("Intent installation: deleting Intent for prefix: {}",
673 prefix);
674 intentService.withdraw(intent);
675 }
676 }
677 }
678
679 /**
Jonathan Hart31582d12014-10-22 13:52:41 -0700680 * Signals the Router that the MAC to IP mapping has potentially been
681 * updated. This has the effect of updating the MAC address for any
682 * installed prefixes if it has changed, as well as installing any pending
683 * prefixes that were waiting for MAC resolution.
Jonathan Hart335ef462014-10-16 08:20:46 -0700684 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700685 * @param ipAddress the IP address that an event was received for
686 * @param macAddress the most recently known MAC address for the IP address
Jonathan Hart335ef462014-10-16 08:20:46 -0700687 */
Jonathan Hart31582d12014-10-22 13:52:41 -0700688 private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
689 log.debug("Received updated MAC info: {} => {}", ipAddress, macAddress);
690
691 // TODO here we should check whether the next hop for any of our
692 // installed prefixes has changed, not just prefixes pending installation.
Jonathan Hart335ef462014-10-16 08:20:46 -0700693
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 // We only push prefix flows if the prefix is still in the
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700711 // radix tree and the next hop is the same as our
Jonathan Hart335ef462014-10-16 08:20:46 -0700712 // update.
713 // The prefix could have been removed while we were waiting
714 // for the ARP, or the next hop could have changed.
715 addRouteIntentToNextHop(prefix, ipAddress, macAddress);
716 } else {
Jonathan Hart31582d12014-10-22 13:52:41 -0700717 log.debug("{} has been revoked before the MAC was resolved",
718 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700719 }
720 }
721 }
722 }
723
724 /**
725 * Gets the SDN-IP routes.
726 *
727 * @return the SDN-IP routes
728 */
729 public Collection<RouteEntry> getRoutes() {
730 Iterator<KeyValuePair<RouteEntry>> it =
731 bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
732
733 List<RouteEntry> routes = new LinkedList<>();
734
735 while (it.hasNext()) {
736 KeyValuePair<RouteEntry> entry = it.next();
737 routes.add(entry.getValue());
738 }
739
740 return routes;
741 }
742
743 /**
Pingping3855f312014-10-22 12:50:37 -0700744 * Gets the pushed route intents.
745 *
746 * @return the pushed route intents
747 */
748 public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
749 List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
750
751 for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
752 pushedRouteIntents.entrySet()) {
753 pushedIntents.add(entry.getValue());
754 }
755 return pushedIntents;
756 }
757
758 /**
Jonathan Hart335ef462014-10-16 08:20:46 -0700759 * Listener for host events.
760 */
761 class InternalHostListener implements HostListener {
762 @Override
763 public void event(HostEvent event) {
764 if (event.type() == HostEvent.Type.HOST_ADDED ||
765 event.type() == HostEvent.Type.HOST_UPDATED) {
766 Host host = event.subject();
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700767 for (IpAddress ip : host.ipAddresses()) {
768 updateMac(ip, host.mac());
Jonathan Hart335ef462014-10-16 08:20:46 -0700769 }
770 }
771 }
772 }
773}