blob: 1a9d64fc6449c187dc58f2526ebfcffa23421eac [file] [log] [blame]
Lee Yongjae7c27bb42017-11-17 12:00:45 +09001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * 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
7 *
8 * 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.
15 */
16package org.onosproject.simplefabric;
17
18import com.google.common.collect.ImmutableList;
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Reference;
23import org.apache.felix.scr.annotations.ReferenceCardinality;
24import org.onlab.packet.EthType;
25import org.onlab.packet.Ethernet;
26import org.onlab.packet.ICMP;
27import org.onlab.packet.ICMP6;
28import org.onlab.packet.IPv4;
29import org.onlab.packet.IPv6;
30import org.onlab.packet.IpAddress;
31import org.onlab.packet.IpPrefix;
32import org.onlab.packet.Ip4Address;
33import org.onlab.packet.Ip4Prefix;
34import org.onlab.packet.Ip6Address;
35import org.onlab.packet.Ip6Prefix;
36import org.onlab.packet.MacAddress;
37import org.onlab.packet.VlanId;
38import org.onosproject.core.ApplicationId;
39import org.onosproject.core.CoreService;
40import org.onosproject.net.intf.InterfaceService;
41import org.onosproject.net.ConnectPoint;
42import org.onosproject.net.EncapsulationType;
43import org.onosproject.net.Device;
44import org.onosproject.net.DeviceId;
45import org.onosproject.net.device.DeviceService;
46import org.onosproject.net.flow.DefaultFlowRule;
47import org.onosproject.net.flow.DefaultTrafficSelector;
48import org.onosproject.net.flow.DefaultTrafficTreatment;
49import org.onosproject.net.flow.TrafficSelector;
50import org.onosproject.net.flow.TrafficTreatment;
51import org.onosproject.net.flow.FlowRule;
52import org.onosproject.net.flow.FlowRuleService;
53import org.onosproject.net.Host;
54import org.onosproject.net.host.HostService;
55import org.onosproject.net.intent.Constraint;
56import org.onosproject.net.intent.constraint.EncapsulationConstraint;
57import org.onosproject.net.intent.constraint.PartialFailureConstraint;
58import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint;
59import org.onosproject.net.intent.Intent;
60import org.onosproject.net.intent.IntentService;
61import org.onosproject.net.intent.Key;
62import org.onosproject.net.intent.MultiPointToSinglePointIntent;
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +090063import org.onosproject.net.intf.Interface;
Lee Yongjae7c27bb42017-11-17 12:00:45 +090064import org.onosproject.net.link.LinkService;
65import org.onosproject.net.packet.DefaultOutboundPacket;
66import org.onosproject.net.packet.InboundPacket;
67import org.onosproject.net.packet.OutboundPacket;
68import org.onosproject.net.packet.PacketContext;
69import org.onosproject.net.packet.PacketPriority;
70import org.onosproject.net.packet.PacketProcessor;
71import org.onosproject.net.packet.PacketService;
Lee Yongjae7c27bb42017-11-17 12:00:45 +090072import org.slf4j.Logger;
73import org.slf4j.LoggerFactory;
74
75import java.io.PrintStream;
76import java.nio.ByteBuffer;
77import java.util.ArrayList;
78import java.util.Collections;
79import java.util.Comparator;
80import java.util.HashSet;
81import java.util.List;
82import java.util.Set;
83
84
85/**
86 * SimpleFabricReactiveRouting handles L3 Reactive Routing.
87 */
88@Component(immediate = true, enabled = false)
89public class SimpleFabricReactiveRouting {
90
91 private final Logger log = LoggerFactory.getLogger(getClass());
92 private ApplicationId reactiveAppId;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected CoreService coreService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected PacketService packetService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected InterfaceService interfaceService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected DeviceService deviceService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 protected LinkService linkService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected HostService hostService;
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected IntentService intentService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected FlowRuleService flowRuleService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 protected SimpleFabricService simpleFabric;
120
121 private ImmutableList<Constraint> reactiveConstraints
122 = ImmutableList.of(new PartialFailureConstraint());
123 //= ImmutableList.of();
124 // NOTE: SHOULD NOT use HashedPathSelectionConstraint
125 // for unpredictable srcCp of Link appears as reactive packet traffic
126
127 private Set<FlowRule> interceptFlowRules = new HashSet<>();
128 private Set<Key> toBePurgedIntentKeys = new HashSet<>();
129 // NOTE: manage purged intents by key for intentService.getIntent() supports key only
130
131 private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
132 private ReactiveRoutingProcessor processor = new ReactiveRoutingProcessor();
133
134 @Activate
135 public void activate() {
136 reactiveAppId = coreService.registerApplication(simpleFabric.REACTIVE_APP_ID);
137 log.info("simple fabric reactive routing starting with app id {}", reactiveAppId.toString());
138
139 // NOTE: may not clear at init for MIGHT generate pending_remove garbages
140 // use flush event from simple fabric cli command
141
142 if (simpleFabric.REACTIVE_HASHED_PATH_SELECTION) {
143 reactiveConstraints = ImmutableList.of(new PartialFailureConstraint(),
144 new HashedPathSelectionConstraint());
145 } else {
146 reactiveConstraints = ImmutableList.of(new PartialFailureConstraint());
147 }
148
149 processor = new ReactiveRoutingProcessor();
150 packetService.addProcessor(processor, PacketProcessor.director(2));
151 simpleFabric.addListener(simpleFabricListener);
152
153 registerIntercepts();
154 refreshIntercepts();
155
156 log.info("simple fabric reactive routing started");
157 }
158
159 @Deactivate
160 public void deactivate() {
161 log.info("simple fabric reactive routing stopping");
162
163 packetService.removeProcessor(processor);
164 simpleFabric.removeListener(simpleFabricListener);
165
166 withdrawIntercepts();
167
168 // NOTE: may not clear at init for MIGHT generate pending_remove garbages
169 // use flush event from simple fabric cli command
170
171 toBePurgedIntentKeys.clear();
172
173 flowRuleService.removeFlowRulesById(reactiveAppId);
174
175 processor = null;
176
177 log.info("simple fabric reactive routing stopped");
178 }
179
180 /**
181 * Request packet in via the PacketService.
182 */
183 private void registerIntercepts() {
184 // register default intercepts on packetService for broder routing intercepts
185
186 packetService.requestPackets(
187 DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV4).build(),
188 PacketPriority.REACTIVE, reactiveAppId);
189
190 if (simpleFabric.ALLOW_IPV6) {
191 packetService.requestPackets(
192 DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
193 PacketPriority.REACTIVE, reactiveAppId);
194 }
195
196 log.info("simple fabric reactive routing ip packet intercepts started");
197 }
198
199 /**
200 * Cancel request for packet in via PacketService.
201 */
202 private void withdrawIntercepts() {
203 // unregister default intercepts on packetService
204
205 packetService.cancelPackets(
206 DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV4).build(),
207 PacketPriority.REACTIVE, reactiveAppId);
208
209 if (simpleFabric.ALLOW_IPV6) {
210 packetService.cancelPackets(
211 DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
212 PacketPriority.REACTIVE, reactiveAppId);
213 }
214
215 log.info("simple fabric reactive routing ip packet intercepts stopped");
216 }
217
218 /**
219 * Refresh device flow rules for reative intercepts on local ipSubnets.
220 */
221 private void refreshIntercepts() {
222 Set<FlowRule> newInterceptFlowRules = new HashSet<>();
223 for (Device device : deviceService.getAvailableDevices()) {
224 for (IpSubnet subnet : simpleFabric.getIpSubnets()) {
225 newInterceptFlowRules.add(generateInterceptFlowRule(true, device.id(), subnet.ipPrefix()));
226 // check if this devices has the ipSubnet, then add ip broadcast flue rule
227 L2Network l2Network = simpleFabric.findL2Network(subnet.l2NetworkName());
228 if (l2Network != null && l2Network.contains(device.id())) {
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900229 newInterceptFlowRules.add(generateLocalSubnetIpBctFlowRule(device.id(), subnet.ipPrefix(),
230 l2Network));
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900231 }
232 // JUST FOR FLOW RULE TEST ONLY
233 //newInterceptFlowRules.add(generateTestFlowRule(device.id(), subnet.ipPrefix()));
234 }
235 for (Route route : simpleFabric.getBorderRoutes()) {
236 newInterceptFlowRules.add(generateInterceptFlowRule(false, device.id(), route.prefix()));
237 }
238 }
239
240 if (!newInterceptFlowRules.equals(interceptFlowRules)) {
241 // NOTE: DO NOT REMOVE INTERCEPT FLOW RULES FOR FAILED DEVICE FLOW UPDATE MIGHT BE BLOCKED
242 /*
243 interceptFlowRules.stream()
244 .filter(rule -> !newInterceptFlowRules.contains(rule))
245 .forEach(rule -> {
246 flowRuleService.removeFlowRules(rule);
247 log.info("simple fabric reactive routing remove intercept flow rule: {}", rule);
248 });
249 */
250 newInterceptFlowRules.stream()
251 .filter(rule -> !interceptFlowRules.contains(rule))
252 .forEach(rule -> {
253 flowRuleService.applyFlowRules(rule);
254 log.info("simple fabric reactive routing apply intercept flow rule: {}", rule);
255 });
256 interceptFlowRules = newInterceptFlowRules;
257 }
258 }
259
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900260 private FlowRule generateInterceptFlowRule(boolean isDstLocalSubnet, DeviceId deviceId, IpPrefix prefix) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900261 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
262 if (prefix.isIp4()) {
263 selector.matchEthType(Ethernet.TYPE_IPV4);
264 if (prefix.prefixLength() > 0) {
265 selector.matchIPDst(prefix);
266 }
267 } else {
268 selector.matchEthType(Ethernet.TYPE_IPV6);
269 if (prefix.prefixLength() > 0) {
270 selector.matchIPv6Dst(prefix);
271 }
272 }
273 FlowRule rule = DefaultFlowRule.builder()
274 .forDevice(deviceId)
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900275 .withPriority(reactivePriority(false, isDstLocalSubnet, prefix.prefixLength()))
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900276 .withSelector(selector.build())
277 .withTreatment(DefaultTrafficTreatment.builder().punt().build())
278 .fromApp(reactiveAppId)
279 .makePermanent()
280 .forTable(0).build();
281 return rule;
282 }
283
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900284 private FlowRule generateLocalSubnetIpBctFlowRule(DeviceId deviceId, IpPrefix prefix, L2Network l2Network) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900285 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
286 IpPrefix bctPrefix;
287 if (prefix.isIp4()) {
288 bctPrefix = Ip4Prefix.valueOf(prefix.getIp4Prefix().address().toInt() |
289 ~Ip4Address.makeMaskPrefix(prefix.prefixLength()).toInt(),
290 Ip4Address.BIT_LENGTH);
291 selector.matchEthType(Ethernet.TYPE_IPV4);
292 selector.matchIPDst(bctPrefix);
293 } else {
294 byte[] p = prefix.getIp6Prefix().address().toOctets();
295 byte[] m = Ip6Address.makeMaskPrefix(prefix.prefixLength()).toOctets();
296 for (int i = 0; i < p.length; i++) {
297 p[i] |= ~m[i];
298 }
299 bctPrefix = Ip6Prefix.valueOf(p, Ip6Address.BIT_LENGTH);
300 selector.matchEthType(Ethernet.TYPE_IPV6);
301 selector.matchIPv6Dst(bctPrefix);
302 }
303 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
304 Set<ConnectPoint> newEgressPoints = new HashSet<>();
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900305 for (Interface iface : l2Network.interfaces()) {
306 if (iface.connectPoint().deviceId().equals(deviceId)) {
307 treatment.setOutput(iface.connectPoint().port());
308 }
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900309 }
310 FlowRule rule = DefaultFlowRule.builder()
311 .forDevice(deviceId)
312 .withPriority(reactivePriority(true, true, bctPrefix.prefixLength()))
313 .withSelector(selector.build())
314 .withTreatment(treatment.build())
315 .fromApp(reactiveAppId)
316 .makePermanent()
317 .forTable(0).build();
318 return rule;
319 }
320
321 /**
322 * Refresh routes by examining network resource status.
323 */
324 private void refreshRouteIntents() {
325 for (Intent entry : intentService.getIntents()) {
326 if (!reactiveAppId.equals(entry.appId())) {
327 continue;
328 }
329
330 MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
331
332 if (!intentService.isLocal(intent.key())) {
333 if (toBePurgedIntentKeys.contains(intent.key())) {
334 toBePurgedIntentKeys.remove(intent.key()); // clear non local intent
335 }
336 continue;
337 }
338
339 try {
340 switch (intentService.getIntentState(intent.key())) {
341 //case FAILED: // failed intent is not auto removed
342 case WITHDRAWN:
343 log.warn("intent found failed or withdrawn; "
344 + "remove and try to purge intent: key={}", intent.key());
345 // purge intents here without withdraw
346 intentService.purge(intentService.getIntent(intent.key()));
347 toBePurgedIntentKeys.add(intent.key());
348 continue;
349 default: // no action
350 break;
351 }
352 } catch (Exception e) {
353 log.warn("intent status lookup failed: error={}", e);
354 continue; // this intent seems invalid; no action
355 }
356
357 // dummy loop to break on remove cases
358 if (!deviceService.isAvailable(intent.egressPoint().deviceId())) {
359 log.info("refresh route intents; remove intent for no device: key={}", intent.key());
360 intentService.withdraw(intentService.getIntent(intent.key()));
361 toBePurgedIntentKeys.add(intent.key());
362 continue;
363 }
364 if (!(simpleFabric.findL2Network(intent.egressPoint(), VlanId.NONE) != null ||
365 (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
366 !linkService.getEgressLinks(intent.egressPoint()).isEmpty()))) {
367 log.info("refresh route intents; remove intent for egress point not available: key={}", intent.key());
368 intentService.withdraw(intentService.getIntent(intent.key()));
369 toBePurgedIntentKeys.add(intent.key());
370 continue;
371 }
372
373 // MAY NEED TO CHECK: intent.egressPoint and intent.treatment's dstMac is valid against hosts
374 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && !simpleFabric.REACTIVE_ALLOW_LINK_CP) {
375 // single path intent only; no need to check ingress points
376 continue;
377 }
378
379 Set<ConnectPoint> newIngressPoints = new HashSet<>();
380 boolean ingressPointChanged = false;
381 for (ConnectPoint cp : intent.ingressPoints()) {
382 if (deviceService.isAvailable(cp.deviceId()) &&
383 (simpleFabric.findL2Network(cp, VlanId.NONE) != null ||
384 (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
385 !linkService.getIngressLinks(cp).isEmpty()))) {
386 newIngressPoints.add(cp);
387 } else {
388 log.info("refresh route ingress cp of "
389 + "not in 2Networks nor links: {}", cp);
390 ingressPointChanged = true;
391 }
392 }
393 if (newIngressPoints.isEmpty()) {
394 log.info("refresh route intents; "
395 + "remove intent for no ingress nor egress point available: key={}", intent.key());
396 intentService.withdraw(intentService.getIntent(intent.key()));
397 toBePurgedIntentKeys.add(intent.key());
398 continue;
399 }
400 // update ingress points
401 if (ingressPointChanged) {
402 MultiPointToSinglePointIntent updatedIntent =
403 MultiPointToSinglePointIntent.builder()
404 .appId(reactiveAppId)
405 .key(intent.key())
406 .selector(intent.selector())
407 .treatment(intent.treatment())
408 .ingressPoints(newIngressPoints)
409 .egressPoint(intent.egressPoint())
410 .priority(intent.priority())
411 .constraints(intent.constraints())
412 .build();
413 log.info("refresh route update intent: key={} updatedIntent={}",
414 intent.key(), updatedIntent);
415 toBePurgedIntentKeys.remove(intent.key()); // may remove from old purged entry
416 intentService.submit(updatedIntent);
417 }
418 }
419 }
420
421 private void checkIntentsPurge() {
422 // check intents to be purge
423 if (!toBePurgedIntentKeys.isEmpty()) {
424 Set<Key> removeKeys = new HashSet<>();
425 for (Key key : toBePurgedIntentKeys) {
426 if (!intentService.isLocal(key)) {
427 removeKeys.add(key);
428 continue;
429 }
430 Intent intentToPurge = intentService.getIntent(key);
431 if (intentToPurge == null) {
432 log.info("purged intent: key={}", key);
433 removeKeys.add(key);
434 } else {
435 switch (intentService.getIntentState(key)) {
436 // case FAILED: // not auto removed
437 case WITHDRAWN:
438 log.info("try to purge intent: key={}", key);
439 intentService.purge(intentToPurge);
440 break;
441 case INSTALL_REQ:
442 case INSTALLED:
443 case INSTALLING:
444 case RECOMPILING:
445 case COMPILING:
446 log.warn("not to purge for active intent: key={}", key);
447 removeKeys.add(key);
448 break;
449 case WITHDRAW_REQ:
450 case WITHDRAWING:
451 case PURGE_REQ:
452 case CORRUPT:
453 default:
454 // no action
455 break;
456 }
457 }
458 }
459 toBePurgedIntentKeys.removeAll(removeKeys);
460 }
461 }
462
463 public void withdrawAllReactiveIntents() {
464 // check all intents of this app
465 // NOTE: cli calls are handling within the cli called node only; so should not user inents.isLocal()
466 Set<Intent> myIntents = new HashSet<>();
467 for (Intent intent : intentService.getIntents()) {
468 if (reactiveAppId.equals(intent.appId())) {
469 myIntents.add(intent);
470 }
471 }
472 // withdraw all my intents
473 for (Intent intent : myIntents) {
474 switch (intentService.getIntentState(intent.key())) {
475 case FAILED:
476 intentService.purge(intent);
477 toBePurgedIntentKeys.add(intent.key());
478 break;
479 case WITHDRAWN:
480 intentService.purge(intent);
481 toBePurgedIntentKeys.add(intent.key());
482 break;
483 case INSTALL_REQ:
484 case INSTALLED:
485 case INSTALLING:
486 case RECOMPILING:
487 case COMPILING:
488 intentService.withdraw(intent);
489 toBePurgedIntentKeys.add(intent.key());
490 break;
491 case WITHDRAW_REQ:
492 case WITHDRAWING:
493 toBePurgedIntentKeys.add(intent.key());
494 break;
495 case PURGE_REQ:
496 case CORRUPT:
497 default:
498 // no action
499 break;
500 }
501 }
502 }
503
504 /**
505 * Reactive Packet Handling.
506 */
507 private class ReactiveRoutingProcessor implements PacketProcessor {
508 @Override
509 public void process(PacketContext context) {
510 InboundPacket pkt = context.inPacket();
511 Ethernet ethPkt = pkt.parsed();
512 if (ethPkt == null) {
513 return;
514 }
515 ConnectPoint srcCp = pkt.receivedFrom();
516 IpAddress srcIp;
517 IpAddress dstIp;
518 byte ipProto = 0; /* 0 or tcp, udp */
519
520 switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
521 case IPV4:
522 IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
523 srcIp = IpAddress.valueOf(ipv4Packet.getSourceAddress());
524 dstIp = IpAddress.valueOf(ipv4Packet.getDestinationAddress());
525 ipProto = ipv4Packet.getProtocol();
526 break;
527 case IPV6:
528 IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
529 srcIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getSourceAddress());
530 dstIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getDestinationAddress());
531 ipProto = ipv6Packet.getNextHeader();
532 break;
533 default:
534 return; // ignore unknow ether type packets
535 }
536 if (ipProto != 6 && ipProto != 17) {
537 ipProto = 0; /* handle special for TCP and UDP only */
538 }
539
540 if (!checkVirtualGatewayIpPacket(pkt, srcIp, dstIp)) {
541 ipPacketReactiveProcessor(context, ethPkt, srcCp, srcIp, dstIp, ipProto);
542 // TODO: add ReactiveRouting for dstIp to srcIp with discovered egressCp as srcCp
543 }
544 }
545 }
546
547 /**
548 * handle Packet with dstIp=virtualGatewayIpAddresses.
549 * returns true(handled) or false(not for virtual gateway)
550 */
551 private boolean checkVirtualGatewayIpPacket(InboundPacket pkt, IpAddress srcIp, IpAddress dstIp) {
552 Ethernet ethPkt = pkt.parsed(); // assume valid
553
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900554 MacAddress mac = simpleFabric.findVMacForIp(dstIp);
555 if (mac == null || !simpleFabric.isVMac(ethPkt.getDestinationMAC())) {
556 /* Destination MAC should be any of virtual gateway macs */
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900557 return false;
558 } else if (dstIp.isIp4()) {
559 IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
560 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_ICMP) {
561 ICMP icmpPacket = (ICMP) ipv4Packet.getPayload();
562
563 if (icmpPacket.getIcmpType() == ICMP.TYPE_ECHO_REQUEST) {
564 log.info("IPV4 ICMP ECHO request to virtual gateway: "
565 + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
566 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
567 .setOutput(pkt.receivedFrom().port()).build();
568 OutboundPacket packet =
569 new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
570 ByteBuffer.wrap(icmpPacket.buildIcmpReply(pkt.parsed()).serialize()));
571 packetService.emit(packet);
572 return true;
573 }
574 }
575 log.warn("IPV4 packet to virtual gateway dropped: "
576 + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
577 return true;
578
579 } else if (dstIp.isIp6()) {
580 // TODO: not tested yet (2017-07-20)
581 IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
582 if (ipv6Packet.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
583 ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload();
584
585 if (icmp6Packet.getIcmpType() == ICMP6.ECHO_REQUEST) {
586 log.info("IPV6 ICMP6 ECHO request to virtual gateway: srcIp={} dstIp={} nextHeader={}",
587 srcIp, dstIp, ipv6Packet.getNextHeader());
588 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
589 .setOutput(pkt.receivedFrom().port()).build();
590 OutboundPacket packet =
591 new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
592 ByteBuffer.wrap(icmp6Packet.buildIcmp6Reply(pkt.parsed()).serialize()));
593 packetService.emit(packet);
594 return true;
595 }
596 }
597 log.warn("IPV6 packet to virtual gateway dropped: srcIp={} dstIp={} nextHeader={}",
598 srcIp, dstIp, ipv6Packet.getNextHeader());
599 return true;
600
601 }
602 return false; // unknown traffic
603 }
604
605 /**
606 * Routes packet reactively.
607 */
608 private void ipPacketReactiveProcessor(PacketContext context, Ethernet ethPkt, ConnectPoint srcCp,
609 IpAddress srcIp, IpAddress dstIp, byte ipProto) {
610 /* check reactive handling and forward packet */
611 log.trace("ip packet: srcCp={} srcIp={} dstIp={} ipProto={}",
612 srcCp, srcIp, dstIp, ipProto);
613
614 EncapsulationType encap = EncapsulationType.NONE;
615
616 // prefix and nextHop for local Subnet
617 IpPrefix srcPrefix = srcIp.toIpPrefix();
618 IpPrefix dstPrefix = dstIp.toIpPrefix();
619 IpAddress srcNextHop = srcIp;
620 IpAddress dstNextHop = dstIp;
621 MacAddress treatmentSrcMac = ethPkt.getDestinationMAC();
622 int borderRoutePrefixLength = 0;
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900623 boolean updateMac = simpleFabric.isVMac(ethPkt.getDestinationMAC());
624
625 // check subnet local or route
626 IpSubnet srcSubnet = simpleFabric.findIpSubnet(srcIp);
627 if (srcSubnet == null) {
628 Route route = simpleFabric.findBorderRoute(srcIp);
629 if (route == null) {
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900630 log.warn("unknown srcIp; drop: srcCp={} srcIp={} dstIp={} ipProto={}",
631 srcCp, srcIp, dstIp, ipProto);
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900632 return;
633 }
634 srcPrefix = route.prefix();
635 srcNextHop = route.nextHop();
636 borderRoutePrefixLength = route.prefix().prefixLength();
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900637 }
638 IpSubnet dstSubnet = simpleFabric.findIpSubnet(dstIp);
639 if (dstSubnet == null) {
640 Route route = simpleFabric.findBorderRoute(dstIp);
641 if (route == null) {
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900642 log.warn("unknown dstIp; drop: srcCp={} srcIp={} dstIp={} ipProto={}",
643 srcCp, srcIp, dstIp, ipProto);
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900644 return;
645 }
646 dstPrefix = route.prefix();
647 dstNextHop = route.nextHop();
648 borderRoutePrefixLength = route.prefix().prefixLength();
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900649 }
650
651 if (dstSubnet != null) {
652 // destination is local subnet ip
653 if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR && dstSubnet.equals(srcSubnet)) {
654 // NOTE: if ALLOW_ETH_ADDRESS_SELECTOR=false; l2Forward is always false
655 L2Network l2Network = simpleFabric.findL2Network(dstSubnet.l2NetworkName());
656 treatmentSrcMac = ethPkt.getSourceMAC();
657 if (l2Network != null && l2Network.l2Forward()) {
658 // NOTE: no reactive route action but do forward packet for L2Forward do not handle packet
659 // update mac only if dstMac is virtualGatewayMac, else assume valid mac already for the l2 network
660 log.info("LOCAL FORWARD ONLY: "
661 + "srcCp={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
662 context.inPacket().receivedFrom(),
663 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
664 ethPkt.getVlanID(), ipProto, updateMac);
665 forwardPacketToDstIp(context, dstIp, treatmentSrcMac, updateMac);
666 return;
667 }
668 }
669 encap = dstSubnet.encapsulation();
670 if (encap == EncapsulationType.NONE && srcSubnet != null) {
671 encap = srcSubnet.encapsulation();
672 }
673 } else {
674 // destination is external network
675 if (srcSubnet == null) {
676 // both are externel network
677 log.warn("INVALID PACKET: srcIp and dstIp are both NON-LOCAL: "
678 + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
679 context.inPacket().receivedFrom(),
680 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
681 ethPkt.getVlanID(), ipProto, updateMac);
682 return;
683 }
684 encap = srcSubnet.encapsulation();
685 }
686
687 log.info("REGI AND FORWARD: "
688 + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
689 context.inPacket().receivedFrom(),
690 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
691 ethPkt.getVlanID(), ipProto, updateMac);
692 setUpConnectivity(srcCp, ipProto, srcPrefix, dstPrefix, dstNextHop, treatmentSrcMac, encap, updateMac,
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900693 dstSubnet != null, borderRoutePrefixLength);
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900694 forwardPacketToDstIp(context, dstNextHop, treatmentSrcMac, updateMac);
695 }
696
697 /**
698 * Emits the specified packet onto the network.
699 */
700 private void forwardPacketToDstIp(PacketContext context, IpAddress nextHopIp,
701 MacAddress srcMac, boolean updateMac) {
702 Set<Host> hosts = hostService.getHostsByIp(nextHopIp);
703 Host dstHost;
704 if (!hosts.isEmpty()) {
705 dstHost = hosts.iterator().next();
706 } else {
707 // NOTE: hostService.requestMac(nextHopIp); NOT IMPLEMENTED in ONOS HostManager.java; do it myself
708 log.warn("forward packet nextHopIp host_mac unknown: nextHopIp={}", nextHopIp);
709 hostService.startMonitoringIp(nextHopIp);
710 simpleFabric.requestMac(nextHopIp);
711 // CONSIDER: make flood on all port of the dstHost's L2Network
712 return;
713 }
714 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
715 .setOutput(dstHost.location().port()).build();
716 OutboundPacket outPacket;
717 if (updateMac) {
718 // NOTE: eth address update by treatment is NOT applied, so update mac myself
719 outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
720 ByteBuffer.wrap(context.inPacket().parsed()
721 .setSourceMACAddress(srcMac)
722 .setDestinationMACAddress(dstHost.mac()).serialize()));
723 } else {
724 outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
725 context.inPacket().unparsed());
726 }
727 // be quiet on normal situation
728 log.info("forward packet: nextHopIP={} srcCP={} dstCP={}",
729 nextHopIp, context.inPacket().receivedFrom(), dstHost.location());
730 packetService.emit(outPacket);
731 }
732
733 /**
734 * Update intents for connectivity.
735 *
736 * ToHost: dstPrefix = dstHostIp.toIpPrefix(), nextHopIp = destHostIp
737 * ToInternet: dstPrefix = route.prefix(), nextHopIp = route.nextHopIp
738 * returns intent submited or not
739 */
740 private boolean setUpConnectivity(ConnectPoint srcCp, byte ipProto, IpPrefix srcPrefix, IpPrefix dstPrefix,
741 IpAddress nextHopIp, MacAddress treatmentSrcMac,
742 EncapsulationType encap, boolean updateMac,
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900743 boolean isDstLocalSubnet, int borderRoutePrefixLength) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900744 if (!(simpleFabric.findL2Network(srcCp, VlanId.NONE) != null ||
745 (simpleFabric.REACTIVE_ALLOW_LINK_CP && !linkService.getIngressLinks(srcCp).isEmpty()))) {
746 log.warn("NO REGI for srcCp not in L2Network; srcCp={} srcPrefix={} dstPrefix={} nextHopIp={}",
747 srcCp, srcPrefix, dstPrefix, nextHopIp);
748 return false;
749 }
750
751 MacAddress nextHopMac = null;
752 ConnectPoint egressPoint = null;
753 for (Host host : hostService.getHostsByIp(nextHopIp)) {
754 if (host.mac() != null) {
755 nextHopMac = host.mac();
756 egressPoint = host.location();
757 break;
758 }
759 }
760 if (nextHopMac == null || egressPoint == null) {
761 log.info("NO REGI for unknown nextHop Cp and Mac: srcPrefix={} dstPrefix={} nextHopIp={}",
762 srcPrefix, dstPrefix, nextHopIp);
763 hostService.startMonitoringIp(nextHopIp);
764 simpleFabric.requestMac(nextHopIp);
765 return false;
766 }
767 TrafficTreatment treatment;
768 if (updateMac && simpleFabric.ALLOW_ETH_ADDRESS_SELECTOR) {
769 treatment = generateSetMacTreatment(nextHopMac, treatmentSrcMac);
770 } else {
771 treatment = DefaultTrafficTreatment.builder().build();
772 }
773
774 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
775 if (dstPrefix.isIp4()) {
776 selector.matchEthType(Ethernet.TYPE_IPV4);
777 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
778 selector.matchIPSrc(srcPrefix);
779 }
780 if (dstPrefix.prefixLength() > 0) {
781 selector.matchIPDst(dstPrefix);
782 }
783 if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
784 selector.matchIPProtocol(ipProto);
785 }
786 } else {
787 selector.matchEthType(Ethernet.TYPE_IPV6);
788 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
789 selector.matchIPv6Src(srcPrefix);
790 }
791 if (dstPrefix.prefixLength() > 0) {
792 selector.matchIPv6Dst(dstPrefix);
793 }
794 if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
795 selector.matchIPProtocol(ipProto);
796 }
797 }
798
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900799 Key key;
800 String keyProtoTag = "";
801 if (simpleFabric.REACTIVE_MATCH_IP_PROTO) {
802 keyProtoTag = "-p" + ipProto;
803 }
804 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE) {
805 // allocate intent per (srcPrefix, dstPrefix)
806 key = Key.of(srcPrefix.toString() + "-to-" + dstPrefix.toString() + keyProtoTag, reactiveAppId);
807 } else {
808 // allocate intent per (srcDeviceId, dstPrefix)
809 key = Key.of(srcCp.deviceId().toString() + "-to-" + dstPrefix.toString() + keyProtoTag, reactiveAppId);
810 }
811
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900812 // check and merge already existing ingress points
813 Set<ConnectPoint> ingressPoints = new HashSet<>();
814 MultiPointToSinglePointIntent existingIntent = (MultiPointToSinglePointIntent) intentService.getIntent(key);
815 if (existingIntent != null) {
816 ingressPoints.addAll(existingIntent.ingressPoints());
817 if (!ingressPoints.add(srcCp) // alread exists and dst not changed
818 && egressPoint.equals(existingIntent.egressPoint())
819 && treatment.equals(existingIntent.treatment())) {
820 log.warn("srcCP is already in mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
821 srcPrefix, dstPrefix, srcCp);
822 return false;
823 }
824 log.info("update mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
825 srcPrefix, dstPrefix, srcCp);
826 } else {
827 log.info("create mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
828 srcPrefix, dstPrefix, srcCp);
829 ingressPoints.add(srcCp);
830 }
831
832 // priority for forwarding case
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900833 int priority = reactivePriority(true, isDstLocalSubnet, borderRoutePrefixLength);
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900834
835 MultiPointToSinglePointIntent newIntent = MultiPointToSinglePointIntent.builder()
836 .key(key)
837 .appId(reactiveAppId)
838 .selector(selector.build())
839 .treatment(treatment)
840 .ingressPoints(ingressPoints)
841 .egressPoint(egressPoint)
842 .priority(priority)
843 .constraints(buildConstraints(reactiveConstraints, encap))
844 .build();
845 log.info("submmit mp2p intent: srcPrefix={} dstPrefix={} srcCp={} "
846 + "newIntent={} nextHopIp={} nextHopMac={} priority={}",
847 srcPrefix, dstPrefix, ingressPoints, newIntent, nextHopIp, nextHopMac, priority);
848 toBePurgedIntentKeys.remove(newIntent.key());
849 intentService.submit(newIntent);
850 return true;
851 }
852
853 // generate treatment to target
854 private TrafficTreatment generateSetMacTreatment(MacAddress dstMac, MacAddress srcMac) {
855 return DefaultTrafficTreatment.builder()
856 // NOTE: Cisco Switch requires both src and dst mac set
857 .setEthDst(dstMac)
858 .setEthSrc(srcMac)
859 .build();
860 }
861
862 // monitor border peers for routeService lookup to be effective
863 private void monitorBorderPeers() {
864 for (Route route : simpleFabric.getBorderRoutes()) {
865 hostService.startMonitoringIp(route.nextHop());
866 simpleFabric.requestMac(route.nextHop());
867 }
868 }
869
870 // priority calculator
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900871 private int reactivePriority(boolean isForward, boolean isDstLocalSubnet, int borderRoutePrefixLength) {
872 if (isDstLocalSubnet) { // -> dst:localSubnet
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900873 if (isForward) {
874 return simpleFabric.PRI_REACTIVE_LOCAL_FORWARD;
875 } else { // isInterncept
876 return simpleFabric.PRI_REACTIVE_LOCAL_INTERCEPT;
877 }
Lee Yongjae6dc7e4f2017-12-06 16:17:51 +0900878 } else { // -> dst:boarderRouteNextHop
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900879 int offset;
880 if (isForward) {
881 offset = simpleFabric.PRI_REACTIVE_BORDER_FORWARD;
882 } else { // isIntercept
883 offset = simpleFabric.PRI_REACTIVE_BORDER_INTERCEPT;
884 }
885 return simpleFabric.PRI_REACTIVE_BORDER_BASE
886 + borderRoutePrefixLength * simpleFabric.PRI_REACTIVE_BORDER_STEP + offset;
887 }
888 }
889
890 // constraints generator
891 private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encap) {
892 if (!encap.equals(EncapsulationType.NONE)) {
893 List<Constraint> newConstraints = new ArrayList<>(constraints);
894 constraints.stream()
895 .filter(c -> c instanceof EncapsulationConstraint)
896 .forEach(newConstraints::remove);
897 newConstraints.add(new EncapsulationConstraint(encap));
898 return ImmutableList.copyOf(newConstraints);
899 }
900 return constraints;
901 }
902
903 // Dump Cli Handler
904 private void dump(String subject, PrintStream out) {
Ray Milkey2ff67162018-01-22 10:14:19 -0800905 if ("intents".equals(subject)) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900906 out.println("Reactive Routing Route Intents:\n");
907 for (Intent entry : intentService.getIntents()) {
908 if (reactiveAppId.equals(entry.appId())) {
909 MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
910 out.println(" " + intent.key().toString()
911 + " to " + intent.egressPoint().toString()
912 + " set " + intent.treatment().immediate().toString()
913 + " from " + intent.ingressPoints().toString());
914 }
915 }
916 out.println("");
917
918 out.println("Reactive Routing Intercept Flow Rules:\n");
919 List<FlowRule> rules = new ArrayList(interceptFlowRules);
920 Collections.sort(rules, new Comparator<FlowRule>() {
921 @Override
922 public int compare(FlowRule a, FlowRule b) {
923 int r = a.deviceId().toString().compareTo(b.deviceId().toString());
924 return (r != 0) ? r : Integer.compare(b.priority(), a.priority()); // descending on priority
925 }
926 });
927 for (FlowRule rule : rules) {
928 out.println(" device=" + rule.deviceId().toString()
929 + " priority=" + rule.priority()
930 + " selector=" + rule.selector().criteria().toString());
931 }
932 out.println("");
933 out.println("Reactive Routing Intents to Be Purged:\n");
934 for (Key key: toBePurgedIntentKeys) {
935 out.println(" " + key.toString());
936 }
937 out.println("");
938
Ray Milkey2ff67162018-01-22 10:14:19 -0800939 } else if ("reactive-intents".equals(subject)) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900940 for (Intent entry : intentService.getIntents()) {
941 if (reactiveAppId.equals(entry.appId())) {
942 MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
943 out.println(intent.key().toString()
944 + " to " + intent.egressPoint().toString()
945 + " set " + intent.treatment().immediate().toString()
946 + " from " + intent.ingressPoints().toString());
947 }
948 }
949 }
950 }
951
952 // Listener
953 private class InternalSimpleFabricListener implements SimpleFabricListener {
954 @Override
955 public void event(SimpleFabricEvent event) {
956 switch (event.type()) {
957 case SIMPLE_FABRIC_UPDATED:
958 refreshIntercepts();
959 refreshRouteIntents();
960 checkIntentsPurge();
961 break;
962 case SIMPLE_FABRIC_FLUSH:
963 withdrawAllReactiveIntents();
964 checkIntentsPurge();
965 break;
966 case SIMPLE_FABRIC_IDLE:
967 refreshIntercepts();
968 refreshRouteIntents();
969 checkIntentsPurge();
970 monitorBorderPeers();
971 break;
972 case SIMPLE_FABRIC_DUMP:
973 dump(event.subject(), event.out());
974 break;
975 default:
976 break;
977 }
978 }
979 }
980
981}
982