blob: faf9aff70a857b6c30fc36fb52aae07b954f4ee9 [file] [log] [blame]
maojianwei42e23442016-02-15 10:40:48 +08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
maojianwei42e23442016-02-15 10:40:48 +08003 *
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.fnl.impl;
17
18import org.onlab.packet.IpPrefix;
19import org.onosproject.fnl.intf.NetworkAnomaly;
20import org.onosproject.fnl.intf.NetworkDiagnostic;
21import org.onosproject.fnl.base.TsLoopPacket;
22import org.onosproject.fnl.base.TsReturn;
23import org.onosproject.net.ConnectPoint;
24import org.onosproject.net.Device;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.Link;
27import org.onosproject.net.PortNumber;
28import org.onosproject.net.device.DeviceService;
29import org.onosproject.net.flow.FlowEntry;
30import org.onosproject.net.flow.FlowRuleService;
31import org.onosproject.net.flow.criteria.Criterion;
32import org.onosproject.net.flow.criteria.EthTypeCriterion;
33import org.onosproject.net.flow.criteria.IPCriterion;
34import org.onosproject.net.flow.criteria.IPProtocolCriterion;
35import org.onosproject.net.flow.criteria.PortCriterion;
36import org.onosproject.net.flow.instructions.Instruction;
37import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
38import org.onosproject.net.host.HostService;
39import org.onosproject.net.link.LinkService;
40import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
43import java.util.Iterator;
44import java.util.HashMap;
45import java.util.ArrayList;
46import java.util.HashSet;
47import java.util.List;
48import java.util.Map;
49import java.util.Set;
50import java.util.stream.Collectors;
51
52import static com.google.common.base.Preconditions.checkArgument;
53import static com.google.common.base.Preconditions.checkNotNull;
54import static org.onlab.packet.EthType.EtherType.IPV4;
55import static org.onlab.packet.EthType.EtherType.VLAN;
56import static org.onosproject.fnl.base.NetworkDiagnosticUtils.isDevice;
57import static org.onosproject.fnl.base.NetworkDiagnosticUtils.sortCriteria;
58import static org.onosproject.fnl.base.NetworkDiagnosticUtils.sortFlowTable;
59import static org.onosproject.fnl.base.TsLoopPacket.matchBuilder;
60import static org.onosproject.fnl.intf.NetworkDiagnostic.Type.LOOP;
61import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
62import static org.onosproject.net.flow.criteria.Criteria.matchInPort;
63import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_TYPE;
64import static org.onosproject.net.flow.criteria.Criterion.Type.IN_PORT;
65import static org.onosproject.net.flow.criteria.Criterion.Type.IP_PROTO;
66
67/**
68 * Loop Checking Diagnostic Implementation.
69 *
70 * Strategy Pattern.
71 */
72public class DefaultCheckLoop implements NetworkDiagnostic {
73
74 private static final int IP_PROTO_TCP_TS = 6;
75 private static final int IP_PROTO_UDP_TS = 17;
76
77 private static final String E_CANNOT_HANDLE = "can not handle {} now.";
78
79 private final Logger log = LoggerFactory.getLogger(getClass());
80
81 private final DeviceService deviceService;
82 private final HostService hostService;
83 private final FlowRuleService flowRuleService;
84 private final LinkService linkService;
85
86
87 private Map<DeviceId, Device> deviceInfo;
88 private Map<DeviceId, Iterable<FlowEntry>> flowEntryInfo;
89 private Set<Device> accessDevices;
90
91 //conventionally used by tsGetEgressLinks()
92 private Map<DeviceId, Set<Link>> egressLinkInfo;
93
94 //Two below are hot data in checking process.
95 private Set<NetworkAnomaly> loops;
96 private Set<DeviceId> excludeDeviceId;
97
98 /**
99 * Creates and returns an instance of Loop Checking algorithm module.
100 *
101 * @param ds reference of DeviceService
102 * @param hs reference of HostService
103 * @param frs reference of FlowRuleService
104 * @param ls reference of LinkService
105 */
106 public DefaultCheckLoop(DeviceService ds,
107 HostService hs,
108 FlowRuleService frs,
109 LinkService ls) {
110 checkNotNull(ds, "DeviceService cannot be null");
111 checkNotNull(hs, "HostService cannot be null");
112 checkNotNull(frs, "FlowRuleService cannot be null");
113 checkNotNull(ls, "LinkService cannot be null");
114
115 deviceService = ds;
116 hostService = hs;
117 flowRuleService = frs;
118 linkService = ls;
119 }
120
121 /**
122 * Checks for loops and returns any that were found.
123 * An empty set is returned if there are no loops.
124 *
125 * @return the set of loops; may be empty
126 */
127 @Override
128 public Set<NetworkAnomaly> findAnomalies() {
129 return findLoop();
130 }
131
132 @Override
133 public Type type() {
134 return LOOP;
135 }
136
137 /**
138 * Enter of the loop checking algorithm.
139 *
140 * @return the set of loop results; empty, if there is no loop
141 */
142 private Set<NetworkAnomaly> findLoop() {
143
144 getNetworkSnapshot();
145
146 loops = new HashSet<>();
147 excludeDeviceId = new HashSet<>();
148
149 for (Device device : accessDevices) {
150 if (excludeDeviceId.contains(device.id())) {
151 continue;
152 }
153
154
155 List<FlowEntry> availableFlowEntries = new ArrayList<>();
156
157 flowEntryInfo.get(device.id()).forEach(flowEntry -> {
158 if (flowEntry.state() == ADDED) {
159 availableFlowEntries.add(flowEntry);
160 }
161 });
162
163 List<FlowEntry> sortedFlowEntries = sortFlowTable(availableFlowEntries);
164
165
166 for (FlowEntry flow : sortedFlowEntries) {
167
168 TsLoopPacket pkt =
169 matchBuilder(flow.selector().criteria(), null);
170
171 pkt.pushPathFlow(flow);
172
173 List<Instruction> inst = flow.treatment().immediate();
174
175 for (Instruction instOne : inst) {
176 // Attention !!!
177 // if you would like to modify the code here,
178 // please MAKE VERY SURE that you are clear with
179 // the relationship of any invoked methods, and
180 // the relationship of params which are passed in and out.
181 processOneInstruction(instOne, device.id(),
182 null, pkt, true, flow);
183 }
184 }
185 }
186
187 // TODO - avoid two-hop LOOP
188
189 // TODO - another clean operations
190
191 return loops;
192 }
193
194 /**
195 * Iterate one by one at switch hops.
196 * Return whether we discover a Loop now or not.
197 *
198 * When flows form a loop,
199 * pkt is also a return value indicating the loop header.
200 *
201 * @param deviceId the device needed to be checked
202 * @param pkt virtual packet forwarded by switches
203 * @return true if a loop is discovered
204 */
205 private boolean matchDeviceFlows(DeviceId deviceId, TsLoopPacket pkt) {
206 if (pkt.isPassedDevice(deviceId)) {
207 return true; // Attention: pkt should be held outside
208 }
209
210
211 List<FlowEntry> availableFlowEntries = new ArrayList<>();
212
213 flowEntryInfo.get(deviceId).forEach(flowEntry -> {
214 if (flowEntry.state() == ADDED) {
215 availableFlowEntries.add(flowEntry);
216 }
217 });
218
219 List<FlowEntry> sortedFlowEntries = sortFlowTable(availableFlowEntries);
220
221
222 for (FlowEntry flowEntry : sortedFlowEntries) {
223 TsReturn<Boolean> isBigger = new TsReturn<>();
224 TsLoopPacket newPkt = pkt.copyPacketMatch();
225
226 if (!matchAndAddFlowEntry(flowEntry, newPkt, isBigger)) {
227 continue;
228 }
229
230 newPkt.pushPathFlow(flowEntry);
231 // no need to popPathFlow(),
232 // because we will drop this newPkt, and copy pkt again
233
234 for (Instruction instOne : flowEntry.treatment().immediate()) {
235 // Attention !!!
236 // if you would like to modify the code here,
237 // please MAKE VERY SURE that you are clear with
238 // the relationship of any invoked methods, and
239 // the relationship of params which are passed in and out.
240 if (processOneInstruction(instOne, deviceId,
241 pkt, newPkt, false, null)) {
242 return true;
243 }
244 }
245
246 newPkt.popPathFlow();
247
248 if (!isBigger.getValue()) {
249 break;
250 }
251 }
252 return false;
253 }
254
255 /**
256 * Process one of every instructions.
257 *
258 * The isFindLoop should be true if it is invoked by findLoop method,
259 * and be false if it is invoked by matchDeviceFlows method.
260 *
261 * @param instOne the instruction to be processed
262 * @param currentDeviceId id of the device we are now in
263 * @param initPkt the packet before being copied
264 * @param matchedPkt the packet which matched the flow entry,
265 * to which this instruction belongs
266 * @param isFindLoop indicate if it is invoked by findLoop method
267 * @param firstEntry the flow entry from which the packet is generated
268 * @return true, if a loop is discovered;
269 * false, 1. invoked by matchDeviceFlows method, and detected no loop;
270 * 2. invoked by findLoop method
271 */
272 private boolean processOneInstruction(Instruction instOne,
273 DeviceId currentDeviceId,
274 TsLoopPacket initPkt,
275 TsLoopPacket matchedPkt,
276 boolean isFindLoop,
277 FlowEntry firstEntry) {
278 if (isFindLoop) {
279 checkArgument(initPkt == null,
280 "initPkt is not null, while isFindLoop is true.");
281 } else {
282 checkArgument(firstEntry == null,
283 "firstEntry is not null, while isFindLoop is false.");
284 }
285
286
287 Instruction.Type type = instOne.type();
288 switch (type) {
289 case L2MODIFICATION:
290 //TODO - modify the L2 header of virtual packet
291 log.warn(E_CANNOT_HANDLE, type);
292 break;
293 case L3MODIFICATION:
294 //TODO - modify the L3 header of virtual packet
295 log.warn(E_CANNOT_HANDLE, type);
296 break;
297 case L4MODIFICATION:
298 //TODO - modify the L4 header of virtual packet
299 log.warn(E_CANNOT_HANDLE, type);
300 break;
301 case OUTPUT:
302 if (processOneOutputInstruction(instOne, currentDeviceId,
303 initPkt, matchedPkt, isFindLoop, firstEntry)) {
304 return true;
305 }
306 break;
307 default:
308 log.error("Can't process this type of instruction: {} now.",
309 instOne.type());
310 break;
311 }
312 return false;
313 }
314
315 /**
316 * Process one output instruction.
317 *
318 * Params are passed from processOneInstruction directly,
319 * and obey the same rules.
320 *
321 * @param instOne the instruction to be processed
322 * @param currentDeviceId id of the device we are now in
323 * @param initPkt the packet before being copied
324 * @param matchedPkt the packet which matched the flow entry,
325 * to which this instruction belongs
326 * @param isFindLoop indicate if it is invoked by findLoop method
327 * @param firstEntry the flow entry from which the packet is generated
328 * @return true, if a loop is discovered;
329 * false, 1. invoked by matchDeviceFlows method, and detected no loop;
330 * 2. invoked by findLoop method
331 */
332 private boolean processOneOutputInstruction(Instruction instOne,
333 DeviceId currentDeviceId,
334 TsLoopPacket initPkt,
335 TsLoopPacket matchedPkt,
336 boolean isFindLoop,
337 FlowEntry firstEntry) {
338 OutputInstruction instOutput = (OutputInstruction) instOne;
339 PortNumber instPort = instOutput.port();
340
341 if (!instPort.isLogical()) {
342 // single OUTPUT - NIC or normal port
343
344 Set<Link> dstLink = tsGetEgressLinks(
345 new ConnectPoint(currentDeviceId, instPort));
346 if (!dstLink.isEmpty()) {
347
348 // TODO - now, just deal with the first destination.
349 // will there be more destinations?
350
351 Link dstThisLink = dstLink.iterator().next();
352 ConnectPoint dstPoint = dstThisLink.dst();
353
354 // check output to devices only (output to a host is normal)
355 if (isDevice(dstPoint)) {
356 PortCriterion oldInPort =
357 updatePktInportPerHop(matchedPkt, dstPoint);
358 matchedPkt.pushPathLink(dstThisLink);
359 TsLoopPacket newNewPkt = matchedPkt.copyPacketMatch();
360
361 boolean loopFound =
362 matchDeviceFlows(dstPoint.deviceId(), newNewPkt);
363 if (isFindLoop) {
364 if (loopFound) {
365 loops.add(newNewPkt);
366 updateExcludeDeviceSet(newNewPkt);
367 }
368 matchedPkt.resetLinkFlow(firstEntry);
369 } else {
370 if (loopFound) {
371 initPkt.handInLoopMatch(newNewPkt);
372 return true;
373 }
374 matchedPkt.popPathLink();
375 }
376 restorePktInportPerHop(matchedPkt, oldInPort);
377 }
378 } else {
379 if (!isFindLoop) {
380 //TODO - NEED
381 log.warn("no link connecting at device {}, port {}",
382 currentDeviceId, instPort);
383 }
384 }
385 } else if (instPort.equals(PortNumber.IN_PORT)) {
386 //TODO - in the future,
387 // we may need to resolve this condition 1
388 log.warn("can not handle {} port now.", PortNumber.IN_PORT);
389 } else if (instPort.equals(PortNumber.NORMAL) ||
390 instPort.equals(PortNumber.FLOOD) ||
391 instPort.equals(PortNumber.ALL)) {
392 //TODO - in the future,
393 // we may need to resolve this condition 2
394 log.warn("can not handle {}/{}/{} now.",
395 PortNumber.NORMAL, PortNumber.FLOOD, PortNumber.ALL);
396 }
397 return false;
398 }
399
400 private void updateExcludeDeviceSet(TsLoopPacket loopPkt) {
401 Iterator<Link> iter = loopPkt.getPathLink();
402 while (iter.hasNext()) {
403 excludeDeviceId.add(iter.next().src().deviceId());
404 }
405 }
406
407 private PortCriterion updatePktInportPerHop(TsLoopPacket oldPkt,
408 ConnectPoint dstPoint) {
409
410 PortCriterion inPort = oldPkt.getInport();
411 PortCriterion oldInPort =
412 null != inPort ? (PortCriterion) matchInPort(inPort.port()) : null;
413 // TODO - check - if it really copies this object
414
415 oldPkt.setHeader(matchInPort(dstPoint.port()));
416
417 return oldInPort;
418 }
419
420 private void restorePktInportPerHop(TsLoopPacket pkt,
421 PortCriterion oldInPort) {
422 if (oldInPort == null) {
423 pkt.delHeader(IN_PORT);
424 } else {
425 pkt.setHeader(oldInPort);
426 }
427 }
428
429 private void getNetworkSnapshot() {
430 deviceInfo = new HashMap<>();
431 deviceService.getDevices().forEach(d -> deviceInfo.put(d.id(), d));
432
433 flowEntryInfo = new HashMap<>();
434 deviceInfo.keySet().forEach(id ->
435 flowEntryInfo.put(id, flowRuleService.getFlowEntries(id)));
436
437 egressLinkInfo = new HashMap<>();
438 deviceInfo.keySet().forEach(id ->
439 egressLinkInfo.put(id, linkService.getDeviceEgressLinks(id)));
440
441 accessDevices = new HashSet<>();
442 hostService.getHosts().forEach(h ->
443 accessDevices.add(deviceInfo.get(h.location().deviceId())));
444 }
445
446 private Set<Link> tsGetEgressLinks(ConnectPoint point) {
447 Set<Link> portEgressLink = new HashSet<>();
448 DeviceId deviceId = point.deviceId();
449
450 portEgressLink.addAll(
451 //all egress links
452 egressLinkInfo.get(deviceId)
453 .stream()
454 .filter(l -> l.src().equals(point))
455 .collect(Collectors.toList()));
456
457 return portEgressLink;
458 }
459
460 private boolean matchAndAddFlowEntry(FlowEntry flowEntry,
461 TsLoopPacket pkt,
462 TsReturn<Boolean> isBigger) {
463 isBigger.setValue(false);
464
465 List<Criterion> criterionArray =
466 sortCriteria(flowEntry.selector().criteria());
467
468 for (Criterion criterion : criterionArray) {
469 // TODO - advance
470 switch (criterion.type()) {
471 case IN_PORT:
472 case ETH_SRC:
473 // At present, not support Ethernet mask (ONOS?)
474 case ETH_DST:
475 // At present, not support Ethernet mask (ONOS?)
476 case ETH_TYPE:
477 if (!matchAddExactly(pkt, criterion, isBigger)) {
478 return false;
479 }
480 break;
481 case VLAN_VID:
482 // At present, not support VLAN mask (ONOS?)
483 case VLAN_PCP:
484 if (!pkt.headerExists(ETH_TYPE) ||
485 !((EthTypeCriterion) pkt.getHeader(ETH_TYPE))
486 .ethType().equals(VLAN.ethType())) {
487 return false;
488 }
489 if (!matchAddExactly(pkt, criterion, isBigger)) {
490 return false;
491 }
492 break;
493 case IPV4_SRC:
494 case IPV4_DST:
495 if (!pkt.headerExists(ETH_TYPE) ||
496 !((EthTypeCriterion) pkt.getHeader(ETH_TYPE))
497 .ethType().equals(IPV4.ethType())) {
498 return false;
499 }
500 if (!matchAddIPV4(pkt, criterion, isBigger)) {
501 return false;
502 }
503 break;
504 case IP_PROTO:
505 if (!pkt.headerExists(ETH_TYPE) ||
506 !((EthTypeCriterion) pkt.getHeader(ETH_TYPE))
507 .ethType().equals(IPV4.ethType())) {
508 return false;
509 }
510 if (!matchAddExactly(pkt, criterion, isBigger)) {
511 return false;
512 }
513 break;
514 case IP_DSCP:
515 // TODO: 10/28/16 support IP_DSCP match field
516 break;
517 case IP_ECN:
518 // TODO: 10/28/16 support IP_DSCP match field
519 break;
520 case TCP_SRC:
521 case TCP_DST:
522 if (!pkt.headerExists(IP_PROTO) ||
523 IP_PROTO_TCP_TS !=
524 ((IPProtocolCriterion)
525 pkt.getHeader(IP_PROTO))
526 .protocol()
527 ) {
528 // has TCP match requirement, but can't afford TCP
529 return false;
530 }
531 // in this "for" loop
532 // avoid IP_PROTO locates after TCP_*
533 if (!matchAddExactly(pkt, criterion, isBigger)) {
534 return false;
535 }
536 break;
537 case UDP_SRC:
538 case UDP_DST:
539 if (!pkt.headerExists(IP_PROTO) ||
540 IP_PROTO_UDP_TS !=
541 ((IPProtocolCriterion)
542 pkt.getHeader(IP_PROTO))
543 .protocol()
544 ) {
545 // has UDP match requirement, but can't afford UDP
546 return false;
547 }
548 // in this "for" loop
549 // avoid IP_PROTO locates after UDP_*
550 if (!matchAddExactly(pkt, criterion, isBigger)) {
551 return false;
552 }
553 break;
554
555 default:
556 log.debug("{} can't be supported by OF1.0",
557 criterion.type());
558 return false;
559 }
560 }
561 return true;
562 }
563
564 private boolean matchAddExactly(TsLoopPacket pkt,
565 Criterion criterion,
566 TsReturn<Boolean> isBigger) {
567
568 if (pkt.headerExists(criterion.type())) {
569 if (!pkt.getHeader(criterion.type()).equals(criterion)) {
570 return false;
571 }
572
573 } else {
574 // TODO - check if it is IN_PORT or IN_PHY_PORT, should be strict
575 pkt.setHeader(criterion);
576 isBigger.setValue(true);
577 }
578
579 return true; // should put it here
580 }
581
582 // before invoking this, MUST insure EtherType is IPv4.
583 private boolean matchAddIPV4(TsLoopPacket pkt,
584 Criterion criterion,
585 TsReturn<Boolean> isBigger) {
586
587 if (pkt.headerExists(criterion.type())) {
588
589 IpPrefix ipFlow = ((IPCriterion) criterion).ip();
590 IpPrefix ipPkt =
591 ((IPCriterion) pkt.getHeader(criterion.type())).ip();
592
593 // attention - the order below is important
594 if (ipFlow.equals(ipPkt)) {
595 // shoot
596
597 } else if (ipFlow.contains(ipPkt)) {
598 // shoot, pkt is more exact than flowEntry
599
600 } else if (ipPkt.contains(ipFlow)) {
601 // pkt should be changed to be more exact
602 pkt.setHeader(criterion);
603 isBigger.setValue(true);
604 } else {
605 // match fail
606 return false;
607 }
608
609 } else {
610 // attention the order of criteria in "for" loop
611 pkt.setHeader(criterion);
612 isBigger.setValue(true);
613 }
614
615 return true;
616 }
617}