blob: 6316b5910a5aa5f9e786fe5a31a3fc03c7cc2a9d [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;
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080056import org.onlab.packet.Ip4Address;
57import org.onlab.packet.Ip4Prefix;
Thomas Vachuskab97cf282014-10-20 23:31:12 -070058import org.onlab.packet.MacAddress;
59import org.slf4j.Logger;
60import org.slf4j.LoggerFactory;
61
Pingping3855f312014-10-22 12:50:37 -070062import com.google.common.base.Objects;
63import com.google.common.collect.HashMultimap;
64import com.google.common.collect.Multimaps;
65import com.google.common.collect.SetMultimap;
66import com.google.common.util.concurrent.ThreadFactoryBuilder;
67import com.googlecode.concurrenttrees.common.KeyValuePair;
68import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
69import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
70import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
Jonathan Hart335ef462014-10-16 08:20:46 -070071
Jonathan Hart335ef462014-10-16 08:20:46 -070072/**
73 * This class processes BGP route update, translates each update into a intent
74 * and submits the intent.
Jonathan Hart335ef462014-10-16 08:20:46 -070075 */
76public class Router implements RouteListener {
77
78 private static final Logger log = LoggerFactory.getLogger(Router.class);
79
Jonathan Hart0b04bed2014-10-16 16:39:19 -070080 // Store all route updates in a radix tree.
81 // The key in this tree is the binary string of prefix of the route.
Jonathan Hart335ef462014-10-16 08:20:46 -070082 private InvertedRadixTree<RouteEntry> bgpRoutes;
83
84 // Stores all incoming route updates in a queue.
85 private BlockingQueue<RouteUpdate> routeUpdates;
86
Pavlin Radoslavov6b570732014-11-06 13:16:45 -080087 // The Ip4Address is the next hop address of each route update.
88 private SetMultimap<Ip4Address, RouteEntry> routesWaitingOnArp;
89 private ConcurrentHashMap<Ip4Prefix, MultiPointToSinglePointIntent> pushedRouteIntents;
Jonathan Hart335ef462014-10-16 08:20:46 -070090
91 private IntentService intentService;
Jonathan Hart335ef462014-10-16 08:20:46 -070092 private HostService hostService;
Jonathan Hart31582d12014-10-22 13:52:41 -070093 private SdnIpConfigService configService;
Jonathan Hart335ef462014-10-16 08:20:46 -070094 private InterfaceService interfaceService;
95
96 private ExecutorService bgpUpdatesExecutor;
97 private ExecutorService bgpIntentsSynchronizerExecutor;
98
Thomas Vachuskab97cf282014-10-20 23:31:12 -070099 private final ApplicationId appId;
Jonathan Hart335ef462014-10-16 08:20:46 -0700100
101 //
102 // State to deal with SDN-IP Leader election and pushing Intents
103 //
104 private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
105 private volatile boolean isElectedLeader = false;
106 private volatile boolean isActivatedLeader = false;
107
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700108 // For routes announced by local BGP daemon in SDN network,
Jonathan Hart335ef462014-10-16 08:20:46 -0700109 // the next hop will be 0.0.0.0.
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800110 public static final Ip4Address LOCAL_NEXT_HOP =
111 Ip4Address.valueOf("0.0.0.0");
Jonathan Hart335ef462014-10-16 08:20:46 -0700112
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(
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800135 HashMultimap.<Ip4Address, RouteEntry>create());
Jonathan Hart335ef462014-10-16 08:20:46 -0700136 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 /**
Jonathan Hart739c8352014-10-29 17:49:26 -0700148 * Starts the router.
Jonathan Hart335ef462014-10-16 08:20:46 -0700149 */
150 public void start() {
Jonathan Hart335ef462014-10-16 08:20:46 -0700151 bgpUpdatesExecutor.execute(new Runnable() {
152 @Override
153 public void run() {
154 doUpdatesThread();
155 }
156 });
157
158 bgpIntentsSynchronizerExecutor.execute(new Runnable() {
159 @Override
160 public void run() {
161 doIntentSynchronizationThread();
162 }
163 });
164 }
165
Jonathan Hart739c8352014-10-29 17:49:26 -0700166 /**
167 * Shuts the router down.
168 */
169 public void shutdown() {
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800170 // Stop all threads
Jonathan Hart739c8352014-10-29 17:49:26 -0700171 bgpUpdatesExecutor.shutdownNow();
172 bgpIntentsSynchronizerExecutor.shutdownNow();
Pavlin Radoslavov7e190942014-11-13 18:50:59 -0800173
174 synchronized (this) {
175 // Cleanup all local state
176 bgpRoutes = new ConcurrentInvertedRadixTree<>(
177 new DefaultByteArrayNodeFactory());
178 routeUpdates.clear();
179 routesWaitingOnArp.clear();
180 pushedRouteIntents.clear();
181
182 //
183 // Withdraw all SDN-IP intents
184 //
185 if (!isElectedLeader) {
186 return; // Nothing to do: not the leader anymore
187 }
188 log.debug("Withdrawing all SDN-IP Route Intents...");
189 for (Intent intent : intentService.getIntents()) {
190 if (!(intent instanceof MultiPointToSinglePointIntent)
191 || !intent.appId().equals(appId)) {
192 continue;
193 }
194 intentService.withdraw(intent);
195 }
196 }
Jonathan Hart739c8352014-10-29 17:49:26 -0700197 }
198
Jonathan Hart335ef462014-10-16 08:20:46 -0700199 //@Override TODO hook this up to something
200 public void leaderChanged(boolean isLeader) {
201 log.debug("Leader changed: {}", isLeader);
202
203 if (!isLeader) {
204 this.isElectedLeader = false;
205 this.isActivatedLeader = false;
206 return; // Nothing to do
207 }
208 this.isActivatedLeader = false;
209 this.isElectedLeader = true;
210
211 //
212 // Tell the Intents Synchronizer thread to start the synchronization
213 //
214 intentsSynchronizerSemaphore.release();
215 }
216
217 @Override
218 public void update(RouteUpdate routeUpdate) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700219 log.debug("Received new route update: {}", routeUpdate);
Jonathan Hart335ef462014-10-16 08:20:46 -0700220
221 try {
222 routeUpdates.put(routeUpdate);
223 } catch (InterruptedException e) {
224 log.debug("Interrupted while putting on routeUpdates queue", e);
225 Thread.currentThread().interrupt();
226 }
227 }
228
229 /**
230 * Thread for Intent Synchronization.
231 */
232 private void doIntentSynchronizationThread() {
233 boolean interrupted = false;
234 try {
235 while (!interrupted) {
236 try {
237 intentsSynchronizerSemaphore.acquire();
238 //
239 // Drain all permits, because a single synchronization is
240 // sufficient.
241 //
242 intentsSynchronizerSemaphore.drainPermits();
243 } catch (InterruptedException e) {
244 log.debug("Interrupted while waiting to become " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700245 "Intent Synchronization leader");
Jonathan Hart335ef462014-10-16 08:20:46 -0700246 interrupted = true;
247 break;
248 }
249 syncIntents();
250 }
251 } finally {
252 if (interrupted) {
253 Thread.currentThread().interrupt();
254 }
255 }
256 }
257
258 /**
259 * Thread for handling route updates.
260 */
261 private void doUpdatesThread() {
262 boolean interrupted = false;
263 try {
264 while (!interrupted) {
265 try {
266 RouteUpdate update = routeUpdates.take();
267 switch (update.type()) {
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700268 case UPDATE:
269 processRouteAdd(update.routeEntry());
270 break;
271 case DELETE:
272 processRouteDelete(update.routeEntry());
273 break;
274 default:
275 log.error("Unknown update Type: {}", update.type());
276 break;
Jonathan Hart335ef462014-10-16 08:20:46 -0700277 }
278 } catch (InterruptedException e) {
279 log.debug("Interrupted while taking from updates queue", e);
280 interrupted = true;
281 } catch (Exception e) {
282 log.debug("exception", e);
283 }
284 }
285 } finally {
286 if (interrupted) {
287 Thread.currentThread().interrupt();
288 }
289 }
290 }
291
292 /**
293 * Performs Intents Synchronization between the internally stored Route
294 * Intents and the installed Route Intents.
295 */
296 private void syncIntents() {
297 synchronized (this) {
298 if (!isElectedLeader) {
299 return; // Nothing to do: not the leader anymore
300 }
301 log.debug("Syncing SDN-IP Route Intents...");
302
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800303 Map<Ip4Prefix, MultiPointToSinglePointIntent> fetchedIntents =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700304 new HashMap<>();
Jonathan Hart335ef462014-10-16 08:20:46 -0700305
306 //
307 // Fetch all intents, and classify the Multi-Point-to-Point Intents
308 // based on the matching prefix.
309 //
310 for (Intent intent : intentService.getIntents()) {
Jonathan Hartec2df012014-10-23 16:40:24 -0700311
312 if (!(intent instanceof MultiPointToSinglePointIntent)
313 || !intent.appId().equals(appId)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700314 continue;
315 }
316 MultiPointToSinglePointIntent mp2pIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700317 (MultiPointToSinglePointIntent) intent;
Jonathan Hartec2df012014-10-23 16:40:24 -0700318
319 Criterion c = mp2pIntent.selector().getCriterion(Type.IPV4_DST);
320 if (c != null && c instanceof IPCriterion) {
321 IPCriterion ipCriterion = (IPCriterion) c;
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800322 Ip4Prefix ip4Prefix = ipCriterion.ip().getIp4Prefix();
323 if (ip4Prefix == null) {
324 // TODO: For now we support only IPv4
325 continue;
326 }
327 fetchedIntents.put(ip4Prefix, mp2pIntent);
Jonathan Hartec2df012014-10-23 16:40:24 -0700328 } else {
329 log.warn("No IPV4_DST criterion found for intent {}",
330 mp2pIntent.id());
Jonathan Hart335ef462014-10-16 08:20:46 -0700331 }
332
333 }
334
335 //
336 // Compare for each prefix the local IN-MEMORY Intents with the
337 // FETCHED Intents:
338 // - If the IN-MEMORY Intent is same as the FETCHED Intent, store
339 // the FETCHED Intent in the local memory (i.e., override the
340 // IN-MEMORY Intent) to preserve the original Intent ID
341 // - if the IN-MEMORY Intent is not same as the FETCHED Intent,
342 // delete the FETCHED Intent, and push/install the IN-MEMORY
343 // Intent.
344 // - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
345 // Intent for same prefix, then push/install the IN-MEMORY
346 // Intent.
347 // - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
348 // Intent for same prefix, then delete/withdraw the FETCHED
349 // Intent.
350 //
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800351 Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700352 storeInMemoryIntents = new LinkedList<>();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800353 Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700354 addIntents = new LinkedList<>();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800355 Collection<Pair<Ip4Prefix, MultiPointToSinglePointIntent>>
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700356 deleteIntents = new LinkedList<>();
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800357 for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700358 pushedRouteIntents.entrySet()) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800359 Ip4Prefix prefix = entry.getKey();
Jonathan Hart335ef462014-10-16 08:20:46 -0700360 MultiPointToSinglePointIntent inMemoryIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700361 entry.getValue();
Jonathan Hart335ef462014-10-16 08:20:46 -0700362 MultiPointToSinglePointIntent fetchedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700363 fetchedIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700364
365 if (fetchedIntent == null) {
366 //
367 // No FETCHED Intent for same prefix: push the IN-MEMORY
368 // Intent.
369 //
370 addIntents.add(Pair.of(prefix, inMemoryIntent));
371 continue;
372 }
373
Jonathan Hartec2df012014-10-23 16:40:24 -0700374 IntentState state = intentService.getIntentState(fetchedIntent.id());
375 if (state == IntentState.WITHDRAWING ||
376 state == IntentState.WITHDRAWN) {
377 // The intent has been withdrawn but according to our route
378 // table it should be installed. We'll reinstall it.
379 addIntents.add(Pair.of(prefix, inMemoryIntent));
380 }
381
Jonathan Hart335ef462014-10-16 08:20:46 -0700382 //
383 // If IN-MEMORY Intent is same as the FETCHED Intent,
384 // store the FETCHED Intent in the local memory.
385 //
386 if (compareMultiPointToSinglePointIntents(inMemoryIntent,
387 fetchedIntent)) {
388 storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
389 } else {
390 //
391 // The IN-MEMORY Intent is not same as the FETCHED Intent,
392 // hence delete the FETCHED Intent, and install the
393 // IN-MEMORY Intent.
394 //
395 deleteIntents.add(Pair.of(prefix, fetchedIntent));
396 addIntents.add(Pair.of(prefix, inMemoryIntent));
397 }
398 fetchedIntents.remove(prefix);
399 }
400
401 //
402 // Any remaining FETCHED Intents have to be deleted/withdrawn
403 //
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800404 for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700405 fetchedIntents.entrySet()) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800406 Ip4Prefix prefix = entry.getKey();
Jonathan Hart335ef462014-10-16 08:20:46 -0700407 MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
408 deleteIntents.add(Pair.of(prefix, fetchedIntent));
409 }
410
411 //
412 // Perform the actions:
413 // 1. Store in memory fetched intents that are same. Can be done
414 // even if we are not the leader anymore
415 // 2. Delete intents: check if the leader before each operation
416 // 3. Add intents: check if the leader before each operation
417 //
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800418 for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700419 storeInMemoryIntents) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800420 Ip4Prefix prefix = pair.getLeft();
Jonathan Hart335ef462014-10-16 08:20:46 -0700421 MultiPointToSinglePointIntent intent = pair.getRight();
422 log.debug("Intent synchronization: updating in-memory " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700423 "Intent for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700424 pushedRouteIntents.put(prefix, intent);
425 }
426 //
427 isActivatedLeader = true; // Allow push of Intents
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800428 for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700429 deleteIntents) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800430 Ip4Prefix prefix = pair.getLeft();
Jonathan Hart335ef462014-10-16 08:20:46 -0700431 MultiPointToSinglePointIntent intent = pair.getRight();
432 if (!isElectedLeader) {
433 isActivatedLeader = false;
434 return;
435 }
436 log.debug("Intent synchronization: deleting Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700437 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700438 intentService.withdraw(intent);
439 }
440 //
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800441 for (Pair<Ip4Prefix, MultiPointToSinglePointIntent> pair :
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700442 addIntents) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800443 Ip4Prefix prefix = pair.getLeft();
Jonathan Hart335ef462014-10-16 08:20:46 -0700444 MultiPointToSinglePointIntent intent = pair.getRight();
445 if (!isElectedLeader) {
446 isActivatedLeader = false;
447 return;
448 }
449 log.debug("Intent synchronization: adding Intent for " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700450 "prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700451 intentService.submit(intent);
452 }
453 if (!isElectedLeader) {
454 isActivatedLeader = false;
455 }
456 log.debug("Syncing SDN-IP routes completed.");
457 }
458 }
459
460 /**
461 * Compares two Multi-point to Single Point Intents whether they represent
462 * same logical intention.
463 *
464 * @param intent1 the first Intent to compare
465 * @param intent2 the second Intent to compare
466 * @return true if both Intents represent same logical intention, otherwise
467 * false
468 */
469 private boolean compareMultiPointToSinglePointIntents(
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700470 MultiPointToSinglePointIntent intent1,
471 MultiPointToSinglePointIntent intent2) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700472
Pingpingf5d90932014-10-27 10:50:04 -0700473 return Objects.equal(intent1.appId(), intent2.appId()) &&
474 Objects.equal(intent1.selector(), intent2.selector()) &&
Jonathan Hart335ef462014-10-16 08:20:46 -0700475 Objects.equal(intent1.treatment(), intent2.treatment()) &&
476 Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
477 Objects.equal(intent1.egressPoint(), intent2.egressPoint());
478 }
479
480 /**
481 * Processes adding a route entry.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700482 * <p>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700483 * Put new route entry into the radix tree. If there was an existing
484 * next hop for this prefix, but the next hop was different, then execute
Jonathan Hart335ef462014-10-16 08:20:46 -0700485 * deleting old route entry. If the next hop is the SDN domain, we do not
486 * handle it at the moment. Otherwise, execute adding a route.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700487 * </p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700488 *
489 * @param routeEntry the route entry to add
490 */
491 protected void processRouteAdd(RouteEntry routeEntry) {
492 synchronized (this) {
493 log.debug("Processing route add: {}", routeEntry);
494
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800495 Ip4Prefix prefix = routeEntry.prefix();
496 Ip4Address nextHop = null;
Jonathan Hart335ef462014-10-16 08:20:46 -0700497 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700498 bgpRoutes.put(RouteEntry.createBinaryString(prefix),
499 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700500 if (foundRouteEntry != null) {
501 nextHop = foundRouteEntry.nextHop();
502 }
503
504 if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
505 // There was an existing nexthop for this prefix. This update
506 // supersedes that, so we need to remove the old flows for this
507 // prefix from the switches
508 executeRouteDelete(routeEntry);
509 }
510 if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
511 return;
512 }
513
514 if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
515 // Route originated by SDN domain
516 // We don't handle these at the moment
517 log.debug("Own route {} to {}",
518 routeEntry.prefix(), routeEntry.nextHop());
519 return;
520 }
521
522 executeRouteAdd(routeEntry);
523 }
524 }
525
526 /**
527 * Executes adding a route entry.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700528 * <p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700529 * Find out the egress Interface and MAC address of next hop router for
530 * this route entry. If the MAC address can not be found in ARP cache,
531 * then this prefix will be put in routesWaitingOnArp queue. Otherwise,
532 * new route intent will be created and installed.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700533 * </p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700534 *
535 * @param routeEntry the route entry to add
536 */
537 private void executeRouteAdd(RouteEntry routeEntry) {
538 log.debug("Executing route add: {}", routeEntry);
539
Jonathan Hart31582d12014-10-22 13:52:41 -0700540 // Monitor the IP address so we'll get notified of updates to the MAC
541 // address.
542 hostService.startMonitoringIp(routeEntry.nextHop());
543
Jonathan Hart335ef462014-10-16 08:20:46 -0700544 // See if we know the MAC address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700545 MacAddress nextHopMacAddress = null;
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700546 Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
Jonathan Hart335ef462014-10-16 08:20:46 -0700547 if (!hosts.isEmpty()) {
548 // TODO how to handle if multiple hosts are returned?
549 nextHopMacAddress = hosts.iterator().next().mac();
550 }
551
552 if (nextHopMacAddress == null) {
553 routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700554 return;
555 }
556
557 addRouteIntentToNextHop(routeEntry.prefix(),
558 routeEntry.nextHop(),
559 nextHopMacAddress);
560 }
561
562 /**
563 * Adds a route intent given a prefix and a next hop IP address. This
564 * method will find the egress interface for the intent.
565 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700566 * @param prefix IP prefix of the route to add
567 * @param nextHopIpAddress IP address of the next hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700568 * @param nextHopMacAddress MAC address of the next hop
569 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800570 private void addRouteIntentToNextHop(Ip4Prefix prefix,
571 Ip4Address nextHopIpAddress,
Jonathan Hart335ef462014-10-16 08:20:46 -0700572 MacAddress nextHopMacAddress) {
573
574 // Find the attachment point (egress interface) of the next hop
575 Interface egressInterface;
Jonathan Hart31582d12014-10-22 13:52:41 -0700576 if (configService.getBgpPeers().containsKey(nextHopIpAddress)) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700577 // Route to a peer
578 log.debug("Route to peer {}", nextHopIpAddress);
579 BgpPeer peer =
Jonathan Hart31582d12014-10-22 13:52:41 -0700580 configService.getBgpPeers().get(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700581 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700582 interfaceService.getInterface(peer.connectPoint());
Jonathan Hart335ef462014-10-16 08:20:46 -0700583 } else {
584 // Route to non-peer
585 log.debug("Route to non-peer {}", nextHopIpAddress);
586 egressInterface =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700587 interfaceService.getMatchingInterface(nextHopIpAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700588 if (egressInterface == null) {
589 log.warn("No outgoing interface found for {}",
590 nextHopIpAddress);
591 return;
592 }
593 }
594
595 doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
596 }
597
598 /**
599 * Installs a route intent for a prefix.
600 * <p/>
601 * Intent will match dst IP prefix and rewrite dst MAC address at all other
602 * border switches, then forward packets according to dst MAC address.
603 *
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700604 * @param prefix IP prefix from route
605 * @param egressInterface egress Interface connected to next hop router
Jonathan Hart335ef462014-10-16 08:20:46 -0700606 * @param nextHopMacAddress MAC address of next hop router
607 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800608 private void doAddRouteIntent(Ip4Prefix prefix, Interface egressInterface,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700609 MacAddress nextHopMacAddress) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700610 log.debug("Adding intent for prefix {}, next hop mac {}",
611 prefix, nextHopMacAddress);
612
613 MultiPointToSinglePointIntent pushedIntent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700614 pushedRouteIntents.get(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700615
616 // Just for testing.
617 if (pushedIntent != null) {
618 log.error("There should not be a pushed intent: {}", pushedIntent);
619 }
620
621 ConnectPoint egressPort = egressInterface.connectPoint();
622
623 Set<ConnectPoint> ingressPorts = new HashSet<>();
624
625 for (Interface intf : interfaceService.getInterfaces()) {
Jonathan Hart2e3eef32014-11-12 11:05:40 -0800626 if (!intf.connectPoint().equals(egressInterface.connectPoint())) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700627 ConnectPoint srcPort = intf.connectPoint();
628 ingressPorts.add(srcPort);
629 }
630 }
631
632 // Match the destination IP prefix at the first hop
Jonathan Hart335ef462014-10-16 08:20:46 -0700633 TrafficSelector selector = DefaultTrafficSelector.builder()
634 .matchEthType(Ethernet.TYPE_IPV4)
635 .matchIPDst(prefix)
636 .build();
637
638 // Rewrite the destination MAC address
Jonathan Hart335ef462014-10-16 08:20:46 -0700639 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
640 .setEthDst(nextHopMacAddress)
641 .build();
642
643 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700644 new MultiPointToSinglePointIntent(appId, selector, treatment,
645 ingressPorts, egressPort);
Jonathan Hart335ef462014-10-16 08:20:46 -0700646
647 if (isElectedLeader && isActivatedLeader) {
648 log.debug("Intent installation: adding Intent for prefix: {}",
649 prefix);
650 intentService.submit(intent);
651 }
652
653 // Maintain the Intent
654 pushedRouteIntents.put(prefix, intent);
655 }
656
657 /**
658 * Executes deleting a route entry.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700659 * <p>
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700660 * Removes prefix from radix tree, and if successful, then try to delete
661 * the related intent.
Thomas Vachuska4b420772014-10-30 16:46:17 -0700662 * </p>
Jonathan Hart335ef462014-10-16 08:20:46 -0700663 *
664 * @param routeEntry the route entry to delete
665 */
666 protected void processRouteDelete(RouteEntry routeEntry) {
667 synchronized (this) {
668 log.debug("Processing route delete: {}", routeEntry);
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800669 Ip4Prefix prefix = routeEntry.prefix();
Jonathan Hart335ef462014-10-16 08:20:46 -0700670
Jonathan Hart335ef462014-10-16 08:20:46 -0700671 if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
672 //
673 // Only delete flows if an entry was actually removed from the
674 // tree. If no entry was removed, the <prefix, nexthop> wasn't
675 // there so it's probably already been removed and we don't
676 // need to do anything.
677 //
678 executeRouteDelete(routeEntry);
679 }
680
681 routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
682 // TODO cancel the request in the ARP manager as well
683 }
684 }
685
686 /**
687 * Executed deleting a route entry.
688 *
689 * @param routeEntry the route entry to delete
690 */
691 private void executeRouteDelete(RouteEntry routeEntry) {
692 log.debug("Executing route delete: {}", routeEntry);
693
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800694 Ip4Prefix prefix = routeEntry.prefix();
Jonathan Hart335ef462014-10-16 08:20:46 -0700695
696 MultiPointToSinglePointIntent intent =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700697 pushedRouteIntents.remove(prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700698
699 if (intent == null) {
700 log.debug("There is no intent in pushedRouteIntents to delete " +
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700701 "for prefix: {}", prefix);
Jonathan Hart335ef462014-10-16 08:20:46 -0700702 } else {
703 if (isElectedLeader && isActivatedLeader) {
704 log.debug("Intent installation: deleting Intent for prefix: {}",
705 prefix);
706 intentService.withdraw(intent);
707 }
708 }
709 }
710
711 /**
Jonathan Hart31582d12014-10-22 13:52:41 -0700712 * Signals the Router that the MAC to IP mapping has potentially been
713 * updated. This has the effect of updating the MAC address for any
714 * installed prefixes if it has changed, as well as installing any pending
715 * prefixes that were waiting for MAC resolution.
Jonathan Hart335ef462014-10-16 08:20:46 -0700716 *
Jonathan Hart31582d12014-10-22 13:52:41 -0700717 * @param ipAddress the IP address that an event was received for
718 * @param macAddress the most recently known MAC address for the IP address
Jonathan Hart335ef462014-10-16 08:20:46 -0700719 */
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800720 private void updateMac(Ip4Address ipAddress, MacAddress macAddress) {
Jonathan Hart31582d12014-10-22 13:52:41 -0700721 log.debug("Received updated MAC info: {} => {}", ipAddress, macAddress);
722
723 // TODO here we should check whether the next hop for any of our
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800724 // installed prefixes has changed, not just prefixes pending
725 // installation.
Jonathan Hart335ef462014-10-16 08:20:46 -0700726
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700727 // We synchronize on this to prevent changes to the radix tree
728 // while we're pushing intents. If the tree changes, the
729 // tree and intents could get out of sync.
Jonathan Hart335ef462014-10-16 08:20:46 -0700730 synchronized (this) {
731
732 Set<RouteEntry> routesToPush =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700733 routesWaitingOnArp.removeAll(ipAddress);
Jonathan Hart335ef462014-10-16 08:20:46 -0700734
735 for (RouteEntry routeEntry : routesToPush) {
736 // These will always be adds
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800737 Ip4Prefix prefix = routeEntry.prefix();
Jonathan Hart335ef462014-10-16 08:20:46 -0700738 String binaryString = RouteEntry.createBinaryString(prefix);
739 RouteEntry foundRouteEntry =
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700740 bgpRoutes.getValueForExactKey(binaryString);
Jonathan Hart335ef462014-10-16 08:20:46 -0700741 if (foundRouteEntry != null &&
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700742 foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
Jonathan Hart335ef462014-10-16 08:20:46 -0700743 // We only push prefix flows if the prefix is still in the
Jonathan Hart0b04bed2014-10-16 16:39:19 -0700744 // radix tree and the next hop is the same as our
Jonathan Hart335ef462014-10-16 08:20:46 -0700745 // update.
746 // The prefix could have been removed while we were waiting
747 // for the ARP, or the next hop could have changed.
748 addRouteIntentToNextHop(prefix, ipAddress, macAddress);
749 } else {
Jonathan Hart31582d12014-10-22 13:52:41 -0700750 log.debug("{} has been revoked before the MAC was resolved",
751 routeEntry);
Jonathan Hart335ef462014-10-16 08:20:46 -0700752 }
753 }
754 }
755 }
756
757 /**
758 * Gets the SDN-IP routes.
759 *
760 * @return the SDN-IP routes
761 */
762 public Collection<RouteEntry> getRoutes() {
763 Iterator<KeyValuePair<RouteEntry>> it =
764 bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
765
766 List<RouteEntry> routes = new LinkedList<>();
767
768 while (it.hasNext()) {
769 KeyValuePair<RouteEntry> entry = it.next();
770 routes.add(entry.getValue());
771 }
772
773 return routes;
774 }
775
776 /**
Pingping3855f312014-10-22 12:50:37 -0700777 * Gets the pushed route intents.
778 *
779 * @return the pushed route intents
780 */
781 public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
782 List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
783
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800784 for (Map.Entry<Ip4Prefix, MultiPointToSinglePointIntent> entry :
Pingping3855f312014-10-22 12:50:37 -0700785 pushedRouteIntents.entrySet()) {
786 pushedIntents.add(entry.getValue());
787 }
788 return pushedIntents;
789 }
790
791 /**
Jonathan Hart335ef462014-10-16 08:20:46 -0700792 * Listener for host events.
793 */
794 class InternalHostListener implements HostListener {
795 @Override
796 public void event(HostEvent event) {
797 if (event.type() == HostEvent.Type.HOST_ADDED ||
798 event.type() == HostEvent.Type.HOST_UPDATED) {
799 Host host = event.subject();
Pavlin Radoslavov33f228a2014-10-27 19:33:16 -0700800 for (IpAddress ip : host.ipAddresses()) {
Pavlin Radoslavov6b570732014-11-06 13:16:45 -0800801 Ip4Address ip4Address = ip.getIp4Address();
802 if (ip4Address == null) {
803 // TODO: For now we support only IPv4
804 continue;
805 }
806 updateMac(ip4Address, host.mac());
Jonathan Hart335ef462014-10-16 08:20:46 -0700807 }
808 }
809 }
810 }
811}