package org.batfish.question;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.batfish.common.Answerer;
import org.batfish.common.BatfishException;
import org.batfish.common.plugin.IBatfish;
import org.batfish.datamodel.Protocol;
import org.batfish.datamodel.Configuration;
import org.batfish.datamodel.Flow;
import org.batfish.datamodel.FlowBuilder;
import org.batfish.datamodel.FlowHistory;
import org.batfish.datamodel.FlowTrace;
import org.batfish.datamodel.Ip;
import org.batfish.datamodel.IpProtocol;
import org.batfish.datamodel.State;
import org.batfish.datamodel.answers.AnswerElement;
import org.batfish.datamodel.questions.ITracerouteQuestion;
import org.batfish.datamodel.questions.Question;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TracerouteQuestionPlugin extends QuestionPlugin {
public static class TracerouteAnswerer extends Answerer {
public TracerouteAnswerer(Question question, IBatfish batfish) {
super(question, batfish);
}
@Override
public AnswerElement answer() {
_batfish.checkDataPlaneQuestionDependencies();
String tag = _batfish.getFlowTag();
Set<Flow> flows = getFlows(tag);
_batfish.processFlows(flows);
AnswerElement answerElement = _batfish.getHistory();
return answerElement;
}
@Override
public AnswerElement answerDiff() {
String tag = _batfish.getDifferentialFlowTag();
Set<Flow> flows = getFlows(tag);
_batfish.pushBaseEnvironment();
_batfish.processFlows(flows);
_batfish.popEnvironment();
_batfish.pushDeltaEnvironment();
_batfish.processFlows(flows);
_batfish.popEnvironment();
FlowHistory history = _batfish.getHistory();
FlowHistory filteredHistory = new FlowHistory();
for (String flowText : history.getFlowsByText().keySet()) {
// String baseEnvId = _batfish.getBaseTestrigSettings().getName() +
// ":"
// + _batfish.getBaseTestrigSettings().getEnvironmentSettings()
// .getName();
_batfish.pushBaseEnvironment();
String baseEnvId = _batfish.getFlowTag();
_batfish.popEnvironment();
// String deltaEnvId = _batfish.getDeltaTestrigSettings().getName()
// +
// ":"
// + _batfish.getDeltaTestrigSettings().getEnvironmentSettings()
// .getName();
_batfish.pushDeltaEnvironment();
String deltaEnvId = _batfish.getFlowTag();
_batfish.popEnvironment();
Set<FlowTrace> baseFlowTraces = history.getTraces().get(flowText)
.get(baseEnvId);
Set<FlowTrace> deltaFlowTraces = history.getTraces().get(flowText)
.get(deltaEnvId);
if (!baseFlowTraces.toString().equals(deltaFlowTraces.toString())) {
Flow flow = history.getFlowsByText().get(flowText);
for (FlowTrace flowTrace : baseFlowTraces) {
filteredHistory.addFlowTrace(flow, baseEnvId, flowTrace);
}
for (FlowTrace flowTrace : deltaFlowTraces) {
filteredHistory.addFlowTrace(flow, deltaEnvId, flowTrace);
}
}
}
return filteredHistory;
}
private Set<Flow> getFlows(String tag) {
Set<Flow> flows = new TreeSet<>();
TracerouteQuestion question = (TracerouteQuestion) _question;
Set<FlowBuilder> flowBuilders = question.getFlowBuilders();
Map<String, Configuration> configurations = null;
for (FlowBuilder flowBuilder : flowBuilders) {
// TODO: better automatic source ip, considering VRFs and routing
if (flowBuilder.getSrcIp().equals(Ip.AUTO)) {
if (configurations == null) {
_batfish.pushBaseEnvironment();
_batfish.checkConfigurations();
configurations = _batfish.loadConfigurations();
_batfish.popEnvironment();
}
String hostname = flowBuilder.getIngressNode();
Configuration node = (hostname == null) ? null
: configurations.get(hostname);
if (node != null) {
Set<Ip> ips = new TreeSet<>(node.getVrfs().values().stream()
.flatMap(v -> v.getInterfaces().values().stream())
.flatMap(i -> i.getAllPrefixes().stream())
.map(prefix -> prefix.getAddress())
.collect(Collectors.toSet()));
if (!ips.isEmpty()) {
Ip lowestIp = ips.toArray(new Ip[] {})[0];
flowBuilder.setSrcIp(lowestIp);
}
else {
throw new BatfishException(
"Cannot automatically assign source ip to flow since no there are no ip addresses assigned to any interface on ingress node: '"
+ hostname + "'");
}
}
else {
throw new BatfishException(
"Cannot create flow with non-existent ingress node: '"
+ hostname + "'");
}
}
flowBuilder.setTag(tag);
Flow flow = flowBuilder.build();
flows.add(flow);
}
return flows;
}
}
// <question_page_comment>
/**
* Perform a traceroute.
* <p>
* This question performs a virtual traceroute in the network from a starting
* node. The destination IP is randomly picked if not explicitly specified.
* Other IP headers are also randomly picked if unspecified, with a bias
* toward generating packets similar to a real traceroute (see below).
* <p>
* Unlike a real traceroute, this traceroute is directional. That is, for it
* to succeed, the reverse connectivity is not needed. This feature can help
* debug connectivity issues by decoupling the two directions.
*
* @type Traceroute dataplane
*
* @param ingressNode
* Name of the node where the traceroute should be done from. This
* parameter is mandatory and has no default value.
* @param ingressVrf
* Name of the VRF to use on the ingress node. If unspecified, uses
* the default VRF.
* @param dscp
* Details coming
* @param dstIp
* Destination IP for the traceroute. The default is to pick one
* randomly.
* @param dstPort
* Destination port for the traceroute. The default is Details
* coming.
* @param ecn
* Details coming
* @param icmpCode
* Details coming
* @param icmpType
* Details coming
* @param ipProtocol
* Details coming
* @param srcIp
* Details coming
* @param srcPort
* Details coming
* @param stateVar
* Details coming
* @param tcpAck
* Details coming
* @param tcpAck
* Details coming
* @param tcpAck
* Details coming
* @param tcpCwr
* Details coming
* @param tcpEce
* Details coming
* @param tcpFin
* Details coming
* @param tcpPsh
* Details coming
* @param tcpRst
* Details coming
* @param tcpSyn
* Details coming
* @param tcpUrg
* Details coming
*
* @example bf_answer("Traceroute", ingressNode="as2border1",
* dstIp="2.128.0.101", dstPort=53, ipProtocol="UDP") Show the path
* of a DNS packet (UDP to port 53) from as2border1
*/
public static class TracerouteQuestion extends Question
implements ITracerouteQuestion {
private static final String DSCP_VAR = "dscp";
private static final String DST_IP_VAR = "dstIp";
private static final String DST_PORT_VAR = "dstPort";
private static final String DST_PROTOCOL_VAR = "dstProtocol";
private static final String ECN_VAR = "ecn";
private static final String ICMP_CODE_VAR = "icmpCode";
private static final String ICMP_TYPE_VAR = "icmpType";
private static final String INGRESS_NODE_VAR = "ingressNode";
private static final String INGRESS_VRF_VAR = "ingressVrf";
private static final String IP_PROTOCOL_VAR = "ipProtocol";
private static final String PACKET_LENGTH_VAR = "packetLength";
private static final String SRC_IP_VAR = "srcIp";
private static final String SRC_PORT_VAR = "srcPort";
private static final String SRC_PROTOCOL_VAR = "srcProtocol";
private static final String STATE_VAR = "state";
private static final String TCP_FLAGS_ACK_VAR = "tcpAck";
private static final String TCP_FLAGS_CWR_VAR = "tcpCwr";
private static final String TCP_FLAGS_ECE_VAR = "tcpEce";
private static final String TCP_FLAGS_FIN_VAR = "tcpFin";
private static final String TCP_FLAGS_PSH_VAR = "tcpPsh";
private static final String TCP_FLAGS_RST_VAR = "tcpRst";
private static final String TCP_FLAGS_SYN_VAR = "tcpSyn";
private static final String TCP_FLAGS_URG_VAR = "tcpUrg";
private Integer _dscp;
private Ip _dstIp;
private Integer _dstPort;
private Protocol _dstProtocol;
private Integer _ecn;
private Integer _icmpCode;
private Integer _icmpType;
private String _ingressNode;
private String _ingressVrf;
private IpProtocol _ipProtocol;
private Integer _packetLength;
private Ip _srcIp;
private Integer _srcPort;
private Protocol _srcProtocol;
private State _state;
private Boolean _tcpFlagsAck;
private Boolean _tcpFlagsCwr;
private Boolean _tcpFlagsEce;
private Boolean _tcpFlagsFin;
private Boolean _tcpFlagsPsh;
private Boolean _tcpFlagsRst;
private Boolean _tcpFlagsSyn;
private Boolean _tcpFlagsUrg;
public TracerouteQuestion() {
}
public FlowBuilder createFlowBuilder() {
FlowBuilder flowBuilder = new FlowBuilder();
if (_dscp != null) {
flowBuilder.setDscp(_dscp);
}
if (_dstIp != null) {
flowBuilder.setDstIp(_dstIp);
}
if (_dstPort != null) {
flowBuilder.setDstPort(_dstPort);
}
if (_ecn != null) {
flowBuilder.setEcn(_ecn);
}
if (_icmpCode != null) {
flowBuilder.setIcmpCode(_icmpCode);
}
if (_icmpType != null) {
flowBuilder.setIcmpType(_icmpType);
}
if (_ingressNode != null) {
flowBuilder.setIngressNode(_ingressNode);
}
if (_ingressVrf != null) {
flowBuilder.setIngressVrf(_ingressVrf);
}
if (_ipProtocol != null) {
flowBuilder.setIpProtocol(_ipProtocol);
}
if (_packetLength != null) {
flowBuilder.setPacketLength(_packetLength);
}
if (_srcIp != null) {
flowBuilder.setSrcIp(_srcIp);
}
else {
flowBuilder.setSrcIp(Ip.AUTO);
}
if (_srcPort != null) {
flowBuilder.setSrcPort(_srcPort);
}
if (_state != null) {
flowBuilder.setState(_state);
}
if (_tcpFlagsAck != null) {
flowBuilder.setTcpFlagsAck(_tcpFlagsAck ? 1 : 0);
}
if (_tcpFlagsCwr != null) {
flowBuilder.setTcpFlagsCwr(_tcpFlagsCwr ? 1 : 0);
}
if (_tcpFlagsEce != null) {
flowBuilder.setTcpFlagsEce(_tcpFlagsEce ? 1 : 0);
}
if (_tcpFlagsFin != null) {
flowBuilder.setTcpFlagsFin(_tcpFlagsFin ? 1 : 0);
}
if (_tcpFlagsPsh != null) {
flowBuilder.setTcpFlagsPsh(_tcpFlagsPsh ? 1 : 0);
}
if (_tcpFlagsRst != null) {
flowBuilder.setTcpFlagsRst(_tcpFlagsRst ? 1 : 0);
}
if (_tcpFlagsSyn != null) {
flowBuilder.setTcpFlagsSyn(_tcpFlagsSyn ? 1 : 0);
}
if (_tcpFlagsUrg != null) {
flowBuilder.setTcpFlagsUrg(_tcpFlagsUrg ? 1 : 0);
}
// do not move src or dst protocol up
if (_srcProtocol != null) {
IpProtocol ipProtocol = _srcProtocol.getIpProtocol();
flowBuilder.setIpProtocol(ipProtocol);
Integer port = _srcProtocol.getPort();
if (port != null) {
flowBuilder.setSrcPort(port);
}
}
if (_dstProtocol != null) {
IpProtocol ipProtocol = _dstProtocol.getIpProtocol();
flowBuilder.setIpProtocol(ipProtocol);
Integer port = _dstProtocol.getPort();
if (port != null) {
flowBuilder.setDstPort(port);
}
}
return flowBuilder;
}
@Override
public boolean getDataPlane() {
return true;
}
@JsonProperty(DSCP_VAR)
public Integer getDscp() {
return _dscp;
}
@JsonProperty(DST_IP_VAR)
public Ip getDstIp() {
return _dstIp;
}
@JsonProperty(DST_PORT_VAR)
public Integer getDstPort() {
return _dstPort;
}
@JsonProperty(DST_PROTOCOL_VAR)
public Protocol getDstProtocol() {
return _dstProtocol;
}
@JsonProperty(ECN_VAR)
public Integer getEcn() {
return _ecn;
}
@JsonIgnore
public Set<FlowBuilder> getFlowBuilders() {
return Collections.singleton(createFlowBuilder());
}
@JsonProperty(ICMP_CODE_VAR)
public Integer getIcmpCode() {
return _icmpCode;
}
@JsonProperty(ICMP_TYPE_VAR)
public Integer getIcmpType() {
return _icmpType;
}
@JsonProperty(INGRESS_NODE_VAR)
public String getIngressNode() {
return _ingressNode;
}
@JsonProperty(INGRESS_VRF_VAR)
public String getIngressVrf() {
return _ingressVrf;
}
@JsonProperty(IP_PROTOCOL_VAR)
public IpProtocol getIpProtocol() {
return _ipProtocol;
}
@Override
public String getName() {
return NAME;
}
@JsonProperty(PACKET_LENGTH_VAR)
public Integer getPacketLength() {
return _packetLength;
}
@JsonProperty(SRC_IP_VAR)
public Ip getSrcIp() {
return _srcIp;
}
@JsonProperty(SRC_PORT_VAR)
public Integer getSrcPort() {
return _srcPort;
}
@JsonProperty(SRC_PROTOCOL_VAR)
public Protocol getSrcProtocol() {
return _srcProtocol;
}
@JsonProperty(STATE_VAR)
public State getState() {
return _state;
}
@JsonProperty(TCP_FLAGS_ACK_VAR)
public Boolean getTcpFlagsAck() {
return _tcpFlagsAck;
}
@JsonProperty(TCP_FLAGS_CWR_VAR)
public Boolean getTcpFlagsCwr() {
return _tcpFlagsCwr;
}
@JsonProperty(TCP_FLAGS_ECE_VAR)
public Boolean getTcpFlagsEce() {
return _tcpFlagsEce;
}
@JsonProperty(TCP_FLAGS_FIN_VAR)
public Boolean getTcpFlagsFin() {
return _tcpFlagsFin;
}
@JsonProperty(TCP_FLAGS_PSH_VAR)
public Boolean getTcpFlagsPsh() {
return _tcpFlagsPsh;
}
@JsonProperty(TCP_FLAGS_RST_VAR)
public Boolean getTcpFlagsRst() {
return _tcpFlagsRst;
}
@JsonProperty(TCP_FLAGS_SYN_VAR)
public Boolean getTcpFlagsSyn() {
return _tcpFlagsSyn;
}
@JsonProperty(TCP_FLAGS_URG_VAR)
public Boolean getTcpFlagsUrg() {
return _tcpFlagsUrg;
}
@Override
public boolean getTraffic() {
return true;
}
@Override
public String prettyPrint() {
try {
String retString = String.format("traceroute %singressNode=%s",
prettyPrintBase(), _ingressNode);
// we only print "interesting" values
if (_ingressVrf != null) {
retString += String.format(" | %s=%s", INGRESS_VRF_VAR,
_ingressVrf);
}
if (_dscp != null) {
retString += String.format(" | %s=%s", DSCP_VAR, _dscp);
}
if (_dstIp != null) {
retString += String.format(" | %s=%s", DST_IP_VAR, _dstIp);
}
if (_dstPort != null) {
retString += String.format(" | %S=%s", DST_PORT_VAR, _dstPort);
}
if (_dstProtocol != null) {
retString += String.format(" | %s=%s", DST_PROTOCOL_VAR,
_dstProtocol);
}
if (_ecn != null) {
retString += String.format(" | %s=%s", ECN_VAR, _ecn);
}
if (_icmpCode != null) {
retString += String.format(" | %s=%s", ICMP_CODE_VAR, _icmpCode);
}
if (_icmpType != null) {
retString += String.format(" | %s=%s", ICMP_TYPE_VAR, _icmpType);
}
if (_ipProtocol != null) {
retString += String.format(" | %s=%s", IP_PROTOCOL_VAR,
_ipProtocol);
}
if (_packetLength != null) {
retString += String.format(" | %s=%s", PACKET_LENGTH_VAR,
_packetLength);
}
if (_srcIp != null) {
retString += String.format(" | %s=%s", SRC_IP_VAR, _srcIp);
}
if (_srcPort != null) {
retString += String.format(" | %s=%s", SRC_PORT_VAR, _srcPort);
}
if (_srcProtocol != null) {
retString += String.format(" | %s=%s", SRC_PROTOCOL_VAR,
_srcProtocol);
}
if (_state != null) {
retString += String.format(" | %s=%s", STATE_VAR, _state);
}
if (_tcpFlagsAck != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_ACK_VAR,
_tcpFlagsAck);
}
if (_tcpFlagsCwr != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_CWR_VAR,
_tcpFlagsCwr);
}
if (_tcpFlagsEce != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_ECE_VAR,
_tcpFlagsEce);
}
if (_tcpFlagsFin != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_FIN_VAR,
_tcpFlagsFin);
}
if (_tcpFlagsPsh != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_PSH_VAR,
_tcpFlagsPsh);
}
if (_tcpFlagsRst != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_RST_VAR,
_tcpFlagsRst);
}
if (_tcpFlagsSyn != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_SYN_VAR,
_tcpFlagsSyn);
}
if (_tcpFlagsUrg != null) {
retString += String.format(" | %s=%s", TCP_FLAGS_URG_VAR,
_tcpFlagsUrg);
}
return retString;
}
catch (Exception e) {
try {
return "Pretty printing failed. Printing Json\n"
+ toJsonString();
}
catch (BatfishException e1) {
throw new BatfishException(
"Both pretty and json printing failed\n");
}
}
}
@JsonProperty(DSCP_VAR)
public void setDscp(Integer dscp) {
_dscp = dscp;
}
@Override
@JsonProperty(DST_IP_VAR)
public void setDstIp(Ip dstIp) {
_dstIp = dstIp;
}
@JsonProperty(DST_PORT_VAR)
public void setDstPort(Integer dstPort) {
_dstPort = dstPort;
}
@Override
@JsonProperty(DST_PROTOCOL_VAR)
public void setDstProtocol(Protocol dstProtocol) {
_dstProtocol = dstProtocol;
}
@JsonProperty(ECN_VAR)
public void setEcn(Integer ecn) {
_ecn = ecn;
}
@JsonProperty(ICMP_CODE_VAR)
public void setIcmpCode(Integer icmpCode) {
_icmpCode = icmpCode;
}
@JsonProperty(ICMP_TYPE_VAR)
public void setIcmpType(Integer icmpType) {
_icmpType = icmpType;
}
@Override
@JsonProperty(INGRESS_NODE_VAR)
public void setIngressNode(String ingressNode) {
_ingressNode = ingressNode;
}
@Override
@JsonProperty(INGRESS_VRF_VAR)
public void setIngressVrf(String ingressVrf) {
_ingressVrf = ingressVrf;
}
@JsonProperty(IP_PROTOCOL_VAR)
public void setIpProtocol(IpProtocol ipProtocol) {
_ipProtocol = ipProtocol;
}
@JsonProperty(PACKET_LENGTH_VAR)
public void setPacketLength(Integer packetLength) {
_packetLength = packetLength;
}
@JsonProperty(SRC_IP_VAR)
public void setSrcIp(Ip srcIp) {
_srcIp = srcIp;
}
@JsonProperty(SRC_PORT_VAR)
public void setSrcPort(Integer srcPort) {
_srcPort = srcPort;
}
@JsonProperty(SRC_PROTOCOL_VAR)
public void setSrcProtocol(Protocol srcProtocol) {
_srcProtocol = srcProtocol;
}
@JsonProperty(STATE_VAR)
public void setState(State state) {
_state = state;
}
@JsonProperty(TCP_FLAGS_ACK_VAR)
public void setTcpFlagsAck(Boolean tcpFlagsAck) {
_tcpFlagsAck = tcpFlagsAck;
}
@JsonProperty(TCP_FLAGS_CWR_VAR)
public void setTcpFlagsCwr(Boolean tcpFlagsCwr) {
_tcpFlagsCwr = tcpFlagsCwr;
}
@JsonProperty(TCP_FLAGS_ECE_VAR)
public void setTcpFlagsEce(Boolean tcpFlagsEce) {
_tcpFlagsEce = tcpFlagsEce;
}
@JsonProperty(TCP_FLAGS_FIN_VAR)
public void setTcpFlagsFin(Boolean tcpFlagsFin) {
_tcpFlagsFin = tcpFlagsFin;
}
@JsonProperty(TCP_FLAGS_PSH_VAR)
public void setTcpFlagsPsh(Boolean tcpFlagsPsh) {
_tcpFlagsPsh = tcpFlagsPsh;
}
@JsonProperty(TCP_FLAGS_RST_VAR)
public void setTcpFlagsRst(Boolean tcpFlagsRst) {
_tcpFlagsRst = tcpFlagsRst;
}
@JsonProperty(TCP_FLAGS_SYN_VAR)
public void setTcpFlagsSyn(Boolean tcpFlagsSyn) {
_tcpFlagsSyn = tcpFlagsSyn;
}
@JsonProperty(TCP_FLAGS_URG_VAR)
public void setTcpFlagsUrg(Boolean tcpFlagsUrg) {
_tcpFlagsUrg = tcpFlagsUrg;
}
}
@Override
protected Answerer createAnswerer(Question question, IBatfish batfish) {
return new TracerouteAnswerer(question, batfish);
}
@Override
protected Question createQuestion() {
return new TracerouteQuestion();
}
}