package org.batfish.question; import java.util.Collections; import java.util.SortedSet; import java.util.TreeSet; import org.batfish.common.Answerer; import org.batfish.common.BatfishException; import org.batfish.common.plugin.IBatfish; import org.batfish.datamodel.ForwardingAction; import org.batfish.datamodel.HeaderSpace; import org.batfish.datamodel.IpProtocol; import org.batfish.datamodel.IpWildcard; import org.batfish.datamodel.Protocol; import org.batfish.datamodel.ReachabilityType; import org.batfish.datamodel.SubRange; import org.batfish.datamodel.answers.AnswerElement; import org.batfish.datamodel.questions.IReachabilityQuestion; import org.batfish.datamodel.questions.Question; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; public class ReachabilityQuestionPlugin extends QuestionPlugin { public static class ReachabilityAnswerer extends Answerer { public ReachabilityAnswerer(Question question, IBatfish batfish) { super(question, batfish); } @Override public AnswerElement answer() { ReachabilityQuestion question = (ReachabilityQuestion) _question; ReachabilityType type = question.getReachabilityType(); switch (type) { case MULTIPATH: return multipath(question); case STANDARD: return standard(question); case PATH_DIFF: case REDUCED_REACHABILITY: case INCREASED: case MULTIPATH_DIFF: default: throw new BatfishException( "Unsupported non-differential reachabilty type: " + type.reachabilityTypeName()); } } @Override public AnswerElement answerDiff() { ReachabilityQuestion question = (ReachabilityQuestion) _question; ReachabilityType type = question.getReachabilityType(); switch (type) { case PATH_DIFF: return pathDiff(question); case REDUCED_REACHABILITY: return reducedReachability(question); case STANDARD: case MULTIPATH: case INCREASED: case MULTIPATH_DIFF: default: throw new BatfishException( "Unsupported differential reachabilty type: " + type.reachabilityTypeName()); } } private AnswerElement multipath(ReachabilityQuestion question) { return _batfish.multipath(question.getHeaderSpace()); } private AnswerElement pathDiff(ReachabilityQuestion question) { return _batfish.pathDiff(question.getHeaderSpace()); } private AnswerElement reducedReachability(ReachabilityQuestion question) { return _batfish.reducedReachability(question.getHeaderSpace()); } private AnswerElement standard(ReachabilityQuestion question) { return _batfish.standard(question.getHeaderSpace(), question.getActions(), question.getIngressNodeRegex(), question.getNotIngressNodeRegex(), question.getFinalNodeRegex(), question.getNotFinalNodeRegex()); } } // <question_page_comment> /** * Details coming. * <p> * More details coming. * * @type Reachability dataplane * * @param DetailsComing * Details coming. * * @example bf_answer("Reachability", dstIps=["2.128.0.101"], dstPorts=[53], * ipProtocols=["UDP"], actions=["drop"]) Finds all (starting node, * packet header) combinations that cannot reach (action=drop) the * 2.128.0.101 using a DNS (UDP on port 53) packet. * @example bf_answer_type("Reachability", actions=["ACCEPT"], * dstIps=["2.128.1.101"], notDstPorts=[22], notIpProtocols=["TCP"]) * Finds all (starting node, packet header) combinations that can * reach (action=drop) 2.128.1.101 using non-SSH packets. */ public static class ReachabilityQuestion extends Question implements IReachabilityQuestion { private static final String ACTIONS_VAR = "actions"; private static final String DEFAULT_FINAL_NODE_REGEX = ".*"; private static final String DEFAULT_INGRESS_NODE_REGEX = ".*"; private static final String DEFAULT_NOT_FINAL_NODE_REGEX = ""; private static final String DEFAULT_NOT_INGRESS_NODE_REGEX = ""; private static final String DST_IPS_VAR = "dstIps"; private static final String DST_PORTS_VAR = "dstPorts"; private static final String DST_PROTOCOLS_VAR = "dstProtocols"; private static final String FINAL_NODE_REGEX_VAR = "finalNodeRegex"; private static final String FRAGMENT_OFFSETS_VAR = "fragmentOffsets"; private static final String ICMP_CODES_VAR = "icmpCodes"; private static final String ICMP_TYPES_VAR = "icmpTypes"; private static final String INGRESS_NODE_REGEX_VAR = "ingressNodeRegex"; private static final String IP_PROTOCOLS_VAR = "ipProtocols"; private static final String NEGATE_HEADER_VAR = "negateHeader"; private static final String NOT_DST_IPS_VAR = "notDstIps"; private static final String NOT_DST_PORTS_VAR = "notDstPorts"; private static final String NOT_DST_PROTOCOLS_VAR = "notDstProtocols"; private static final String NOT_FINAL_NODE_REGEX_VAR = "notFinalNodeRegex"; private static final String NOT_FRAGMENT_OFFSETS_VAR = "notFragmentOffsets"; private static final String NOT_ICMP_CODES_VAR = "notIcmpCodes"; private static final String NOT_ICMP_TYPES_VAR = "notIcmpTypes"; private static final String NOT_INGRESS_NODE_REGEX_VAR = "notIngressNodeRegex"; private static final String NOT_IP_PROTOCOLS_VAR = "notIpProtocols"; private static final String NOT_PACKET_LENGTHS_VAR = "notPacketLengths"; private static final String NOT_SRC_IPS_VAR = "notSrcIps"; private static final String NOT_SRC_PORTS_VAR = "notSrcPorts"; private static final String NOT_SRC_PROTOCOLS_VAR = "notSrcProtocols"; private static final String PACKET_LENGTHS_VAR = "packetLengths"; private static final String REACHABILITY_TYPE_VAR = "type"; private static final String SRC_IPS_VAR = "srcIps"; private static final String SRC_OR_DST_IPS_VAR = "srcOrDstIps"; private static final String SRC_OR_DST_PORTS_VAR = "srcOrDstPorts"; private static final String SRC_OR_DST_PROTOCOLS_VAR = "srcOrDstProtocols"; private static final String SRC_PORTS_VAR = "srcPorts"; private static final String SRC_PROTOCOLS_VAR = "srcProtocols"; private SortedSet<ForwardingAction> _actions; private String _finalNodeRegex; private final HeaderSpace _headerSpace; private String _ingressNodeRegex; private String _notFinalNodeRegex; private String _notIngressNodeRegex; private ReachabilityType _reachabilityType; public ReachabilityQuestion() { _actions = new TreeSet<>( Collections.singleton(ForwardingAction.ACCEPT)); _finalNodeRegex = DEFAULT_FINAL_NODE_REGEX; _headerSpace = new HeaderSpace(); _ingressNodeRegex = DEFAULT_INGRESS_NODE_REGEX; _reachabilityType = ReachabilityType.STANDARD; _notFinalNodeRegex = DEFAULT_NOT_FINAL_NODE_REGEX; _notIngressNodeRegex = DEFAULT_NOT_INGRESS_NODE_REGEX; } @JsonProperty(ACTIONS_VAR) public SortedSet<ForwardingAction> getActions() { return _actions; } @Override public boolean getDataPlane() { return true; } @JsonProperty(DST_IPS_VAR) public SortedSet<IpWildcard> getDstIps() { return _headerSpace.getDstIps(); } @JsonProperty(DST_PORTS_VAR) public SortedSet<SubRange> getDstPorts() { return _headerSpace.getDstPorts(); } @JsonProperty(DST_PROTOCOLS_VAR) public SortedSet<Protocol> getDstProtocols() { return _headerSpace.getDstProtocols(); } @JsonProperty(FINAL_NODE_REGEX_VAR) public String getFinalNodeRegex() { return _finalNodeRegex; } @JsonProperty(FRAGMENT_OFFSETS_VAR) public SortedSet<SubRange> getFragmentOffsets() { return _headerSpace.getFragmentOffsets(); } @JsonIgnore public HeaderSpace getHeaderSpace() { return _headerSpace; } @JsonProperty(ICMP_CODES_VAR) public SortedSet<SubRange> getIcmpCodes() { return _headerSpace.getIcmpCodes(); } @JsonProperty(ICMP_TYPES_VAR) public SortedSet<SubRange> getIcmpTypes() { return _headerSpace.getIcmpTypes(); } @JsonProperty(INGRESS_NODE_REGEX_VAR) public String getIngressNodeRegex() { return _ingressNodeRegex; } @JsonProperty(IP_PROTOCOLS_VAR) public SortedSet<IpProtocol> getIpProtocols() { return _headerSpace.getIpProtocols(); } @Override public String getName() { return NAME; } @JsonProperty(NEGATE_HEADER_VAR) public boolean getNegateHeader() { return _headerSpace.getNegate(); } @JsonProperty(NOT_DST_IPS_VAR) public SortedSet<IpWildcard> getNotDstIps() { return _headerSpace.getNotDstIps(); } @JsonProperty(NOT_DST_PORTS_VAR) public SortedSet<SubRange> getNotDstPorts() { return _headerSpace.getNotDstPorts(); } @JsonProperty(NOT_DST_PROTOCOLS_VAR) public SortedSet<Protocol> getNotDstProtocols() { return _headerSpace.getNotDstProtocols(); } @JsonProperty(NOT_FINAL_NODE_REGEX_VAR) public String getNotFinalNodeRegex() { return _notFinalNodeRegex; } @JsonProperty(NOT_FRAGMENT_OFFSETS_VAR) private SortedSet<SubRange> getNotFragmentOffsets() { return _headerSpace.getNotFragmentOffsets(); } @JsonProperty(NOT_ICMP_CODES_VAR) public SortedSet<SubRange> getNotIcmpCodes() { return _headerSpace.getNotIcmpCodes(); } @JsonProperty(NOT_ICMP_TYPES_VAR) public SortedSet<SubRange> getNotIcmpTypes() { return _headerSpace.getNotIcmpTypes(); } @JsonProperty(NOT_INGRESS_NODE_REGEX_VAR) public String getNotIngressNodeRegex() { return _notIngressNodeRegex; } @JsonProperty(NOT_IP_PROTOCOLS_VAR) public SortedSet<IpProtocol> getNotIpProtocols() { return _headerSpace.getNotIpProtocols(); } @JsonProperty(NOT_PACKET_LENGTHS_VAR) public SortedSet<SubRange> getNotPacketLengths() { return _headerSpace.getNotPacketLengths(); } @JsonProperty(NOT_SRC_IPS_VAR) public SortedSet<IpWildcard> getNotSrcIps() { return _headerSpace.getNotSrcIps(); } @JsonProperty(NOT_SRC_PORTS_VAR) public SortedSet<SubRange> getNotSrcPorts() { return _headerSpace.getNotSrcPorts(); } @JsonProperty(NOT_SRC_PROTOCOLS_VAR) public SortedSet<Protocol> getNotSrcProtocols() { return _headerSpace.getNotSrcProtocols(); } @JsonProperty(PACKET_LENGTHS_VAR) public SortedSet<SubRange> getPacketLengths() { return _headerSpace.getPacketLengths(); } @JsonProperty(REACHABILITY_TYPE_VAR) public ReachabilityType getReachabilityType() { return _reachabilityType; } @JsonProperty(SRC_IPS_VAR) public SortedSet<IpWildcard> getSrcIps() { return _headerSpace.getSrcIps(); } @JsonProperty(SRC_OR_DST_IPS_VAR) public SortedSet<IpWildcard> getSrcOrDstIps() { return _headerSpace.getSrcOrDstIps(); } @JsonProperty(SRC_OR_DST_PORTS_VAR) public SortedSet<SubRange> getSrcOrDstPorts() { return _headerSpace.getSrcOrDstPorts(); } @JsonProperty(SRC_OR_DST_PROTOCOLS_VAR) public SortedSet<Protocol> getSrcOrDstProtocols() { return _headerSpace.getSrcOrDstProtocols(); } @JsonProperty(SRC_PORTS_VAR) public SortedSet<SubRange> getSrcPorts() { return _headerSpace.getSrcPorts(); } @JsonProperty(SRC_PROTOCOLS_VAR) public SortedSet<Protocol> getSrcProtocols() { return _headerSpace.getSrcProtocols(); } @Override public boolean getTraffic() { return true; } @Override public String prettyPrint() { try { String retString = String.format("reachability %sactions=%s", prettyPrintBase(), _actions.toString()); // we only print "interesting" values if (_reachabilityType != ReachabilityType.STANDARD) { retString += String.format(" | %s=%s", REACHABILITY_TYPE_VAR, _reachabilityType); } if (getNegateHeader()) { retString += " | negateHeader=true"; } if (getDstIps() != null && !getDstIps().isEmpty()) { retString += String.format(" | %s=%s", DST_IPS_VAR, getDstIps()); } if (getDstPorts() != null && !getDstPorts().isEmpty()) { retString += String.format(" | %s=%s", DST_PORTS_VAR, getDstPorts()); } if (getDstProtocols() != null && !getDstProtocols().isEmpty()) { retString += String.format(" | %s=%s", DST_PROTOCOLS_VAR, getDstProtocols()); } if (!_finalNodeRegex.equals(DEFAULT_FINAL_NODE_REGEX)) { retString += String.format(" | %s=%s", FINAL_NODE_REGEX_VAR, _finalNodeRegex); } if (getFragmentOffsets() != null && !getFragmentOffsets().isEmpty()) { retString += String.format(" | %s=%s", FRAGMENT_OFFSETS_VAR, getFragmentOffsets()); } if (getIcmpCodes() != null && !getIcmpCodes().isEmpty()) { retString += String.format(" | %s=%s", ICMP_CODES_VAR, getIcmpCodes()); } if (getIcmpTypes() != null && !getIcmpTypes().isEmpty()) { retString += String.format(" | %s=%s", ICMP_TYPES_VAR, getIcmpTypes()); } if (!_ingressNodeRegex.equals(DEFAULT_INGRESS_NODE_REGEX)) { retString += String.format(" | %s=%s", INGRESS_NODE_REGEX_VAR, _finalNodeRegex); } if (getIpProtocols() != null && !getIpProtocols().isEmpty()) { retString += String.format(" | %s=%s", IP_PROTOCOLS_VAR, getIpProtocols().toString()); } if (getPacketLengths() != null && !getPacketLengths().isEmpty()) { retString += String.format(" | %s=%s", PACKET_LENGTHS_VAR, getPacketLengths().toString()); } if (getSrcIps() != null && !getSrcIps().isEmpty()) { retString += String.format(" | %s=%s", SRC_IPS_VAR, getSrcIps()); } if (getSrcPorts() != null && !getSrcPorts().isEmpty()) { retString += String.format(" | %s=%s", SRC_PORTS_VAR, getSrcPorts()); } if (getSrcProtocols() != null && !getSrcProtocols().isEmpty()) { retString += String.format(" | %s=%s", SRC_PROTOCOLS_VAR, getSrcProtocols()); } if (getSrcOrDstIps() != null && !getSrcOrDstIps().isEmpty()) { retString += String.format(" | %s=%s", SRC_OR_DST_IPS_VAR, getSrcOrDstIps()); } if (getSrcOrDstPorts() != null && !getSrcOrDstPorts().isEmpty()) { retString += String.format(" | %s=%s", SRC_OR_DST_PORTS_VAR, getSrcOrDstPorts()); } if (getSrcOrDstProtocols() != null && !getSrcOrDstProtocols().isEmpty()) { retString += String.format(" | %s=%s", SRC_OR_DST_PROTOCOLS_VAR, getSrcOrDstProtocols()); } if (getNotDstIps() != null && !getNotDstIps().isEmpty()) { retString += String.format(" | %s=%s", NOT_DST_IPS_VAR, getNotDstIps()); } if (getNotDstPorts() != null && !getNotDstPorts().isEmpty()) { retString += String.format(" | %s=%s", NOT_DST_PORTS_VAR, getNotDstPorts()); } if (getNotDstProtocols() != null && !getNotDstProtocols().isEmpty()) { retString += String.format(" | %s=%s", NOT_DST_PROTOCOLS_VAR, getNotDstProtocols()); } if (!_notFinalNodeRegex.equals(DEFAULT_NOT_FINAL_NODE_REGEX)) { retString += String.format(" | %s=%s", NOT_FINAL_NODE_REGEX_VAR, _notFinalNodeRegex); } if (getNotFragmentOffsets() != null && !getNotFragmentOffsets().isEmpty()) { retString += String.format(" | %s=%s", NOT_FRAGMENT_OFFSETS_VAR, getNotFragmentOffsets()); } if (getNotIcmpCodes() != null && !getNotIcmpCodes().isEmpty()) { retString += String.format(" | %s=%s", NOT_ICMP_CODES_VAR, getNotIcmpCodes()); } if (getNotIcmpTypes() != null && !getNotIcmpTypes().isEmpty()) { retString += String.format(" | %s=%s", NOT_ICMP_TYPES_VAR, getNotIcmpTypes()); } if (!_notIngressNodeRegex.equals(DEFAULT_NOT_INGRESS_NODE_REGEX)) { retString += String.format(" | %s=%s", NOT_INGRESS_NODE_REGEX_VAR, _notIngressNodeRegex); } if (getNotIpProtocols() != null && !getNotIpProtocols().isEmpty()) { retString += String.format(" | %s=%s", NOT_IP_PROTOCOLS_VAR, getNotIpProtocols().toString()); } if (getNotPacketLengths() != null && !getNotPacketLengths().isEmpty()) { retString += String.format(" | %s=%s", NOT_PACKET_LENGTHS_VAR, getNotPacketLengths().toString()); } if (getNotSrcIps() != null && !getNotSrcIps().isEmpty()) { retString += String.format(" | %s=%s", NOT_SRC_IPS_VAR, getNotSrcIps()); } if (getNotSrcPorts() != null && !getNotSrcPorts().isEmpty()) { retString += String.format(" | %s=%s", NOT_SRC_PORTS_VAR, getNotSrcPorts()); } if (getNotSrcProtocols() != null && !getNotSrcProtocols().isEmpty()) { retString += String.format(" | %s=%s", NOT_SRC_PROTOCOLS_VAR, getNotSrcProtocols()); } 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"); } } } @Override @JsonProperty(ACTIONS_VAR) public void setActions(SortedSet<ForwardingAction> actionSet) { _actions = new TreeSet<>(actionSet); } @Override @JsonProperty(DST_IPS_VAR) public void setDstIps(SortedSet<IpWildcard> dstIps) { _headerSpace.setDstIps(new TreeSet<>(dstIps)); } @JsonProperty(DST_PORTS_VAR) public void setDstPorts(SortedSet<SubRange> dstPorts) { _headerSpace.setDstPorts(new TreeSet<>(dstPorts)); } @Override @JsonProperty(DST_PROTOCOLS_VAR) public void setDstProtocols(SortedSet<Protocol> dstProtocols) { _headerSpace.setDstProtocols(new TreeSet<>(dstProtocols)); } @JsonProperty(FINAL_NODE_REGEX_VAR) public void setFinalNodeRegex(String regex) { _finalNodeRegex = regex; } @JsonProperty(ICMP_CODES_VAR) public void setIcmpCodes(SortedSet<SubRange> icmpCodes) { _headerSpace.setIcmpCodes(new TreeSet<>(icmpCodes)); } @JsonProperty(ICMP_TYPES_VAR) public void setIcmpTypes(SortedSet<SubRange> icmpTypes) { _headerSpace.setIcmpTypes(new TreeSet<>(icmpTypes)); } @Override @JsonProperty(INGRESS_NODE_REGEX_VAR) public void setIngressNodeRegex(String regex) { _ingressNodeRegex = regex; } @JsonProperty(IP_PROTOCOLS_VAR) public void setIpProtocols(SortedSet<IpProtocol> ipProtocols) { _headerSpace.setIpProtocols(ipProtocols); } @JsonProperty(NEGATE_HEADER_VAR) public void setNegateHeader(boolean negateHeader) { _headerSpace.setNegate(negateHeader); } @JsonProperty(NOT_DST_IPS_VAR) public void setNotDstIps(SortedSet<IpWildcard> notDstIps) { _headerSpace.setNotDstIps(new TreeSet<>(notDstIps)); } @JsonProperty(NOT_DST_PORTS_VAR) public void setNotDstPorts(SortedSet<SubRange> notDstPorts) { _headerSpace.setNotDstPorts(new TreeSet<>(notDstPorts)); } @Override @JsonProperty(NOT_DST_PROTOCOLS_VAR) public void setNotDstProtocols(SortedSet<Protocol> notDstProtocols) { _headerSpace.setNotDstProtocols(new TreeSet<>(notDstProtocols)); } @JsonProperty(NOT_FINAL_NODE_REGEX_VAR) public void setNotFinalNodeRegex(String notFinalNodeRegex) { _notFinalNodeRegex = notFinalNodeRegex; } @JsonProperty(NOT_ICMP_CODES_VAR) public void setNotIcmpCodes(SortedSet<SubRange> notIcmpCodes) { _headerSpace.setNotIcmpCodes(new TreeSet<>(notIcmpCodes)); } @JsonProperty(NOT_ICMP_TYPES_VAR) public void setNotIcmpTypes(SortedSet<SubRange> notIcmpType) { _headerSpace.setNotIcmpTypes(new TreeSet<>(notIcmpType)); } @JsonProperty(NOT_INGRESS_NODE_REGEX_VAR) public void setNotIngressNodeRegex(String notIngressNodeRegex) { _notIngressNodeRegex = notIngressNodeRegex; } @JsonProperty(NOT_IP_PROTOCOLS_VAR) public void setNotIpProtocols(SortedSet<IpProtocol> notIpProtocols) { _headerSpace.setNotIpProtocols(notIpProtocols); } @JsonProperty(NOT_PACKET_LENGTHS_VAR) public void setNotPacketLengths(SortedSet<SubRange> notPacketLengths) { _headerSpace.setNotPacketLengths(new TreeSet<>(notPacketLengths)); } @JsonProperty(NOT_SRC_IPS_VAR) public void setNotSrcIps(SortedSet<IpWildcard> notSrcIps) { _headerSpace.setNotSrcIps(new TreeSet<>(notSrcIps)); } @JsonProperty(NOT_SRC_PORTS_VAR) public void setNotSrcPortRange(SortedSet<SubRange> notSrcPorts) { _headerSpace.setNotSrcPorts(new TreeSet<>(notSrcPorts)); } @JsonProperty(NOT_SRC_PROTOCOLS_VAR) public void setNotSrcProtocols(SortedSet<Protocol> notSrcProtocols) { _headerSpace.setNotSrcProtocols(new TreeSet<>(notSrcProtocols)); } @JsonProperty(PACKET_LENGTHS_VAR) public void setPacketLengths(SortedSet<SubRange> packetLengths) { _headerSpace.setPacketLengths(new TreeSet<>(packetLengths)); } @JsonProperty(REACHABILITY_TYPE_VAR) public void setReachabilityType(ReachabilityType reachabilityType) { _reachabilityType = reachabilityType; switch (reachabilityType) { case INCREASED: case MULTIPATH_DIFF: case PATH_DIFF: case REDUCED_REACHABILITY: setDifferential(true); break; case MULTIPATH: case STANDARD: setDifferential(false); break; default: throw new BatfishException("Invalid reachability type: " + reachabilityType.reachabilityTypeName()); } } @JsonProperty(SRC_IPS_VAR) public void setSrcIps(SortedSet<IpWildcard> srcIps) { _headerSpace.setSrcIps(new TreeSet<>(srcIps)); } @JsonProperty(SRC_OR_DST_IPS_VAR) public void setSrcOrDstIps(SortedSet<IpWildcard> srcOrDstIps) { _headerSpace.setSrcOrDstIps(new TreeSet<>(srcOrDstIps)); } @JsonProperty(SRC_OR_DST_PORTS_VAR) public void setSrcOrDstPorts(SortedSet<SubRange> srcOrDstPorts) { _headerSpace.setSrcOrDstPorts(new TreeSet<>(srcOrDstPorts)); } @JsonProperty(SRC_OR_DST_PROTOCOLS_VAR) public void setSrcOrDstProtocols(SortedSet<Protocol> srcOrDstProtocols) { _headerSpace.setSrcOrDstProtocols(new TreeSet<>(srcOrDstProtocols)); } @JsonProperty(SRC_PORTS_VAR) public void setSrcPorts(SortedSet<SubRange> srcPorts) { _headerSpace.setSrcPorts(new TreeSet<>(srcPorts)); } @JsonProperty(SRC_PROTOCOLS_VAR) public void setSrcProtocols(SortedSet<Protocol> srcProtocols) { _headerSpace.setSrcProtocols(new TreeSet<>(srcProtocols)); } } @Override protected Answerer createAnswerer(Question question, IBatfish batfish) { return new ReachabilityAnswerer(question, batfish); } @Override protected Question createQuestion() { return new ReachabilityQuestion(); } }