use of org.batfish.common.topology.IpOwners in project batfish by batfish.
the class BgpTopologyUtils method addActivePeerEdges.
private static void addActivePeerEdges(BgpPeerConfigId neighborId, MutableValueGraph<BgpPeerConfigId, BgpSessionProperties> graph, NetworkConfigurations nc, Map<Ip, Map<String, Set<String>>> ipOwners, Map<String, Multimap<String, BgpPeerConfigId>> receivers, Set<Ip> potentialLocalIps, boolean checkReachability, TracerouteEngine tracerouteEngine) {
BgpActivePeerConfig neighbor = nc.getBgpPointToPointPeerConfig(neighborId);
if (neighbor == null || potentialLocalIps.isEmpty() || neighbor.getLocalAs() == null || neighbor.getPeerAddress() == null || neighbor.getRemoteAsns().isEmpty()) {
return;
}
// Find nodes that own the neighbor's peer address
Map<String, Set<String>> possibleVrfs = ipOwners.get(neighbor.getPeerAddress());
if (possibleVrfs == null) {
return;
}
Set<BgpPeerConfigId> alreadyEstablished = graph.adjacentNodes(neighborId);
for (Entry<String, Set<String>> entry : possibleVrfs.entrySet()) {
String node = entry.getKey();
Set<String> vrfs = entry.getValue();
Multimap<String, BgpPeerConfigId> receiversByVrf = receivers.get(node);
if (receiversByVrf == null) {
continue;
}
for (String vrf : vrfs) {
receiversByVrf.get(vrf).stream().filter(candidateId -> !alreadyEstablished.contains(candidateId)).forEach(candidateId -> {
// Ensure candidate has compatible local/remote AS, isn't in same vrf as initiator
BgpPeerConfig candidate = nc.getBgpPeerConfig(candidateId);
if (!bgpCandidatePassesSanityChecks(neighborId, neighbor, candidateId, candidate)) {
return;
}
// Check if neighbor has any feasible local IPs compatible with this candidate
Set<Ip> feasibleLocalIpsForPeeringWithCandidate = getFeasibleLocalIps(potentialLocalIps, candidate);
if (feasibleLocalIpsForPeeringWithCandidate.isEmpty()) {
return;
}
if (!checkReachability) {
feasibleLocalIpsForPeeringWithCandidate.forEach(ip -> addEdges(neighbor, neighborId, ip, candidateId, graph, nc));
} else {
initiateBgpSessions(neighborId, candidateId, neighbor, feasibleLocalIpsForPeeringWithCandidate, tracerouteEngine).stream().filter(BgpSessionInitiationResult::isSuccessful).map(initiationResult -> initiationResult.getFlow().getSrcIp()).forEach(srcIp -> addEdges(neighbor, neighborId, srcIp, candidateId, graph, nc));
}
});
}
}
}
use of org.batfish.common.topology.IpOwners in project batfish by batfish.
the class BgpTopologyUtils method initBgpTopology.
/**
* Compute the BGP topology -- a network of {@link BgpPeerConfigId}s connected by {@link
* BgpSessionProperties}.
*
* @param configurations node configurations, keyed by hostname
* @param ipVrfOwners network Ip owners (see {@link IpOwners#computeIpNodeOwners(Map, boolean)}
* for reference)
* @param keepInvalid whether to keep improperly configured neighbors. If performing configuration
* checks, you probably want this set to {@code true}, otherwise (e.g., computing dataplane)
* you want this to be {@code false}.
* @param checkReachability whether to perform dataplane-level checks to ensure that neighbors are
* reachable and sessions can be established correctly. <b>Note:</b> this is different from
* {@code keepInvalid=false}, which only does filters invalid neighbors at the control-plane
* level
* @param tracerouteEngine an instance of {@link TracerouteEngine} for doing reachability checks.
* @param l3Adjacencies {@link L3Adjacencies} of the network, for checking BGP unnumbered
* reachability.
* @return A graph ({@link Network}) representing all BGP peerings.
*/
@Nonnull
public static BgpTopology initBgpTopology(Map<String, Configuration> configurations, Map<Ip, Map<String, Set<String>>> ipVrfOwners, boolean keepInvalid, boolean checkReachability, @Nullable TracerouteEngine tracerouteEngine, Map<String, Map<String, Fib>> fibs, L3Adjacencies l3Adjacencies) {
checkArgument(!checkReachability || !keepInvalid, "Cannot check reachability while keeping invalid peers");
checkArgument(!checkReachability || tracerouteEngine != null, "Cannot check reachability without a traceroute engine");
// TODO: handle duplicate ips on different vrfs
NetworkConfigurations networkConfigurations = NetworkConfigurations.of(configurations);
/*
* First pass: identify all addresses "owned" by BgpNeighbors, add neighbor ids as vertices to
* the graph; dynamically determine local IPs as needed
*/
MutableValueGraph<BgpPeerConfigId, BgpSessionProperties> graph = ValueGraphBuilder.directed().allowsSelfLoops(false).build();
/*
* Multimap of active peers' BgpPeerConfigIds to all IPs that each peer may use as local IP
* when initiating a session. For a peer with an explicitly configured local IP, that IP is
* the only value associated with the peer in this map. Otherwise:
* - If FIBs are provided, the map contains all local IPs with which the peer may initiate,
* as inferred by getPotentialSrcIps().
* - Else no IPs are associated with the peer.
*/
ImmutableSetMultimap.Builder<BgpPeerConfigId, Ip> localIpsBuilder = ImmutableSetMultimap.builder();
for (Configuration node : configurations.values()) {
String hostname = node.getHostname();
for (Vrf vrf : node.getVrfs().values()) {
String vrfName = vrf.getName();
BgpProcess proc = vrf.getBgpProcess();
if (proc == null) {
// nothing to do if no bgp process on this VRF
continue;
}
Fib fib = fibs.getOrDefault(hostname, ImmutableMap.of()).get(vrfName);
for (Entry<Ip, BgpActivePeerConfig> e : proc.getActiveNeighbors().entrySet()) {
Ip peerAddress = e.getKey();
BgpActivePeerConfig config = e.getValue();
if (!keepInvalid && !bgpConfigPassesSanityChecks(config, hostname, vrfName, ipVrfOwners)) {
continue;
}
BgpPeerConfigId neighborId = new BgpPeerConfigId(hostname, vrfName, peerAddress.toPrefix(), false);
graph.addNode(neighborId);
if (config.getLocalIp() != null) {
localIpsBuilder.put(neighborId, config.getLocalIp());
} else if (fib != null) {
// No explicitly configured local IP. Check for dynamically resolvable local IPs.
localIpsBuilder.putAll(neighborId, getPotentialSrcIps(peerAddress, fib, node));
}
}
// Dynamic peers: map of prefix to BgpPassivePeerConfig
proc.getPassiveNeighbors().entrySet().stream().filter(entry -> keepInvalid || bgpConfigPassesSanityChecks(entry.getValue(), hostname, vrfName, ipVrfOwners)).forEach(entry -> graph.addNode(new BgpPeerConfigId(hostname, vrfName, entry.getKey(), true)));
// Unnumbered BGP peers: map of interface name to BgpUnnumberedPeerConfig
proc.getInterfaceNeighbors().entrySet().stream().filter(e -> keepInvalid || bgpConfigPassesSanityChecks(e.getValue(), hostname, vrfName, ipVrfOwners)).forEach(e -> graph.addNode(new BgpPeerConfigId(hostname, vrf.getName(), e.getKey())));
}
}
// Second pass: add edges to the graph. Note, these are directed edges.
Map<String, Multimap<String, BgpPeerConfigId>> receivers = new HashMap<>();
for (BgpPeerConfigId peer : graph.nodes()) {
if (peer.getType() == BgpPeerConfigType.UNNUMBERED) {
// Unnumbered configs only form sessions with each other
continue;
}
Multimap<String, BgpPeerConfigId> vrf = receivers.computeIfAbsent(peer.getHostname(), name -> LinkedListMultimap.create());
vrf.put(peer.getVrfName(), peer);
}
SetMultimap<BgpPeerConfigId, Ip> localIps = localIpsBuilder.build();
for (BgpPeerConfigId neighborId : graph.nodes()) {
switch(neighborId.getType()) {
case DYNAMIC:
// Passive end of the peering cannot initiate a connection
continue;
case ACTIVE:
addActivePeerEdges(neighborId, graph, networkConfigurations, ipVrfOwners, receivers, localIps.get(neighborId), checkReachability, tracerouteEngine);
break;
case UNNUMBERED:
addUnnumberedPeerEdges(neighborId, graph, networkConfigurations, l3Adjacencies);
break;
default:
throw new IllegalArgumentException(String.format("Unrecognized peer type: %s", neighborId));
}
}
return new BgpTopology(graph);
}
use of org.batfish.common.topology.IpOwners in project batfish by batfish.
the class ForwardingAnalysisImplTest method testDispositionWithTopology.
// If two nodes are in the same subnet but not connected per the given topology,
// sending packets from one to the other should result in Neighbor Unreachable.
@Test
public void testDispositionWithTopology() {
Prefix prefix = Prefix.parse("1.0.0.0/24");
IpSpace ipSpace = prefix.toIpSpace();
Ip ip2 = Ip.parse("1.0.0.2");
Configuration c1 = _cb.setHostname("c1").build();
Configuration c2 = _cb.setHostname("c2").build();
Vrf v1 = _vb.setName("v1").setOwner(c1).build();
Vrf v2 = _vb.setName("v2").setOwner(c2).build();
Interface i1 = _ib.setAddresses(ConcreteInterfaceAddress.parse("1.0.0.1/24")).setName("i1").setOwner(c1).setVrf(v1).build();
Interface i2 = _ib.setAddresses(ConcreteInterfaceAddress.parse("1.0.0.2/24")).setName("i2").setOwner(c2).setVrf(v2).build();
StaticRoute route1 = StaticRoute.testBuilder().setNetwork(prefix).setNextHopInterface(i1.getName()).setAdministrativeCost(1).build();
StaticRoute route2 = StaticRoute.testBuilder().setNextHopInterface(i2.getName()).setNetwork(prefix).setAdministrativeCost(1).build();
v1.setStaticRoutes(ImmutableSortedSet.of(route1));
v2.setStaticRoutes(ImmutableSortedSet.of(route2));
MockFib fib1 = MockFib.builder().setMatchingIps(ImmutableMap.of(prefix, ipSpace)).setFibEntries(ImmutableMap.of(prefix.getFirstHostIp(), ImmutableSet.of(new FibEntry(FibForward.of(null, i1.getName()), ImmutableList.of(route1))))).build();
MockFib fib2 = MockFib.builder().setMatchingIps(ImmutableMap.of(prefix, ipSpace)).setFibEntries(ImmutableMap.of(prefix.getFirstHostIp(), ImmutableSet.of(new FibEntry(FibForward.of(null, i2.getName()), ImmutableList.of(route2))))).build();
Map<String, Map<String, Fib>> fibs = ImmutableMap.of(c1.getHostname(), ImmutableMap.of(v1.getName(), fib1), c2.getHostname(), ImmutableMap.of(v2.getName(), fib2));
Map<String, Configuration> configs = ImmutableMap.of(c1.getHostname(), c1, c2.getHostname(), c2);
IpOwners ipOwners = new TestIpOwners(configs);
ForwardingAnalysis analysis = new ForwardingAnalysisImpl(configs, fibs, Topology.EMPTY, computeLocationInfo(ipOwners, configs), ipOwners);
InterfaceForwardingBehavior i1ForwardingBehavior = analysis.getVrfForwardingBehavior().get(c1.getHostname()).get(v1.getName()).getInterfaceForwardingBehavior().get(i1.getName());
assertFalse(i1ForwardingBehavior.getDeliveredToSubnet().containsIp(ip2, c1.getIpSpaces()));
assertTrue(i1ForwardingBehavior.getNeighborUnreachable().containsIp(ip2, c1.getIpSpaces()));
assertFalse(i1ForwardingBehavior.getInsufficientInfo().containsIp(ip2, c1.getIpSpaces()));
}
use of org.batfish.common.topology.IpOwners in project batfish by batfish.
the class ForwardingAnalysisImplTest method testUnknownedNextHopIpOwnedArpIp.
/**
* Test that if a route has an unowned next hop IP, but it resolves to an owned ARP IP and we
* don't get an ARP response, we get insufficient info.
*/
@Test
public void testUnknownedNextHopIpOwnedArpIp() {
Configuration n1 = _cb.setHostname("n1").build();
Vrf v1 = _vb.setOwner(n1).build();
Interface i1 = _ib.setOwner(n1).setVrf(v1).setAddress(ConcreteInterfaceAddress.parse("10.0.1.0/31")).build();
// unowned
Ip arpIp = Ip.parse("3.3.3.3");
StaticRoute route = StaticRoute.testBuilder().setNetwork(Prefix.parse("1.1.1.1/32")).setNextHopIp(Ip.parse("2.2.2.2")).setAdmin(1).build();
MockFib fib1 = MockFib.builder().setMatchingIps(ImmutableMap.of(route.getNetwork(), route.getNetwork().toIpSpace())).setFibEntries(ImmutableMap.of(arpIp, ImmutableSet.of(new FibEntry(FibForward.of(arpIp, i1.getName()), ImmutableList.of(route))))).build();
Map<String, Configuration> configs = ImmutableMap.of(n1.getHostname(), n1);
Map<String, Map<String, Fib>> fibs = ImmutableMap.of(n1.getHostname(), ImmutableMap.of(v1.getName(), fib1));
IpOwners ipOwners = new TestIpOwners(configs);
ForwardingAnalysis fa = new ForwardingAnalysisImpl(configs, fibs, Topology.EMPTY, computeLocationInfo(ipOwners, configs), ipOwners);
InterfaceForwardingBehavior ifb = fa.getVrfForwardingBehavior().get(n1.getHostname()).get(v1.getName()).getInterfaceForwardingBehavior().get(i1.getName());
assertThat(ifb.getInsufficientInfo(), not(containsIp(route.getNetwork().getStartIp())));
assertThat(ifb.getExitsNetwork(), containsIp(route.getNetwork().getStartIp()));
}
use of org.batfish.common.topology.IpOwners in project batfish by batfish.
the class IncrementalBdpEngine method computeDataPlane.
ComputeDataPlaneResult computeDataPlane(Map<String, Configuration> configurations, TopologyContext initialTopologyContext, Set<BgpAdvertisement> externalAdverts, IpOwners initialIpOwners) {
LOGGER.info("Computing Data Plane using iBDP");
Map<Ip, Map<String, Set<String>>> initialIpVrfOwners = initialIpOwners.getIpVrfOwners();
// Generate our nodes, keyed by name, sorted for determinism
SortedMap<String, Node> nodes = toImmutableSortedMap(configurations.values(), Configuration::getHostname, Node::new);
// A collection of all the virtual routers in random order enables parallelization across all
// VRs, and likely spreads nodes with similar hostnames across different cores. In contrast,
// nodes.values().parallelStream().flatMap(get vrs stream) is only node-parallel and clusters
// nodes by hostname. See https://github.com/batfish/batfish/pull/7054 description.
List<VirtualRouter> vrs = toListInRandomOrder(nodes.values().stream().flatMap(n -> n.getVirtualRouters().stream()));
NetworkConfigurations networkConfigurations = NetworkConfigurations.of(configurations);
/*
* Run the data plane computation here:
* - First, let the IGP routes converge
* - Second, re-init BGP neighbors with reachability checks
* - Third, let the EGP routes converge
* - Finally, compute FIBs, return answer
*/
IncrementalBdpAnswerElement answerElement = new IncrementalBdpAnswerElement();
// TODO: eventually, IGP needs to be part of fixed-point below, because tunnels.
computeIgpDataPlane(nodes, vrs, initialTopologyContext, answerElement);
LOGGER.info("Initialize virtual routers before topology fixed point");
vrs.parallelStream().forEach(vr -> vr.initForEgpComputationBeforeTopologyLoop(externalAdverts, initialIpVrfOwners));
/*
* Perform a fixed-point computation, in which every round the topology is updated based
* on what we have learned in the previous round.
*/
// Since the topology iterations are incremental, clear fields that are pruned to get the real
// topology. They are not actually yet included in topologies.
TopologyContext priorTopologyContext = initialTopologyContext.toBuilder().setIpsecTopology(IpsecTopology.EMPTY).setTunnelTopology(TunnelTopology.EMPTY).setVxlanTopology(VxlanTopology.EMPTY).build();
PartialDataplane currentDataplane = nextDataplane(priorTopologyContext, nodes, vrs, initialIpOwners);
TopologyContext currentTopologyContext = nextTopologyContext(priorTopologyContext, currentDataplane, initialTopologyContext, networkConfigurations, initialIpVrfOwners);
Map<String, Collection<TrackRoute>> trackRoutesByHostname = collectTrackRoutes(configurations);
Map<String, Collection<TrackReachability>> trackReachabilitiesByHostname = collectTrackReachabilities(configurations);
Map<String, Map<TrackReachability, Boolean>> currentTrackReachabilityResultsByHostname = nextTrackReachabilityResultsByHostname(currentDataplane, currentTopologyContext, configurations, trackReachabilitiesByHostname);
Map<String, Map<TrackRoute, Boolean>> currentTrackRouteResultsByHostname = nextTrackRoutesByHostname(trackRoutesByHostname, nodes);
DataPlaneTrackMethodEvaluatorProvider currentTrackMethodEvaluatorProvider = nextTrackMethodEvaluatorProvider(currentTrackReachabilityResultsByHostname, currentTrackRouteResultsByHostname);
DataPlaneIpOwners currentIpOwners = new DataPlaneIpOwners(configurations, currentTopologyContext.getL3Adjacencies(), currentTrackMethodEvaluatorProvider);
int topologyIterations = 0;
boolean converged = false;
while (!converged && topologyIterations++ < MAX_TOPOLOGY_ITERATIONS) {
LOGGER.info("Starting topology iteration {}", topologyIterations);
boolean isOscillating = computeNonMonotonicPortionOfDataPlane(nodes, vrs, answerElement, currentTopologyContext, initialTopologyContext.getLayer3Topology(), currentIpOwners, networkConfigurations, currentTrackMethodEvaluatorProvider);
if (isOscillating) {
// If we are oscillating here, network has no stable solution.
LOGGER.error("Network has no stable solution");
throw new BdpOscillationException("Network has no stable solution");
}
updateLayer3Vnis(vrs);
// free the old one
currentDataplane = null;
currentDataplane = nextDataplane(currentTopologyContext, nodes, vrs, currentIpOwners);
TopologyContext nextTopologyContext = nextTopologyContext(currentTopologyContext, currentDataplane, initialTopologyContext, networkConfigurations, currentIpOwners.getIpVrfOwners());
Map<String, Map<TrackReachability, Boolean>> nextTrackReachabilityResultsByHostname = nextTrackReachabilityResultsByHostname(currentDataplane, currentTopologyContext, configurations, trackReachabilitiesByHostname);
Map<String, Map<TrackRoute, Boolean>> nextTrackRouteResultsByHostname = nextTrackRoutesByHostname(trackRoutesByHostname, nodes);
currentTrackMethodEvaluatorProvider = nextTrackMethodEvaluatorProvider(nextTrackReachabilityResultsByHostname, nextTrackRouteResultsByHostname);
DataPlaneIpOwners nextIpOwners = new DataPlaneIpOwners(configurations, nextTopologyContext.getL3Adjacencies(), currentTrackMethodEvaluatorProvider);
converged = currentTopologyContext.equals(nextTopologyContext) && currentTrackReachabilityResultsByHostname.equals(nextTrackReachabilityResultsByHostname) && currentTrackRouteResultsByHostname.equals(nextTrackRouteResultsByHostname) && currentIpOwners.equals(nextIpOwners);
currentTopologyContext = nextTopologyContext;
currentTrackReachabilityResultsByHostname = nextTrackReachabilityResultsByHostname;
currentTrackRouteResultsByHostname = nextTrackRouteResultsByHostname;
currentIpOwners = nextIpOwners;
}
if (!converged) {
LOGGER.error("Could not reach a fixed point topology in {} iterations", MAX_TOPOLOGY_ITERATIONS);
throw new BdpOscillationException(String.format("Could not reach a fixed point topology in %d iterations", MAX_TOPOLOGY_ITERATIONS));
}
// Generate the answers from the computation, compute final FIBs
// TODO: Properly finalize topologies, IpOwners, etc.
LOGGER.info("Finalizing dataplane");
answerElement.setVersion(BatfishVersion.getVersionStatic());
IncrementalDataPlane finalDataplane = IncrementalDataPlane.builder().setNodes(nodes).setPartialDataplane(currentDataplane).build();
return new IbdpResult(answerElement, finalDataplane, currentTopologyContext, nodes);
}
Aggregations