blob: b683351d11537c9aa8ba54f1e5154611a7481817 [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.t3.impl;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.onlab.packet.IpAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.Generator;
import org.onosproject.cluster.NodeId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DataPlaneEntity;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
import org.onosproject.net.PipelineTraceableHitChain;
import org.onosproject.net.PipelineTraceableInput;
import org.onosproject.net.PipelineTraceableOutput;
import org.onosproject.net.PipelineTraceableOutput.PipelineTraceableResult;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.PipelineTraceable;
import org.onosproject.net.config.ConfigException;
import org.onosproject.net.config.basics.InterfaceConfig;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.EthTypeCriterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.group.Group;
import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.intf.Interface;
import org.onosproject.routeservice.ResolvedRoute;
import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
import org.onosproject.t3.api.DeviceNib;
import org.onosproject.t3.api.DriverNib;
import org.onosproject.t3.api.EdgePortNib;
import org.onosproject.t3.api.FlowNib;
import org.onosproject.t3.api.GroupNib;
import org.onosproject.t3.api.HostNib;
import org.onosproject.t3.api.LinkNib;
import org.onosproject.t3.api.MastershipNib;
import org.onosproject.t3.api.MulticastRouteNib;
import org.onosproject.t3.api.NetworkConfigNib;
import org.onosproject.t3.api.NibProfile;
import org.onosproject.t3.api.RouteNib;
import org.onosproject.t3.api.StaticPacketTrace;
import org.onosproject.t3.api.TroubleshootService;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Optional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.onlab.packet.EthType.EtherType;
import static org.onosproject.net.flow.TrafficSelector.Builder;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Manager to troubleshoot packets inside the network.
* Given a representation of a packet follows it's path in the network according to the existing flows and groups in
* the devices.
*/
@Component(immediate = true, service = TroubleshootService.class)
public class TroubleshootManager implements TroubleshootService {
private static final Logger log = getLogger(TroubleshootManager.class);
static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
// uses a snapshot (cache) of NIBs instead of interacting with ONOS core in runtime
protected FlowNib flowNib = FlowNib.getInstance();
protected GroupNib groupNib = GroupNib.getInstance();
protected LinkNib linkNib = LinkNib.getInstance();
protected HostNib hostNib = HostNib.getInstance();
protected DeviceNib deviceNib = DeviceNib.getInstance();
protected DriverNib driverNib = DriverNib.getInstance();
protected MastershipNib mastershipNib = MastershipNib.getInstance();
protected EdgePortNib edgePortNib = EdgePortNib.getInstance();
protected RouteNib routeNib = RouteNib.getInstance();
protected NetworkConfigNib networkConfigNib = NetworkConfigNib.getInstance();
protected MulticastRouteNib mcastRouteNib = MulticastRouteNib.getInstance();
// FIXME Revisit offline mode after a first implementation
private final Map<DeviceId, PipelineTraceable> pipelineTraceables = Maps.newConcurrentMap();
@Override
public boolean checkNibValidity() {
return Stream.of(flowNib, groupNib, linkNib, hostNib, deviceNib, driverNib,
mastershipNib, edgePortNib, routeNib, networkConfigNib, mcastRouteNib)
.allMatch(nib -> nib != null && nib.isValid());
}
@Override
public String printNibSummary() {
StringBuilder summary = new StringBuilder().append("*** Current NIB in valid: ***\n");
Stream.of(flowNib, groupNib, linkNib, hostNib, deviceNib, driverNib,
mastershipNib, edgePortNib, routeNib, networkConfigNib, mcastRouteNib)
.forEach(nib -> {
NibProfile profile = nib.getProfile();
summary.append(String.format(
nib.getClass().getName() + " created %s from %s\n",
profile.date(), profile.sourceType()));
});
return summary.append(StringUtils.rightPad("", 125, '-')).toString();
}
@Override
public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
return new PingAllGenerator(type, hostNib, this);
}
@Override
public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
return new McastGenerator(mcastRouteNib, this, vlanId);
}
@Override
public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
Host source = hostNib.getHost(sourceHost);
Host destination = hostNib.getHost(destinationHost);
//Temporary trace to fail in case we don't have enough information or what is provided is incoherent
StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
if (source == null) {
failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
failTrace.setSuccess(false);
return ImmutableSet.of(failTrace);
}
if (destination == null) {
failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
failTrace.setSuccess(false);
return ImmutableSet.of(failTrace);
}
TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
.matchEthType(etherType.ethType().toShort())
.matchEthDst(source.mac())
.matchVlanId(source.vlan());
try {
ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
//if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
// we are under same leaf so it's L2 Unicast.
if (areBridged(source, destination)) {
selectorBuilder.matchEthDst(destination.mac());
source.locations().forEach(hostLocation -> {
selectorBuilder.matchInPort(hostLocation.port());
StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
trace.addEndpointHosts(Pair.of(source, destination));
traces.add(trace);
});
//The destination host is not dual homed, if it is the other path might be done through routing.
if (destination.locations().size() == 1) {
return traces.build();
}
}
//handle the IPs for src and dst in case of L3
if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
//Match on the source IP
if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
return ImmutableSet.of(failTrace);
}
//Match on destination IP
if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
return ImmutableSet.of(failTrace);
}
} else {
failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
"please use packet based");
failTrace.setSuccess(false);
return ImmutableSet.of(failTrace);
}
//l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigNib.getConfig(source.location()
.deviceId(), SegmentRoutingDeviceConfig.class);
if (segmentRoutingConfig != null) {
selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
} else {
failTrace.addResultMessage("Can't get " + source.location().deviceId() +
" router MAC from segment routing config can't perform L3 tracing.");
failTrace.setSuccess(false);
}
source.locations().forEach(hostLocation -> {
selectorBuilder.matchInPort(hostLocation.port());
StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
trace.addEndpointHosts(Pair.of(source, destination));
traces.add(trace);
});
return traces.build();
} catch (ConfigException e) {
failTrace.addResultMessage("Can't get config " + e.getMessage());
return ImmutableSet.of(failTrace);
}
}
private PipelineTraceable getPipelineMatchable(DeviceId deviceId) {
return pipelineTraceables.compute(deviceId, (k, v) -> {
if (v == null) {
log.info("PipelineMatchable not found for {}", deviceId);
Device d = deviceNib.getDevice(deviceId);
if (d.is(PipelineTraceable.class)) {
v = d.as(PipelineTraceable.class);
v.init();
} else {
log.warn("PipelineMatchable behaviour not supported for device {}",
deviceId);
}
}
return v;
});
}
private List<DataPlaneEntity> getDataPlaneEntities(DeviceId deviceId) {
List<DataPlaneEntity> dataPlaneEntities = Lists.newArrayList();
flowNib.getFlowEntriesByState(deviceId, FlowEntry.FlowEntryState.ADDED).forEach(entity ->
dataPlaneEntities.add(new DataPlaneEntity(entity)));
groupNib.getGroupsByState(deviceId, Group.GroupState.ADDED).forEach(entity ->
dataPlaneEntities.add(new DataPlaneEntity(entity)));
return dataPlaneEntities;
}
/**
* Matches src and dst IPs based on host information.
*
* @param host the host
* @param failTrace the trace to use in case of failure
* @param selectorBuilder the packet we are building to trace
* @param etherType the traffic type
* @param src is this src host or dst host
* @return true if properly matched
*/
private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
EtherType etherType, boolean src) {
List<IpAddress> ips = getIpAddresses(host, etherType, true);
if (ips.size() > 0) {
if (etherType.equals(EtherType.IPV4)) {
if (src) {
selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
} else {
selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
}
} else if (etherType.equals(EtherType.IPV6)) {
if (src) {
selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
} else {
selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
}
}
} else {
failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
failTrace.setSuccess(false);
return false;
}
return true;
}
List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
return host.ipAddresses().stream().filter(ipAddress -> {
boolean correctIp = false;
if (etherType.equals(EtherType.IPV4)) {
correctIp = ipAddress.isIp4();
} else if (etherType.equals(EtherType.IPV6)) {
correctIp = ipAddress.isIp6();
}
if (checklocal) {
correctIp = correctIp && !ipAddress.isLinkLocal();
}
return correctIp;
}).collect(Collectors.toList());
}
/**
* Checks that two hosts are bridged (L2Unicast).
*
* @param source the source host
* @param destination the destination host
* @return true if bridged.
* @throws ConfigException if config can't be properly retrieved
*/
private boolean areBridged(Host source, Host destination) throws ConfigException {
//If the locations is not the same we don't even check vlan or subnets
if (Collections.disjoint(source.locations(), destination.locations())) {
return false;
}
if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
&& !source.vlan().equals(destination.vlan())) {
return false;
}
InterfaceConfig interfaceCfgH1 = networkConfigNib.getConfig(source.location(), InterfaceConfig.class);
InterfaceConfig interfaceCfgH2 = networkConfigNib.getConfig(destination.location(), InterfaceConfig.class);
if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
//following can be optimized but for clarity is left as is
Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
return intfH1.vlanUntagged().equals(destination.vlan()) ||
intfH1.vlanNative().equals(destination.vlan());
}
if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
return intfH2.vlanUntagged().equals(source.vlan()) ||
intfH2.vlanNative().equals(source.vlan());
}
if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
return false;
}
if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
return false;
}
List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
intersection.retainAll(intfH2.ipAddressesList());
if (intersection.size() == 0) {
return false;
}
}
return true;
}
@Override
public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
log.info("Tracing packet {} coming in through {}", packet, in);
//device must exist in ONOS
Preconditions.checkNotNull(deviceNib.getDevice(in.deviceId()),
"Device " + in.deviceId() + " must exist in ONOS");
StaticPacketTrace trace = new StaticPacketTrace(packet, in);
boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
//FIXME this can be done recursively
//Building output connect Points
List<ConnectPoint> path = new ArrayList<>();
trace = traceInDevice(trace, packet, in, isDualHomed, path);
trace = getTrace(path, in, trace, isDualHomed);
return trace;
}
/**
* Computes a trace for a give packet that start in the network at the given connect point.
*
* @param completePath the path traversed by the packet
* @param in the input connect point
* @param trace the trace to build
* @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
* @return the build trace for that packet.
*/
private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
boolean isDualHomed) {
log.debug("------------------------------------------------------------");
//if the trace already contains the input connect point there is a loop
if (pathContainsDevice(completePath, in.deviceId())) {
trace.addResultMessage("Loop encountered in device " + in.deviceId());
completePath.add(in);
trace.addCompletePath(completePath);
trace.setSuccess(false);
return trace;
}
//let's add the input connect point
completePath.add(in);
//If the trace has no outputs for the given input we stop here
if (trace.getHitChains(in.deviceId()) == null) {
TroubleshootUtils.computePath(completePath, trace, null);
trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
trace.setSuccess(false);
return trace;
}
//If the trace has outputs we analyze them all
for (PipelineTraceableHitChain outputPath : trace.getHitChains(in.deviceId())) {
ConnectPoint cp = outputPath.getOutputPort();
log.debug("Connect point in {}", in);
log.debug("Output path {}", cp);
log.debug("{}", outputPath.getEgressPacket());
if (outputPath.isDropped()) {
continue;
}
//Hosts for the the given output
Set<Host> hostsList = hostNib.getConnectedHosts(cp);
//Hosts queried from the original ip or mac
Set<Host> hosts = getHosts(trace);
if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
outputPath.getEgressPacket().getCriterion(Criterion.Type.VLAN_VID) != null
&& ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
.equals(((VlanIdCriterion) outputPath.getEgressPacket().getCriterion(Criterion.Type.VLAN_VID))
.vlanId())) {
if (trace.getHitChains(in.deviceId()).size() == 1 &&
TroubleshootUtils.computePath(completePath, trace, outputPath.getOutputPort())) {
trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
trace.setSuccess(false);
}
} else if (!Collections.disjoint(hostsList, hosts)) {
//If the two host collections contain the same item it means we reached the proper output
log.debug("Stopping here because host is expected destination, reached through {}", completePath);
if (TroubleshootUtils.computePath(completePath, trace, outputPath.getOutputPort())) {
trace.addResultMessage("Reached required destination Host " + cp);
trace.setSuccess(true);
}
break;
} else if (cp.port().equals(PortNumber.CONTROLLER)) {
//Getting the master when the packet gets sent as packet in
NodeId master = mastershipNib.getMasterFor(cp.deviceId());
// TODO if we don't need to print master node id, exclude mastership NIB which is used only here
trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
TroubleshootUtils.computePath(completePath, trace, outputPath.getOutputPort());
} else if (linkNib.getEgressLinks(cp).size() > 0) {
//TODO this can be optimized if we use a Tree structure for paths.
//if we already have outputs let's check if the one we are considering starts from one of the devices
// in any of the ones we have.
if (trace.getCompletePaths().size() > 0) {
ConnectPoint inputForOutput = null;
List<ConnectPoint> previousPath = new ArrayList<>();
for (List<ConnectPoint> path : trace.getCompletePaths()) {
for (ConnectPoint connect : path) {
//if the path already contains the input for the output we've found we use it
if (connect.equals(in)) {
inputForOutput = connect;
previousPath = path;
break;
}
}
}
//we use the pre-existing path up to the point we fork to a new output
if (inputForOutput != null && completePath.contains(inputForOutput)) {
List<ConnectPoint> temp = new ArrayList<>(previousPath);
temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
if (completePath.containsAll(temp)) {
completePath = temp;
}
}
}
//let's add the ouput for the input
completePath.add(cp);
//let's compute the links for the given output
Set<Link> links = linkNib.getEgressLinks(cp);
log.debug("Egress Links {}", links);
//For each link we trace the corresponding device
for (Link link : links) {
ConnectPoint dst = link.dst();
//change in-port to the dst link in port
Builder updatedPacket = DefaultTrafficSelector.builder();
outputPath.getEgressPacket().criteria().forEach(updatedPacket::add);
updatedPacket.add(Criteria.matchInPort(dst.port()));
log.debug("DST Connect Point {}", dst);
//build the elements for that device
traceInDevice(trace, updatedPacket.build(), dst, isDualHomed, completePath);
//continue the trace along the path
getTrace(completePath, dst, trace, isDualHomed);
}
} else if (edgePortNib.isEdgePoint(outputPath.getOutputPort()) &&
trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
.mac().isMulticast()) {
trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutputPort() +
" which is enabled and is edge port");
trace.setSuccess(true);
TroubleshootUtils.computePath(completePath, trace, outputPath.getOutputPort());
if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutputPort())) {
return trace;
}
} else if (deviceNib.getPort(cp) != null && deviceNib.getPort(cp).isEnabled()) {
EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.ETH_TYPE);
//We treat as correct output only if it's not LLDP or BDDP
if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
&& !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
if (TroubleshootUtils.computePath(completePath, trace, outputPath.getOutputPort())) {
if (hostsList.isEmpty()) {
trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getEgressPacket()
.getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
cp + " with no hosts connected ");
} else {
IpAddress ipAddress = null;
if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
ipAddress = ((IPCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.IPV4_DST)).ip().address();
} else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
ipAddress = ((IPCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.IPV6_DST)).ip().address();
}
if (ipAddress != null) {
IpAddress finalIpAddress = ipAddress;
if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
hostNib.getHostsByIp(finalIpAddress).isEmpty()) {
trace.addResultMessage("Packet is " +
((EthTypeCriterion) outputPath.getEgressPacket()
.getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
" and reached " + cp + " with hosts " + hostsList);
} else {
trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
ipAddress);
trace.setSuccess(false);
}
} else {
trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getEgressPacket()
.getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
cp + " with hosts " + hostsList);
}
}
trace.setSuccess(true);
}
}
} else {
TroubleshootUtils.computePath(completePath, trace, cp);
trace.setSuccess(false);
if (deviceNib.getPort(cp) == null) {
//Port is not existent on device.
log.warn("Port {} is not available on device.", cp);
trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
} else {
//No links means that the packet gets dropped.
log.warn("No links out of {}", cp);
trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
}
}
}
return trace;
}
/**
* Checks if the device has other outputs than the given connect point.
*
* @param inDeviceId the device
* @param trace the trace we are building
* @param cp an output connect point
* @return true if the device has other outputs.
*/
private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
return trace.getHitChains(inDeviceId).stream().filter(groupsInDevice ->
!groupsInDevice.getOutputPort().equals(cp)).count() > 0;
}
/**
* Checks if the path contains the device.
*
* @param completePath the path
* @param deviceId the device to check
* @return true if the path contains the device
*/
//TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
for (ConnectPoint cp : completePath) {
if (cp.deviceId().equals(deviceId)) {
return true;
}
}
return false;
}
/**
* Gets the hosts for the given initial packet.
*
* @param trace the trace we are building
* @return set of the hosts we are trying to reach
*/
private Set<Host> getHosts(StaticPacketTrace trace) {
IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.IPV4_DST));
IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.IPV6_DST));
Set<Host> hosts = new HashSet<>();
if (ipv4Criterion != null) {
hosts.addAll(hostNib.getHostsByIp(ipv4Criterion.ip().address()));
}
if (ipv6Criterion != null) {
hosts.addAll(hostNib.getHostsByIp(ipv6Criterion.ip().address()));
}
EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.ETH_DST));
if (ethCriterion != null) {
hosts.addAll(hostNib.getHostsByMac(ethCriterion.mac()));
}
return hosts;
}
/**
* Traces the packet inside a device starting from an input connect point.
*
* @param trace the trace we are building
* @param packet the packet we are tracing
* @param in the input connect point.
* @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
* @param completePath the path up until this device
* @return updated trace
*/
private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
boolean isDualHomed, List<ConnectPoint> completePath) {
// Get the behavior - do not proceed if there is no PipelineMatchable for the given device
PipelineTraceable pipelineMatchable = getPipelineMatchable(in.deviceId());
if (pipelineMatchable == null) {
trace.addResultMessage("No PipelineMatchable behavior for " + in.deviceId() + ". Aborting");
TroubleshootUtils.computePath(completePath, trace, null);
trace.setSuccess(false);
return trace;
}
// Verify the presence of multiple routes - if the device has been visited in the past
boolean multipleRoutes = false;
if (trace.getHitChains(in.deviceId()) != null) {
multipleRoutes = multipleRoutes(trace);
}
if (trace.getHitChains(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
log.debug("Trace already contains device and given outputs");
return trace;
}
log.debug("Packet {} coming in from {}", packet, in);
//if device is not available exit here.
if (!deviceNib.isAvailable(in.deviceId())) {
trace.addResultMessage("Device is offline " + in.deviceId());
TroubleshootUtils.computePath(completePath, trace, null);
return trace;
}
// Handle when the input is the controller.
// NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
// a packet in from the controller will not actually traverse the pipeline and have no such notion
// as the input port.
if (in.port().equals(PortNumber.CONTROLLER)) {
StaticPacketTrace outputTrace = inputFromController(trace, in);
if (outputTrace != null) {
return trace;
}
}
// Get the device state in the form of DataPlaneEntity objects - do not proceed if there is no state
List<DataPlaneEntity> dataPlaneEntities = getDataPlaneEntities(in.deviceId());
if (dataPlaneEntities.isEmpty()) {
trace.addResultMessage("No device state for " + in.deviceId() + ". Aborting");
TroubleshootUtils.computePath(completePath, trace, null);
trace.setSuccess(false);
return trace;
}
// Applies pipeline processing
PipelineTraceableInput input = new PipelineTraceableInput(packet, in, dataPlaneEntities);
PipelineTraceableOutput output = pipelineMatchable.apply(input);
// Update the trace
List<PipelineTraceableHitChain> hitChains = output.getHitChains();
hitChains.forEach(hitChain -> trace.addHitChain(in.deviceId(), hitChain));
trace.addResultMessage(output.getLog());
// If there was an error set the success to false
if (output.getResult() != PipelineTraceableResult.SUCCESS) {
TroubleshootUtils.computePath(completePath, trace, null);
trace.setSuccess(false);
}
log.info("Logs -> {}", output.getLog());
hitChains.forEach(hitChain -> log.info("HitChain -> {}", hitChain));
// We are done!
return trace;
}
// Compute whether or not there are multiple routes.
private boolean multipleRoutes(StaticPacketTrace trace) {
boolean multipleRoutes = false;
IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
IpAddress ip = null;
if (ipCriterion != null) {
ip = ipCriterion.ip().address();
} else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
}
if (ip != null) {
Optional<ResolvedRoute> optionalRoute = routeNib.longestPrefixLookup(ip);
if (optionalRoute.isPresent()) {
ResolvedRoute route = optionalRoute.get();
multipleRoutes = routeNib.getAllResolvedRoutes(route.prefix()).size() > 1;
}
}
return multipleRoutes;
}
/**
* Handles the specific case where the Input is the controller.
* Note that the in port is used as a convenience to store the port of the controller even if the packet in
* from a controller should not have a physical input port. The in port from the Controller is used to make sure
* the flood to all active physical ports of the device.
*
* @param trace the trace
* @param in the controller port
* @return the augmented trace.
*/
private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
.getCriterion(Criterion.Type.ETH_TYPE);
//If the packet is LLDP or BDDP we flood it on all active ports of the switch.
if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
|| ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
//get the active ports
List<Port> enabledPorts = deviceNib.getPorts(in.deviceId()).stream()
.filter(Port::isEnabled)
.collect(Collectors.toList());
//build an output from each one
enabledPorts.forEach(port -> {
PipelineTraceableHitChain hitChain = new PipelineTraceableHitChain(
new ConnectPoint(port.element().id(), port.number()), ImmutableList.of(),
trace.getInitialPacket());
trace.addHitChain(in.deviceId(), hitChain);
});
return trace;
}
return null;
}
////////////////////////////////
// Cemetery - Deprecated code //
////////////////////////////////
@Override
public List<StaticPacketTrace> pingAll(EtherType type) {
ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
hostNib.getHosts().forEach(host -> {
List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
if (ipAddresses.size() > 0) {
//check if the host has only local IPs of that ETH type
boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
hostNib.getHosts().forEach(hostToPing -> {
List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
//check if the other host has only local IPs of that ETH type
boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
//Trace is done only if they are both local and under the same location
// or not local and if they are not the same host.
if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
(!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
&& !host.equals(hostToPing)) {
tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
}
});
}
});
return tracesBuilder.build();
}
@Override
public List<Set<StaticPacketTrace>> getMulitcastTrace(VlanId vlanId) {
Generator<Set<StaticPacketTrace>> gen = new McastGenerator(mcastRouteNib, this, vlanId);
List<Set<StaticPacketTrace>> multicastTraceList =
StreamSupport.stream(gen.spliterator(), false).collect(Collectors.toList());
return multicastTraceList;
}
}