blob: 4ce04800bd4ea116bab6d60bf6ff2efb2f10b3fb [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;
63import org.onosproject.net.link.LinkService;
64import org.onosproject.net.packet.DefaultOutboundPacket;
65import org.onosproject.net.packet.InboundPacket;
66import org.onosproject.net.packet.OutboundPacket;
67import org.onosproject.net.packet.PacketContext;
68import org.onosproject.net.packet.PacketPriority;
69import org.onosproject.net.packet.PacketProcessor;
70import org.onosproject.net.packet.PacketService;
71import org.onosproject.net.Port;
72import 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())) {
229 newInterceptFlowRules.add(generateLocalSubnetIpBctFlowRule(device.id(), subnet.ipPrefix()));
230 }
231 // JUST FOR FLOW RULE TEST ONLY
232 //newInterceptFlowRules.add(generateTestFlowRule(device.id(), subnet.ipPrefix()));
233 }
234 for (Route route : simpleFabric.getBorderRoutes()) {
235 newInterceptFlowRules.add(generateInterceptFlowRule(false, device.id(), route.prefix()));
236 }
237 }
238
239 if (!newInterceptFlowRules.equals(interceptFlowRules)) {
240 // NOTE: DO NOT REMOVE INTERCEPT FLOW RULES FOR FAILED DEVICE FLOW UPDATE MIGHT BE BLOCKED
241 /*
242 interceptFlowRules.stream()
243 .filter(rule -> !newInterceptFlowRules.contains(rule))
244 .forEach(rule -> {
245 flowRuleService.removeFlowRules(rule);
246 log.info("simple fabric reactive routing remove intercept flow rule: {}", rule);
247 });
248 */
249 newInterceptFlowRules.stream()
250 .filter(rule -> !interceptFlowRules.contains(rule))
251 .forEach(rule -> {
252 flowRuleService.applyFlowRules(rule);
253 log.info("simple fabric reactive routing apply intercept flow rule: {}", rule);
254 });
255 interceptFlowRules = newInterceptFlowRules;
256 }
257 }
258
259 private FlowRule generateInterceptFlowRule(boolean isLocalSubnet, DeviceId deviceId, IpPrefix prefix) {
260 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
261 if (prefix.isIp4()) {
262 selector.matchEthType(Ethernet.TYPE_IPV4);
263 if (prefix.prefixLength() > 0) {
264 selector.matchIPDst(prefix);
265 }
266 } else {
267 selector.matchEthType(Ethernet.TYPE_IPV6);
268 if (prefix.prefixLength() > 0) {
269 selector.matchIPv6Dst(prefix);
270 }
271 }
272 FlowRule rule = DefaultFlowRule.builder()
273 .forDevice(deviceId)
274 .withPriority(reactivePriority(false, isLocalSubnet, prefix.prefixLength()))
275 .withSelector(selector.build())
276 .withTreatment(DefaultTrafficTreatment.builder().punt().build())
277 .fromApp(reactiveAppId)
278 .makePermanent()
279 .forTable(0).build();
280 return rule;
281 }
282
283 private FlowRule generateLocalSubnetIpBctFlowRule(DeviceId deviceId, IpPrefix prefix) {
284 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
285 IpPrefix bctPrefix;
286 if (prefix.isIp4()) {
287 bctPrefix = Ip4Prefix.valueOf(prefix.getIp4Prefix().address().toInt() |
288 ~Ip4Address.makeMaskPrefix(prefix.prefixLength()).toInt(),
289 Ip4Address.BIT_LENGTH);
290 selector.matchEthType(Ethernet.TYPE_IPV4);
291 selector.matchIPDst(bctPrefix);
292 } else {
293 byte[] p = prefix.getIp6Prefix().address().toOctets();
294 byte[] m = Ip6Address.makeMaskPrefix(prefix.prefixLength()).toOctets();
295 for (int i = 0; i < p.length; i++) {
296 p[i] |= ~m[i];
297 }
298 bctPrefix = Ip6Prefix.valueOf(p, Ip6Address.BIT_LENGTH);
299 selector.matchEthType(Ethernet.TYPE_IPV6);
300 selector.matchIPv6Dst(bctPrefix);
301 }
302 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
303 Set<ConnectPoint> newEgressPoints = new HashSet<>();
304 for (Port port : deviceService.getPorts(deviceId)) {
305 treatment.setOutput(port.number());
306 }
307 FlowRule rule = DefaultFlowRule.builder()
308 .forDevice(deviceId)
309 .withPriority(reactivePriority(true, true, bctPrefix.prefixLength()))
310 .withSelector(selector.build())
311 .withTreatment(treatment.build())
312 .fromApp(reactiveAppId)
313 .makePermanent()
314 .forTable(0).build();
315 return rule;
316 }
317
318 /**
319 * Refresh routes by examining network resource status.
320 */
321 private void refreshRouteIntents() {
322 for (Intent entry : intentService.getIntents()) {
323 if (!reactiveAppId.equals(entry.appId())) {
324 continue;
325 }
326
327 MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
328
329 if (!intentService.isLocal(intent.key())) {
330 if (toBePurgedIntentKeys.contains(intent.key())) {
331 toBePurgedIntentKeys.remove(intent.key()); // clear non local intent
332 }
333 continue;
334 }
335
336 try {
337 switch (intentService.getIntentState(intent.key())) {
338 //case FAILED: // failed intent is not auto removed
339 case WITHDRAWN:
340 log.warn("intent found failed or withdrawn; "
341 + "remove and try to purge intent: key={}", intent.key());
342 // purge intents here without withdraw
343 intentService.purge(intentService.getIntent(intent.key()));
344 toBePurgedIntentKeys.add(intent.key());
345 continue;
346 default: // no action
347 break;
348 }
349 } catch (Exception e) {
350 log.warn("intent status lookup failed: error={}", e);
351 continue; // this intent seems invalid; no action
352 }
353
354 // dummy loop to break on remove cases
355 if (!deviceService.isAvailable(intent.egressPoint().deviceId())) {
356 log.info("refresh route intents; remove intent for no device: key={}", intent.key());
357 intentService.withdraw(intentService.getIntent(intent.key()));
358 toBePurgedIntentKeys.add(intent.key());
359 continue;
360 }
361 if (!(simpleFabric.findL2Network(intent.egressPoint(), VlanId.NONE) != null ||
362 (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
363 !linkService.getEgressLinks(intent.egressPoint()).isEmpty()))) {
364 log.info("refresh route intents; remove intent for egress point not available: key={}", intent.key());
365 intentService.withdraw(intentService.getIntent(intent.key()));
366 toBePurgedIntentKeys.add(intent.key());
367 continue;
368 }
369
370 // MAY NEED TO CHECK: intent.egressPoint and intent.treatment's dstMac is valid against hosts
371 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && !simpleFabric.REACTIVE_ALLOW_LINK_CP) {
372 // single path intent only; no need to check ingress points
373 continue;
374 }
375
376 Set<ConnectPoint> newIngressPoints = new HashSet<>();
377 boolean ingressPointChanged = false;
378 for (ConnectPoint cp : intent.ingressPoints()) {
379 if (deviceService.isAvailable(cp.deviceId()) &&
380 (simpleFabric.findL2Network(cp, VlanId.NONE) != null ||
381 (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
382 !linkService.getIngressLinks(cp).isEmpty()))) {
383 newIngressPoints.add(cp);
384 } else {
385 log.info("refresh route ingress cp of "
386 + "not in 2Networks nor links: {}", cp);
387 ingressPointChanged = true;
388 }
389 }
390 if (newIngressPoints.isEmpty()) {
391 log.info("refresh route intents; "
392 + "remove intent for no ingress nor egress point available: key={}", intent.key());
393 intentService.withdraw(intentService.getIntent(intent.key()));
394 toBePurgedIntentKeys.add(intent.key());
395 continue;
396 }
397 // update ingress points
398 if (ingressPointChanged) {
399 MultiPointToSinglePointIntent updatedIntent =
400 MultiPointToSinglePointIntent.builder()
401 .appId(reactiveAppId)
402 .key(intent.key())
403 .selector(intent.selector())
404 .treatment(intent.treatment())
405 .ingressPoints(newIngressPoints)
406 .egressPoint(intent.egressPoint())
407 .priority(intent.priority())
408 .constraints(intent.constraints())
409 .build();
410 log.info("refresh route update intent: key={} updatedIntent={}",
411 intent.key(), updatedIntent);
412 toBePurgedIntentKeys.remove(intent.key()); // may remove from old purged entry
413 intentService.submit(updatedIntent);
414 }
415 }
416 }
417
418 private void checkIntentsPurge() {
419 // check intents to be purge
420 if (!toBePurgedIntentKeys.isEmpty()) {
421 Set<Key> removeKeys = new HashSet<>();
422 for (Key key : toBePurgedIntentKeys) {
423 if (!intentService.isLocal(key)) {
424 removeKeys.add(key);
425 continue;
426 }
427 Intent intentToPurge = intentService.getIntent(key);
428 if (intentToPurge == null) {
429 log.info("purged intent: key={}", key);
430 removeKeys.add(key);
431 } else {
432 switch (intentService.getIntentState(key)) {
433 // case FAILED: // not auto removed
434 case WITHDRAWN:
435 log.info("try to purge intent: key={}", key);
436 intentService.purge(intentToPurge);
437 break;
438 case INSTALL_REQ:
439 case INSTALLED:
440 case INSTALLING:
441 case RECOMPILING:
442 case COMPILING:
443 log.warn("not to purge for active intent: key={}", key);
444 removeKeys.add(key);
445 break;
446 case WITHDRAW_REQ:
447 case WITHDRAWING:
448 case PURGE_REQ:
449 case CORRUPT:
450 default:
451 // no action
452 break;
453 }
454 }
455 }
456 toBePurgedIntentKeys.removeAll(removeKeys);
457 }
458 }
459
460 public void withdrawAllReactiveIntents() {
461 // check all intents of this app
462 // NOTE: cli calls are handling within the cli called node only; so should not user inents.isLocal()
463 Set<Intent> myIntents = new HashSet<>();
464 for (Intent intent : intentService.getIntents()) {
465 if (reactiveAppId.equals(intent.appId())) {
466 myIntents.add(intent);
467 }
468 }
469 // withdraw all my intents
470 for (Intent intent : myIntents) {
471 switch (intentService.getIntentState(intent.key())) {
472 case FAILED:
473 intentService.purge(intent);
474 toBePurgedIntentKeys.add(intent.key());
475 break;
476 case WITHDRAWN:
477 intentService.purge(intent);
478 toBePurgedIntentKeys.add(intent.key());
479 break;
480 case INSTALL_REQ:
481 case INSTALLED:
482 case INSTALLING:
483 case RECOMPILING:
484 case COMPILING:
485 intentService.withdraw(intent);
486 toBePurgedIntentKeys.add(intent.key());
487 break;
488 case WITHDRAW_REQ:
489 case WITHDRAWING:
490 toBePurgedIntentKeys.add(intent.key());
491 break;
492 case PURGE_REQ:
493 case CORRUPT:
494 default:
495 // no action
496 break;
497 }
498 }
499 }
500
501 /**
502 * Reactive Packet Handling.
503 */
504 private class ReactiveRoutingProcessor implements PacketProcessor {
505 @Override
506 public void process(PacketContext context) {
507 InboundPacket pkt = context.inPacket();
508 Ethernet ethPkt = pkt.parsed();
509 if (ethPkt == null) {
510 return;
511 }
512 ConnectPoint srcCp = pkt.receivedFrom();
513 IpAddress srcIp;
514 IpAddress dstIp;
515 byte ipProto = 0; /* 0 or tcp, udp */
516
517 switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
518 case IPV4:
519 IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
520 srcIp = IpAddress.valueOf(ipv4Packet.getSourceAddress());
521 dstIp = IpAddress.valueOf(ipv4Packet.getDestinationAddress());
522 ipProto = ipv4Packet.getProtocol();
523 break;
524 case IPV6:
525 IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
526 srcIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getSourceAddress());
527 dstIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getDestinationAddress());
528 ipProto = ipv6Packet.getNextHeader();
529 break;
530 default:
531 return; // ignore unknow ether type packets
532 }
533 if (ipProto != 6 && ipProto != 17) {
534 ipProto = 0; /* handle special for TCP and UDP only */
535 }
536
537 if (!checkVirtualGatewayIpPacket(pkt, srcIp, dstIp)) {
538 ipPacketReactiveProcessor(context, ethPkt, srcCp, srcIp, dstIp, ipProto);
539 // TODO: add ReactiveRouting for dstIp to srcIp with discovered egressCp as srcCp
540 }
541 }
542 }
543
544 /**
545 * handle Packet with dstIp=virtualGatewayIpAddresses.
546 * returns true(handled) or false(not for virtual gateway)
547 */
548 private boolean checkVirtualGatewayIpPacket(InboundPacket pkt, IpAddress srcIp, IpAddress dstIp) {
549 Ethernet ethPkt = pkt.parsed(); // assume valid
550
551 MacAddress mac = simpleFabric.getVMacForIp(dstIp);
552 if (mac == null || !ethPkt.getDestinationMAC().equals(mac)) {
553 return false;
554 } else if (dstIp.isIp4()) {
555 IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
556 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_ICMP) {
557 ICMP icmpPacket = (ICMP) ipv4Packet.getPayload();
558
559 if (icmpPacket.getIcmpType() == ICMP.TYPE_ECHO_REQUEST) {
560 log.info("IPV4 ICMP ECHO request to virtual gateway: "
561 + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
562 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
563 .setOutput(pkt.receivedFrom().port()).build();
564 OutboundPacket packet =
565 new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
566 ByteBuffer.wrap(icmpPacket.buildIcmpReply(pkt.parsed()).serialize()));
567 packetService.emit(packet);
568 return true;
569 }
570 }
571 log.warn("IPV4 packet to virtual gateway dropped: "
572 + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
573 return true;
574
575 } else if (dstIp.isIp6()) {
576 // TODO: not tested yet (2017-07-20)
577 IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
578 if (ipv6Packet.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
579 ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload();
580
581 if (icmp6Packet.getIcmpType() == ICMP6.ECHO_REQUEST) {
582 log.info("IPV6 ICMP6 ECHO request to virtual gateway: srcIp={} dstIp={} nextHeader={}",
583 srcIp, dstIp, ipv6Packet.getNextHeader());
584 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
585 .setOutput(pkt.receivedFrom().port()).build();
586 OutboundPacket packet =
587 new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
588 ByteBuffer.wrap(icmp6Packet.buildIcmp6Reply(pkt.parsed()).serialize()));
589 packetService.emit(packet);
590 return true;
591 }
592 }
593 log.warn("IPV6 packet to virtual gateway dropped: srcIp={} dstIp={} nextHeader={}",
594 srcIp, dstIp, ipv6Packet.getNextHeader());
595 return true;
596
597 }
598 return false; // unknown traffic
599 }
600
601 /**
602 * Routes packet reactively.
603 */
604 private void ipPacketReactiveProcessor(PacketContext context, Ethernet ethPkt, ConnectPoint srcCp,
605 IpAddress srcIp, IpAddress dstIp, byte ipProto) {
606 /* check reactive handling and forward packet */
607 log.trace("ip packet: srcCp={} srcIp={} dstIp={} ipProto={}",
608 srcCp, srcIp, dstIp, ipProto);
609
610 EncapsulationType encap = EncapsulationType.NONE;
611
612 // prefix and nextHop for local Subnet
613 IpPrefix srcPrefix = srcIp.toIpPrefix();
614 IpPrefix dstPrefix = dstIp.toIpPrefix();
615 IpAddress srcNextHop = srcIp;
616 IpAddress dstNextHop = dstIp;
617 MacAddress treatmentSrcMac = ethPkt.getDestinationMAC();
618 int borderRoutePrefixLength = 0;
619 boolean isLocalSubnet = true;
620 boolean updateMac = simpleFabric.isVMac(ethPkt.getDestinationMAC());
621
622 // check subnet local or route
623 IpSubnet srcSubnet = simpleFabric.findIpSubnet(srcIp);
624 if (srcSubnet == null) {
625 Route route = simpleFabric.findBorderRoute(srcIp);
626 if (route == null) {
627 log.warn("route unknown: srcIp={}", srcIp);
628 return;
629 }
630 srcPrefix = route.prefix();
631 srcNextHop = route.nextHop();
632 borderRoutePrefixLength = route.prefix().prefixLength();
633 isLocalSubnet = false;
634 }
635 IpSubnet dstSubnet = simpleFabric.findIpSubnet(dstIp);
636 if (dstSubnet == null) {
637 Route route = simpleFabric.findBorderRoute(dstIp);
638 if (route == null) {
639 log.warn("route unknown: dstIp={}", dstIp);
640 return;
641 }
642 dstPrefix = route.prefix();
643 dstNextHop = route.nextHop();
644 borderRoutePrefixLength = route.prefix().prefixLength();
645 isLocalSubnet = false;
646 }
647
648 if (dstSubnet != null) {
649 // destination is local subnet ip
650 if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR && dstSubnet.equals(srcSubnet)) {
651 // NOTE: if ALLOW_ETH_ADDRESS_SELECTOR=false; l2Forward is always false
652 L2Network l2Network = simpleFabric.findL2Network(dstSubnet.l2NetworkName());
653 treatmentSrcMac = ethPkt.getSourceMAC();
654 if (l2Network != null && l2Network.l2Forward()) {
655 // NOTE: no reactive route action but do forward packet for L2Forward do not handle packet
656 // update mac only if dstMac is virtualGatewayMac, else assume valid mac already for the l2 network
657 log.info("LOCAL FORWARD ONLY: "
658 + "srcCp={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
659 context.inPacket().receivedFrom(),
660 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
661 ethPkt.getVlanID(), ipProto, updateMac);
662 forwardPacketToDstIp(context, dstIp, treatmentSrcMac, updateMac);
663 return;
664 }
665 }
666 encap = dstSubnet.encapsulation();
667 if (encap == EncapsulationType.NONE && srcSubnet != null) {
668 encap = srcSubnet.encapsulation();
669 }
670 } else {
671 // destination is external network
672 if (srcSubnet == null) {
673 // both are externel network
674 log.warn("INVALID PACKET: srcIp and dstIp are both NON-LOCAL: "
675 + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
676 context.inPacket().receivedFrom(),
677 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
678 ethPkt.getVlanID(), ipProto, updateMac);
679 return;
680 }
681 encap = srcSubnet.encapsulation();
682 }
683
684 log.info("REGI AND FORWARD: "
685 + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
686 context.inPacket().receivedFrom(),
687 srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
688 ethPkt.getVlanID(), ipProto, updateMac);
689 setUpConnectivity(srcCp, ipProto, srcPrefix, dstPrefix, dstNextHop, treatmentSrcMac, encap, updateMac,
690 isLocalSubnet, borderRoutePrefixLength);
691 forwardPacketToDstIp(context, dstNextHop, treatmentSrcMac, updateMac);
692 }
693
694 /**
695 * Emits the specified packet onto the network.
696 */
697 private void forwardPacketToDstIp(PacketContext context, IpAddress nextHopIp,
698 MacAddress srcMac, boolean updateMac) {
699 Set<Host> hosts = hostService.getHostsByIp(nextHopIp);
700 Host dstHost;
701 if (!hosts.isEmpty()) {
702 dstHost = hosts.iterator().next();
703 } else {
704 // NOTE: hostService.requestMac(nextHopIp); NOT IMPLEMENTED in ONOS HostManager.java; do it myself
705 log.warn("forward packet nextHopIp host_mac unknown: nextHopIp={}", nextHopIp);
706 hostService.startMonitoringIp(nextHopIp);
707 simpleFabric.requestMac(nextHopIp);
708 // CONSIDER: make flood on all port of the dstHost's L2Network
709 return;
710 }
711 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
712 .setOutput(dstHost.location().port()).build();
713 OutboundPacket outPacket;
714 if (updateMac) {
715 // NOTE: eth address update by treatment is NOT applied, so update mac myself
716 outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
717 ByteBuffer.wrap(context.inPacket().parsed()
718 .setSourceMACAddress(srcMac)
719 .setDestinationMACAddress(dstHost.mac()).serialize()));
720 } else {
721 outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
722 context.inPacket().unparsed());
723 }
724 // be quiet on normal situation
725 log.info("forward packet: nextHopIP={} srcCP={} dstCP={}",
726 nextHopIp, context.inPacket().receivedFrom(), dstHost.location());
727 packetService.emit(outPacket);
728 }
729
730 /**
731 * Update intents for connectivity.
732 *
733 * ToHost: dstPrefix = dstHostIp.toIpPrefix(), nextHopIp = destHostIp
734 * ToInternet: dstPrefix = route.prefix(), nextHopIp = route.nextHopIp
735 * returns intent submited or not
736 */
737 private boolean setUpConnectivity(ConnectPoint srcCp, byte ipProto, IpPrefix srcPrefix, IpPrefix dstPrefix,
738 IpAddress nextHopIp, MacAddress treatmentSrcMac,
739 EncapsulationType encap, boolean updateMac,
740 boolean isLocalSubnet, int borderRoutePrefixLength) {
741 Key key;
742 String keyProtoTag = "";
743 if (simpleFabric.REACTIVE_MATCH_IP_PROTO) {
744 keyProtoTag = "-p" + ipProto;
745 }
746 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE) {
747 key = Key.of(srcPrefix.toString() + "-to-" + dstPrefix.toString() + keyProtoTag, reactiveAppId);
748 } else {
749 key = Key.of(dstPrefix.toString() + keyProtoTag, reactiveAppId);
750 }
751
752 if (!(simpleFabric.findL2Network(srcCp, VlanId.NONE) != null ||
753 (simpleFabric.REACTIVE_ALLOW_LINK_CP && !linkService.getIngressLinks(srcCp).isEmpty()))) {
754 log.warn("NO REGI for srcCp not in L2Network; srcCp={} srcPrefix={} dstPrefix={} nextHopIp={}",
755 srcCp, srcPrefix, dstPrefix, nextHopIp);
756 return false;
757 }
758
759 MacAddress nextHopMac = null;
760 ConnectPoint egressPoint = null;
761 for (Host host : hostService.getHostsByIp(nextHopIp)) {
762 if (host.mac() != null) {
763 nextHopMac = host.mac();
764 egressPoint = host.location();
765 break;
766 }
767 }
768 if (nextHopMac == null || egressPoint == null) {
769 log.info("NO REGI for unknown nextHop Cp and Mac: srcPrefix={} dstPrefix={} nextHopIp={}",
770 srcPrefix, dstPrefix, nextHopIp);
771 hostService.startMonitoringIp(nextHopIp);
772 simpleFabric.requestMac(nextHopIp);
773 return false;
774 }
775 TrafficTreatment treatment;
776 if (updateMac && simpleFabric.ALLOW_ETH_ADDRESS_SELECTOR) {
777 treatment = generateSetMacTreatment(nextHopMac, treatmentSrcMac);
778 } else {
779 treatment = DefaultTrafficTreatment.builder().build();
780 }
781
782 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
783 if (dstPrefix.isIp4()) {
784 selector.matchEthType(Ethernet.TYPE_IPV4);
785 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
786 selector.matchIPSrc(srcPrefix);
787 }
788 if (dstPrefix.prefixLength() > 0) {
789 selector.matchIPDst(dstPrefix);
790 }
791 if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
792 selector.matchIPProtocol(ipProto);
793 }
794 } else {
795 selector.matchEthType(Ethernet.TYPE_IPV6);
796 if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
797 selector.matchIPv6Src(srcPrefix);
798 }
799 if (dstPrefix.prefixLength() > 0) {
800 selector.matchIPv6Dst(dstPrefix);
801 }
802 if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
803 selector.matchIPProtocol(ipProto);
804 }
805 }
806
807 // check and merge already existing ingress points
808 Set<ConnectPoint> ingressPoints = new HashSet<>();
809 MultiPointToSinglePointIntent existingIntent = (MultiPointToSinglePointIntent) intentService.getIntent(key);
810 if (existingIntent != null) {
811 ingressPoints.addAll(existingIntent.ingressPoints());
812 if (!ingressPoints.add(srcCp) // alread exists and dst not changed
813 && egressPoint.equals(existingIntent.egressPoint())
814 && treatment.equals(existingIntent.treatment())) {
815 log.warn("srcCP is already in mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
816 srcPrefix, dstPrefix, srcCp);
817 return false;
818 }
819 log.info("update mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
820 srcPrefix, dstPrefix, srcCp);
821 } else {
822 log.info("create mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
823 srcPrefix, dstPrefix, srcCp);
824 ingressPoints.add(srcCp);
825 }
826
827 // priority for forwarding case
828 int priority = reactivePriority(true, isLocalSubnet, borderRoutePrefixLength);
829
830 MultiPointToSinglePointIntent newIntent = MultiPointToSinglePointIntent.builder()
831 .key(key)
832 .appId(reactiveAppId)
833 .selector(selector.build())
834 .treatment(treatment)
835 .ingressPoints(ingressPoints)
836 .egressPoint(egressPoint)
837 .priority(priority)
838 .constraints(buildConstraints(reactiveConstraints, encap))
839 .build();
840 log.info("submmit mp2p intent: srcPrefix={} dstPrefix={} srcCp={} "
841 + "newIntent={} nextHopIp={} nextHopMac={} priority={}",
842 srcPrefix, dstPrefix, ingressPoints, newIntent, nextHopIp, nextHopMac, priority);
843 toBePurgedIntentKeys.remove(newIntent.key());
844 intentService.submit(newIntent);
845 return true;
846 }
847
848 // generate treatment to target
849 private TrafficTreatment generateSetMacTreatment(MacAddress dstMac, MacAddress srcMac) {
850 return DefaultTrafficTreatment.builder()
851 // NOTE: Cisco Switch requires both src and dst mac set
852 .setEthDst(dstMac)
853 .setEthSrc(srcMac)
854 .build();
855 }
856
857 // monitor border peers for routeService lookup to be effective
858 private void monitorBorderPeers() {
859 for (Route route : simpleFabric.getBorderRoutes()) {
860 hostService.startMonitoringIp(route.nextHop());
861 simpleFabric.requestMac(route.nextHop());
862 }
863 }
864
865 // priority calculator
866 private int reactivePriority(boolean isForward, boolean isLocalSubnet, int borderRoutePrefixLength) {
867 if (isLocalSubnet) { // localSubnet <-> localSubnet
868 if (isForward) {
869 return simpleFabric.PRI_REACTIVE_LOCAL_FORWARD;
870 } else { // isInterncept
871 return simpleFabric.PRI_REACTIVE_LOCAL_INTERCEPT;
872 }
873 } else { // isBorder: localSubnet <-> boarderRouteGateway
874 int offset;
875 if (isForward) {
876 offset = simpleFabric.PRI_REACTIVE_BORDER_FORWARD;
877 } else { // isIntercept
878 offset = simpleFabric.PRI_REACTIVE_BORDER_INTERCEPT;
879 }
880 return simpleFabric.PRI_REACTIVE_BORDER_BASE
881 + borderRoutePrefixLength * simpleFabric.PRI_REACTIVE_BORDER_STEP + offset;
882 }
883 }
884
885 // constraints generator
886 private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encap) {
887 if (!encap.equals(EncapsulationType.NONE)) {
888 List<Constraint> newConstraints = new ArrayList<>(constraints);
889 constraints.stream()
890 .filter(c -> c instanceof EncapsulationConstraint)
891 .forEach(newConstraints::remove);
892 newConstraints.add(new EncapsulationConstraint(encap));
893 return ImmutableList.copyOf(newConstraints);
894 }
895 return constraints;
896 }
897
898 // Dump Cli Handler
899 private void dump(String subject, PrintStream out) {
Ray Milkey2ff67162018-01-22 10:14:19 -0800900 if ("intents".equals(subject)) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900901 out.println("Reactive Routing Route Intents:\n");
902 for (Intent entry : intentService.getIntents()) {
903 if (reactiveAppId.equals(entry.appId())) {
904 MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
905 out.println(" " + intent.key().toString()
906 + " to " + intent.egressPoint().toString()
907 + " set " + intent.treatment().immediate().toString()
908 + " from " + intent.ingressPoints().toString());
909 }
910 }
911 out.println("");
912
913 out.println("Reactive Routing Intercept Flow Rules:\n");
914 List<FlowRule> rules = new ArrayList(interceptFlowRules);
915 Collections.sort(rules, new Comparator<FlowRule>() {
916 @Override
917 public int compare(FlowRule a, FlowRule b) {
918 int r = a.deviceId().toString().compareTo(b.deviceId().toString());
919 return (r != 0) ? r : Integer.compare(b.priority(), a.priority()); // descending on priority
920 }
921 });
922 for (FlowRule rule : rules) {
923 out.println(" device=" + rule.deviceId().toString()
924 + " priority=" + rule.priority()
925 + " selector=" + rule.selector().criteria().toString());
926 }
927 out.println("");
928 out.println("Reactive Routing Intents to Be Purged:\n");
929 for (Key key: toBePurgedIntentKeys) {
930 out.println(" " + key.toString());
931 }
932 out.println("");
933
Ray Milkey2ff67162018-01-22 10:14:19 -0800934 } else if ("reactive-intents".equals(subject)) {
Lee Yongjae7c27bb42017-11-17 12:00:45 +0900935 for (Intent entry : intentService.getIntents()) {
936 if (reactiveAppId.equals(entry.appId())) {
937 MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
938 out.println(intent.key().toString()
939 + " to " + intent.egressPoint().toString()
940 + " set " + intent.treatment().immediate().toString()
941 + " from " + intent.ingressPoints().toString());
942 }
943 }
944 }
945 }
946
947 // Listener
948 private class InternalSimpleFabricListener implements SimpleFabricListener {
949 @Override
950 public void event(SimpleFabricEvent event) {
951 switch (event.type()) {
952 case SIMPLE_FABRIC_UPDATED:
953 refreshIntercepts();
954 refreshRouteIntents();
955 checkIntentsPurge();
956 break;
957 case SIMPLE_FABRIC_FLUSH:
958 withdrawAllReactiveIntents();
959 checkIntentsPurge();
960 break;
961 case SIMPLE_FABRIC_IDLE:
962 refreshIntercepts();
963 refreshRouteIntents();
964 checkIntentsPurge();
965 monitorBorderPeers();
966 break;
967 case SIMPLE_FABRIC_DUMP:
968 dump(event.subject(), event.out());
969 break;
970 default:
971 break;
972 }
973 }
974 }
975
976}
977