def three_tier_ibgp_corner_cases(rtrs): """Calculate edges for iBGP l3 clusters that don't contain a HRR. Connects l1 to l3 directly""" up_links = [] down_links = [] over_links = [] for l3_cluster, l3d in ank_utils.groupby("ibgp_l3_cluster", rtrs): for l2_cluster, l2d in ank_utils.groupby("ibgp_l2_cluster", l3d): l2d = list(l2d) if any(r.ibgp_level == 2 for r in l2d): log.debug("Cluster (%s, %s) has l2 devices, not " "adding extra links" % (l3_cluster, l2_cluster)) elif all(r.ibgp_level == 1 for r in l2d): # No l2 or l3 routers -> full-mesh of l1 routers over_links += [(s, t) for s in l2d for t in l2d if s != t] log.debug("Cluster (%s, %s) has no level 2 or 3 iBGP routers." "Connecting l1 routers (%s) in full-mesh" % (l3_cluster, l2_cluster, l2d)) else: l1_rtrs = [r for r in l2d if r.ibgp_level == 1] l3_rtrs = [r for r in l2d if r.ibgp_level == 3] if not (len(l1_rtrs) and len(l3_rtrs)): break # no routers to connect log.debug("Cluster (%s, %s) has no level 2 iBGP routers." "Connecting l1 routers (%s) to l3 routers (%s)" % (l3_cluster, l2_cluster, l1_rtrs, l3_rtrs)) l1_l3_up_links = [(s, t) for s in l1_rtrs for t in l3_rtrs] up_links += l1_l3_up_links down_links += [(t, s) for (s, t) in l1_l3_up_links] return up_links, down_links, over_links
def three_tier_ibgp_corner_cases(rtrs): """Calculate edges for iBGP l3 clusters that don't contain a HRR. Connects l1 to l3 directly""" up_links = [] down_links = [] over_links = [] for l3_cluster, l3d in ank_utils.groupby("ibgp_l3_cluster", rtrs): for l2_cluster, l2d in ank_utils.groupby("ibgp_l2_cluster", l3d): l2d = list(l2d) if any(r.ibgp_level == 2 for r in l2d): log.debug("Cluster (%s, %s) has l2 devices, not " "adding extra links" % (l3_cluster, l2_cluster)) elif all(r.ibgp_level == 1 for r in l2d): # No l2 or l3 routers -> full-mesh of l1 routers over_links += [(s, t) for s in l2d for t in l2d if s != t] log.debug("Cluster (%s, %s) has no level 2 or 3 iBGP routers." "Connecting l1 routers (%s) in full-mesh" % (l3_cluster, l2_cluster, l2d)) else: l1_rtrs = [r for r in l2d if r.ibgp_level == 1] l3_rtrs = [r for r in l2d if r.ibgp_level == 3] if not(len(l1_rtrs) and len(l3_rtrs)): break # no routers to connect log.debug("Cluster (%s, %s) has no level 2 iBGP routers." "Connecting l1 routers (%s) to l3 routers (%s)" % (l3_cluster, l2_cluster, l1_rtrs, l3_rtrs)) l1_l3_up_links = [(s, t) for s in l1_rtrs for t in l3_rtrs] up_links += l1_l3_up_links down_links += [(t, s) for (s, t) in l1_l3_up_links] return up_links, down_links, over_links
def build_vrf(anm): """Build VRF Overlay""" g_in = anm['input'] g_vrf = anm.add_overlay("vrf") g_vrf.add_nodes_from(g_in.nodes("is_router"), retain=["vrf_role", "vrf"]) allocate_vrf_roles(g_vrf) vrf_pre_process(anm) def is_pe_ce_edge(edge): src_vrf_role = g_vrf.node(edge.src).vrf_role dst_vrf_role = g_vrf.node(edge.dst).vrf_role return (src_vrf_role, dst_vrf_role) in (("PE", "CE"), ("CE", "PE")) vrf_add_edges = (e for e in g_in.edges() if is_pe_ce_edge(e)) #TODO: should mark as being towards PE or CE g_vrf.add_edges_from(vrf_add_edges, retain=['edge_id']) def is_pe_p_edge(edge): src_vrf_role = g_vrf.node(edge.src).vrf_role dst_vrf_role = g_vrf.node(edge.dst).vrf_role return (src_vrf_role, dst_vrf_role) in (("PE", "P"), ("P", "PE")) vrf_add_edges = (e for e in g_in.edges() if is_pe_p_edge(e)) g_vrf.add_edges_from(vrf_add_edges, retain=['edge_id']) build_mpls_ldp(anm) # add PE to P edges add_vrf_loopbacks(g_vrf) # allocate route-targets per AS # This could later look at connected components for each ASN route_targets = {} for asn, devices in ank_utils.groupby("asn", g_vrf.nodes(vrf_role="PE")): asn_vrfs = [d.node_vrf_names for d in devices] # flatten list to unique set asn_vrfs = set(itertools.chain.from_iterable(asn_vrfs)) route_targets[asn] = { vrf: "%s:%s" % (asn, index) for index, vrf in enumerate(sorted(asn_vrfs), 1) } g_vrf.data.route_targets = route_targets for node in g_vrf: vrf_loopbacks = node.interfaces("is_loopback", "vrf_name") for index, interface in enumerate(vrf_loopbacks, start=101): interface.index = index for edge in g_vrf.edges(): # Set the vrf of the edge to be that of the CE device (either src or dst) edge.vrf = edge.src.vrf if edge.src.vrf_role is "CE" else edge.dst.vrf # map attributes to interfaces for edge in g_vrf.edges(): for interface in edge.interfaces(): interface.vrf_name = edge.vrf
def build_vrf(anm): """Build VRF Overlay""" g_in = anm['input'] g_vrf = anm.add_overlay("vrf") g_vrf.add_nodes_from(g_in.nodes("is_router"), retain=["vrf_role", "vrf"]) allocate_vrf_roles(g_vrf) vrf_pre_process(anm) def is_pe_ce_edge(edge): src_vrf_role = g_vrf.node(edge.src).vrf_role dst_vrf_role = g_vrf.node(edge.dst).vrf_role return (src_vrf_role, dst_vrf_role) in (("PE", "CE"), ("CE", "PE")) vrf_add_edges = (e for e in g_in.edges() if is_pe_ce_edge(e)) #TODO: should mark as being towards PE or CE g_vrf.add_edges_from(vrf_add_edges, retain=['edge_id']) def is_pe_p_edge(edge): src_vrf_role = g_vrf.node(edge.src).vrf_role dst_vrf_role = g_vrf.node(edge.dst).vrf_role return (src_vrf_role, dst_vrf_role) in (("PE", "P"), ("P", "PE")) vrf_add_edges = (e for e in g_in.edges() if is_pe_p_edge(e)) g_vrf.add_edges_from(vrf_add_edges, retain=['edge_id']) build_mpls_ldp(anm) # add PE to P edges add_vrf_loopbacks(g_vrf) # allocate route-targets per AS # This could later look at connected components for each ASN route_targets = {} for asn, devices in ank_utils.groupby("asn", g_vrf.nodes(vrf_role = "PE")): asn_vrfs = [d.node_vrf_names for d in devices] # flatten list to unique set asn_vrfs = set(itertools.chain.from_iterable(asn_vrfs)) route_targets[asn] = {vrf: "%s:%s" % (asn, index) for index, vrf in enumerate(sorted(asn_vrfs), 1)} g_vrf.data.route_targets = route_targets for node in g_vrf: vrf_loopbacks = node.interfaces("is_loopback", "vrf_name") for index, interface in enumerate(vrf_loopbacks, start = 101): interface.index = index for edge in g_vrf.edges(): # Set the vrf of the edge to be that of the CE device (either src or dst) edge.vrf = edge.src.vrf if edge.src.vrf_role is "CE" else edge.dst.vrf # map attributes to interfaces for edge in g_vrf.edges(): for interface in edge.interfaces(): interface.vrf_name = edge.vrf
def validate_ibgp(anm): import networkx as nx #TODO: repeat for ibgp v6 #TODO: test if overlay is present, if not then warn if not anm.has_overlay("ibgp_v4"): return # no ibgp v4 - eg if ip addressing disabled g_ibgp_v4 = anm['ibgp_v4'] for asn, devices in ank_utils.groupby("asn", g_ibgp_v4): asn_subgraph = g_ibgp_v4.subgraph(devices) graph = asn_subgraph._graph # get subgraph if not nx.is_strongly_connected(graph): g_ibgp_v4.log.warning("iBGP v4 topology for ASN%s is disconnected" % asn) #TODO: list connected components - but not the primary? else: g_ibgp_v4.log.debug("iBGP v4 topology for ASN%s is connected" % asn)
def validate_igp(anm): import networkx as nx # TODO: test if overlay is present, if not then warn if not anm.has_overlay("igp"): return # no ibgp v4 - eg if ip addressing disabled g_igp = anm['igp'] for asn, devices in ank_utils.groupby("asn", g_igp): if asn is None: continue asn_subgraph = g_igp.subgraph(devices) graph = asn_subgraph._graph # get subgraph if not nx.is_connected(graph): g_igp.log.warning("IGP topology for ASN%s is disconnected" % asn) # TODO: list connected components - but not the primary? else: g_igp.log.debug("IGP topology for ASN%s is connected" % asn)
def build_ibgp(anm): g_in = anm['input'] g_bgp = anm['bgp'] # TODO: build direct to ibgp graph - can construct combined bgp for vis #TODO: normalise input property ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_role") ank_utils.copy_attr_from( g_in, g_bgp, "ibgp_l2_cluster", "hrr_cluster", default=None) ank_utils.copy_attr_from( g_in, g_bgp, "ibgp_l3_cluster", "rr_cluster", default=None) # TODO: add more detailed logging for n in g_bgp: # Tag with label to make logic clearer if n.ibgp_role is None: n.ibgp_role = "Peer" # TODO: if top-level, then don't mark as RRC ibgp_nodes = [n for n in g_bgp if not n.ibgp_role is "Disabled"] # Notify user of non-ibgp nodes non_ibgp_nodes = [n for n in g_bgp if n.ibgp_role is "Disabled"] if 0 < len(non_ibgp_nodes) < 10: log.info("Skipping iBGP for iBGP disabled nodes: %s", non_ibgp_nodes) elif len(non_ibgp_nodes) >= 10: log.info("Skipping iBGP for more than 10 iBGP disabled nodes:", "refer to visualization for resulting topology.") # warn for any nodes that have RR set but no rr_cluster, or HRR set and no # hrr_cluster rr_mismatch = [ n for n in ibgp_nodes if n.ibgp_role == "RR" and n.rr_cluster is None] if len(rr_mismatch): log.warning("Some routers are set as RR but have no rr_cluster: %s. Please specify an rr_cluster for peering." % ", ".join(str(n) for n in rr_mismatch)) hrr_mismatch = [ n for n in ibgp_nodes if n.ibgp_role == "HRR" and n.hrr_cluster is None] if len(hrr_mismatch): log.warning("Some routers are set as HRR but have no hrr_cluster: %s. Please specify an hrr_cluster for peering." % ", ".join(str(n) for n in hrr_mismatch)) for _, asn_devices in ank_utils.groupby("asn", ibgp_nodes): asn_devices = list(asn_devices) # iBGP peer peers with peers = [n for n in asn_devices if n.ibgp_role == "Peer"] rrs = [n for n in asn_devices if n.ibgp_role == "RR"] hrrs = [n for n in asn_devices if n.ibgp_role == "HRR"] rrcs = [n for n in asn_devices if n.ibgp_role == "RRC"] over_links = [] up_links = [] down_links = [] # 0. RRCs can only belong to either an rr_cluster or a hrr_cluster invalid_rrcs = [r for r in rrcs if r.rr_cluster is not None and r.hrr_cluster is not None] if len(invalid_rrcs): message = ", ".join(str(r) for r in invalid_rrcs) log.warning("RRCs can only have either a rr_cluster or hrr_cluster set. " "The following have both set, and only the rr_cluster will be used: %s", message) # TODO: do we also want to warn for RRCs with no cluster set? Do we also exclude these? # TODO: do we also want to warn for HRRs and RRs with no cluster set? # Do we also exclude these? # 1. Peers: # 1a. Peers connect over to peers over_links += [(s, t) for s in peers for t in peers] # 1b. Peers connect over to RRs over_links += [(s, t) for s in peers for t in rrs] # 2. RRs: # 2a. RRs connect over to Peers over_links += [(s, t) for s in rrs for t in peers] # 2b. RRs connect over to RRs over_links += [(s, t) for s in rrs for t in rrs] # 2c. RRs connect down to RRCs in same rr_cluster down_links += [(s, t) for s in rrs for t in rrcs if s.rr_cluster == t.rr_cluster != None] # 2d. RRs connect down to HRRs in the same rr_cluster down_links += [(s, t) for s in rrs for t in hrrs if s.rr_cluster == t.rr_cluster != None] # 3. HRRs # 3a. HRRs connect up to RRs in the same rr_cluster up_links += [(s, t) for s in hrrs for t in rrs if s.rr_cluster == t.rr_cluster != None] # 3b. HRRs connect down to RRCs in same hrr_cluster (providing RRC has # no rr_cluster set) down_links += [(s, t) for s in hrrs for t in rrcs if s.hrr_cluster == t.hrr_cluster != None and t.rr_cluster is None] # 4. RRCs # 4a. RRCs connect up to RRs in the same rr_cluster (regardless if RRC # has hrr_cluster set) up_links += [(s, t) for s in rrcs for t in rrs if s.rr_cluster == t.rr_cluster != None] # 3b. RRCs connect up to HRRs in same hrr_cluster (providing RRC has no # rr_cluster set) up_links += [(s, t) for s in rrcs for t in hrrs if s.hrr_cluster == t.hrr_cluster != None and s.rr_cluster is None] # Remove self-links over_links = [(s, t) for s, t in over_links if s != t] up_links = [(s, t) for s, t in up_links if s != t] down_links = [(s, t) for s, t in down_links if s != t] g_bgp.add_edges_from(over_links, type='ibgp', direction='over') g_bgp.add_edges_from(up_links, type='ibgp', direction='up') g_bgp.add_edges_from(down_links, type='ibgp', direction='down')
def build_bgp(anm): """Build iBGP end eBGP overlays""" # eBGP g_in = anm['input'] g_phy = anm['phy'] if not anm['phy'].data.enable_routing: log.info("Routing disabled, not configuring BGP") return build_ebgp(anm) build_ebgp_v4(anm) build_ebgp_v6(anm) """TODO: remove from here once compiler updated""" g_bgp = anm.add_overlay("bgp", directed=True) g_bgp.add_nodes_from(g_in.nodes("is_router")) ebgp_edges = [edge for edge in g_in.edges() if not edge.attr_equal("asn")] g_bgp.add_edges_from(ebgp_edges, bidirectional=True, type='ebgp') #TODO: why don't we include edge_id here ebgp_switches = [n for n in g_in.nodes("is_switch") if not ank_utils.neigh_equal(g_phy, n, "asn")] g_bgp.add_nodes_from(ebgp_switches, retain=['asn']) log.debug("eBGP switches are %s" % ebgp_switches) g_bgp.add_edges_from((e for e in g_in.edges() if e.src in ebgp_switches or e.dst in ebgp_switches), bidirectional=True, type='ebgp') ank_utils.aggregate_nodes(g_bgp, ebgp_switches, retain="edge_id") ebgp_switches = list(g_bgp.nodes("is_switch")) # need to recalculate as may have aggregated log.debug("aggregated eBGP switches are %s" % ebgp_switches) exploded_edges = ank_utils.explode_nodes(g_bgp, ebgp_switches, retain="edge_id") same_asn_edges = [] for edge in exploded_edges: if edge.src.asn == edge.dst.asn: same_asn_edges.append(edge) else: edge.multipoint = True """TODO: remove up to here once compiler updated""" g_bgp.remove_edges_from(same_asn_edges) # now iBGP ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_level") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l2_cluster") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l3_cluster") for node in g_bgp: # set defaults if node.ibgp_level is None: node.ibgp_level = 1 if node.ibgp_level == "None": # if unicode string from yEd node.ibgp_level = 1 #TODO CHECK FOR IBGP NONE node.ibgp_level = int(node.ibgp_level) # ensure is numeric if not node.ibgp_l2_cluster or node.ibgp_l2_cluster == "None": # ibgp_l2_cluster defaults to region node.ibgp_l2_cluster = node.region or "default_l2_cluster" if not node.ibgp_l3_cluster or node.ibgp_l3_cluster == "None": # ibgp_l3_cluster defaults to ASN node.ibgp_l3_cluster = node.asn for asn, devices in ank_utils.groupby("asn", g_bgp): # group by nodes in phy graph routers = list(g_bgp.node(n) for n in devices if n.is_router) # list of nodes from bgp graph ibgp_levels = {int(r.ibgp_level) for r in routers} max_level = max(ibgp_levels) # all possible edge src/dst pairs ibgp_routers = [r for r in routers if r.ibgp_level > 0] all_pairs = [(s, t) for s in ibgp_routers for t in ibgp_routers if s != t] if max_level == 3: up_links, down_links, over_links = three_tier_ibgp_edges(ibgp_routers) elif max_level == 2: #TODO: check when this is reached - as RR is not HRR.... due to naming/levels mapping up_links, down_links, over_links = build_two_tier_ibgp(ibgp_routers) elif max_level == 1: up_links = [] down_links = [] over_links = [(s, t) for (s, t) in all_pairs if s.ibgp_l3_cluster == t.ibgp_l3_cluster and s.ibgp_l2_cluster == t.ibgp_l2_cluster ] else: # no iBGP up_links = [] down_links = [] over_links = [] if max_level > 0: g_bgp.add_edges_from(up_links, type='ibgp', direction='up') g_bgp.add_edges_from(down_links, type='ibgp', direction='down') g_bgp.add_edges_from(over_links, type='ibgp', direction='over') else: log.debug("No iBGP routers in %s" % asn) # and set label back ibgp_label_to_level = { 0: "None", # Explicitly set role to "None" -> Not in iBGP 3: "RR", 1: "RRC", 2: "HRR", } for node in g_bgp: node.ibgp_role = ibgp_label_to_level[node.ibgp_level] ebgp_nodes = [d for d in g_bgp if any( edge.type == 'ebgp' for edge in d.edges())] g_bgp.update(ebgp_nodes, ebgp=True) for ebgp_edge in g_bgp.edges(type = "ebgp"): for interface in ebgp_edge.interfaces(): interface.ebgp = True for edge in g_bgp.edges(type='ibgp'): # TODO: need interface querying/selection. rather than hard-coded ids edge.bind_interface(edge.src, 0) #TODO: need to initialise interface zero to be a loopback rather than physical type for node in g_bgp: for interface in node.interfaces(): interface.multipoint = any(e.multipoint for e in interface.edges()) build_ibgp_v4(anm) build_ibgp_v6(anm)
def build_bgp(anm): """Build iBGP end eBGP overlays""" # eBGP g_in = anm['input'] g_phy = anm['phy'] build_ebgp(anm) build_ebgp_v4(anm) build_ebgp_v6(anm) """TODO: remove from here once compiler updated""" g_bgp = anm.add_overlay("bgp", directed=True) g_bgp.add_nodes_from(g_in.nodes("is_router")) ebgp_edges = [edge for edge in g_in.edges() if not edge.attr_equal("asn")] g_bgp.add_edges_from(ebgp_edges, bidirectional=True, type='ebgp') #TODO: why don't we include edge_id here ebgp_switches = [ n for n in g_in.nodes("is_switch") if not ank_utils.neigh_equal(g_phy, n, "asn") ] g_bgp.add_nodes_from(ebgp_switches, retain=['asn']) log.debug("eBGP switches are %s" % ebgp_switches) g_bgp.add_edges_from((e for e in g_in.edges() if e.src in ebgp_switches or e.dst in ebgp_switches), bidirectional=True, type='ebgp') ank_utils.aggregate_nodes(g_bgp, ebgp_switches, retain="edge_id") ebgp_switches = list( g_bgp.nodes("is_switch")) # need to recalculate as may have aggregated log.debug("aggregated eBGP switches are %s" % ebgp_switches) exploded_edges = ank_utils.explode_nodes(g_bgp, ebgp_switches, retain="edge_id") for edge in exploded_edges: edge.multipoint = True """TODO: remove up to here once compiler updated""" # now iBGP ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_level") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l2_cluster") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l3_cluster") for node in g_bgp: # set defaults if node.ibgp_level is None: node.ibgp_level = 1 if node.ibgp_level == "None": # if unicode string from yEd node.ibgp_level = 1 #TODO CHECK FOR IBGP NONE node.ibgp_level = int(node.ibgp_level) # ensure is numeric if not node.ibgp_l2_cluster or node.ibgp_l2_cluster == "None": # ibgp_l2_cluster defaults to region node.ibgp_l2_cluster = node.region or "default_l2_cluster" if not node.ibgp_l3_cluster or node.ibgp_l3_cluster == "None": # ibgp_l3_cluster defaults to ASN node.ibgp_l3_cluster = node.asn for asn, devices in ank_utils.groupby("asn", g_bgp): # group by nodes in phy graph routers = list(g_bgp.node(n) for n in devices if n.is_router) # list of nodes from bgp graph ibgp_levels = {int(r.ibgp_level) for r in routers} max_level = max(ibgp_levels) # all possible edge src/dst pairs ibgp_routers = [r for r in routers if r.ibgp_level > 0] all_pairs = [(s, t) for s in ibgp_routers for t in ibgp_routers if s != t] if max_level == 3: up_links, down_links, over_links = three_tier_ibgp_edges( ibgp_routers) elif max_level == 2: up_links, down_links, over_links = build_two_tier_ibgp( ibgp_routers) elif max_level == 1: up_links = [] down_links = [] over_links = [(s, t) for (s, t) in all_pairs if s.ibgp_l3_cluster == t.ibgp_l3_cluster and s.ibgp_l2_cluster == t.ibgp_l2_cluster] else: # no iBGP up_links = [] down_links = [] over_links = [] if max_level > 0: g_bgp.add_edges_from(up_links, type='ibgp', direction='up') g_bgp.add_edges_from(down_links, type='ibgp', direction='down') g_bgp.add_edges_from(over_links, type='ibgp', direction='over') else: log.debug("No iBGP routers in %s" % asn) # and set label back ibgp_label_to_level = { 0: "None", # Explicitly set role to "None" -> Not in iBGP 3: "RR", 1: "RRC", 2: "HRR", } for node in g_bgp: node.ibgp_role = ibgp_label_to_level[node.ibgp_level] ebgp_nodes = [ d for d in g_bgp if any(edge.type == 'ebgp' for edge in d.edges()) ] g_bgp.update(ebgp_nodes, ebgp=True) for ebgp_edge in g_bgp.edges(type="ebgp"): for interface in ebgp_edge.interfaces(): interface.ebgp = True for edge in g_bgp.edges(type='ibgp'): # TODO: need interface querying/selection. rather than hard-coded ids edge.bind_interface(edge.src, 0) #TODO: need to initialise interface zero to be a loopback rather than physical type for node in g_bgp: for interface in node.interfaces(): interface.multipoint = any(e.multipoint for e in interface.edges()) build_ibgp_v4(anm) build_ibgp_v6(anm)
def build_bgp(anm): """Build iBGP end eBGP overlays""" # eBGP g_in = anm["input"] g_phy = anm["phy"] g_bgp = anm.add_overlay("bgp", directed=True) g_bgp.add_nodes_from(g_in.nodes("is_router")) ebgp_edges = [edge for edge in g_in.edges() if not edge.attr_equal("asn")] g_bgp.add_edges_from(ebgp_edges, bidirectional=True, type="ebgp") # TODO: why don't we include edge_id here ebgp_switches = [n for n in g_in.nodes("is_switch") if not ank_utils.neigh_equal(g_phy, n, "asn")] g_bgp.add_nodes_from(ebgp_switches, retain=["asn"]) log.debug("eBGP switches are %s" % ebgp_switches) g_bgp.add_edges_from( (e for e in g_in.edges() if e.src in ebgp_switches or e.dst in ebgp_switches), bidirectional=True, type="ebgp" ) ank_utils.aggregate_nodes(g_bgp, ebgp_switches, retain="edge_id") ebgp_switches = list(g_bgp.nodes("is_switch")) # need to recalculate as may have aggregated log.debug("aggregated eBGP switches are %s" % ebgp_switches) exploded_edges = ank_utils.explode_nodes(g_bgp, ebgp_switches, retain="edge_id") for edge in exploded_edges: edge.multipoint = True # now iBGP ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_level") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l2_cluster") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l3_cluster") for node in g_bgp: # set defaults if node.ibgp_level is None: node.ibgp_level = 1 if node.ibgp_level == "None": # if unicode string from yEd node.ibgp_level = 1 # TODO CHECK FOR IBGP NONE node.ibgp_level = int(node.ibgp_level) # ensure is numeric if not node.ibgp_l2_cluster or node.ibgp_l2_cluster == "None": # ibgp_l2_cluster defaults to region node.ibgp_l2_cluster = node.region or "default_l2_cluster" if not node.ibgp_l3_cluster or node.ibgp_l3_cluster == "None": # ibgp_l3_cluster defaults to ASN node.ibgp_l3_cluster = node.asn for asn, devices in ank_utils.groupby("asn", g_bgp): # group by nodes in phy graph routers = list(g_bgp.node(n) for n in devices if n.is_router) # list of nodes from bgp graph ibgp_levels = {int(r.ibgp_level) for r in routers} max_level = max(ibgp_levels) # all possible edge src/dst pairs ibgp_routers = [r for r in routers if r.ibgp_level > 0] all_pairs = [(s, t) for s in ibgp_routers for t in ibgp_routers if s != t] if max_level == 3: up_links, down_links, over_links = three_tier_ibgp_edges(ibgp_routers) elif max_level == 2: up_links, down_links, over_links = build_two_tier_ibgp(ibgp_routers) elif max_level == 1: up_links = [] down_links = [] over_links = [ (s, t) for (s, t) in all_pairs if s.ibgp_l3_cluster == t.ibgp_l3_cluster and s.ibgp_l2_cluster == t.ibgp_l2_cluster ] else: # no iBGP up_links = [] down_links = [] over_links = [] if max_level > 0: g_bgp.add_edges_from(up_links, type="ibgp", direction="up") g_bgp.add_edges_from(down_links, type="ibgp", direction="down") g_bgp.add_edges_from(over_links, type="ibgp", direction="over") else: log.debug("No iBGP routers in %s" % asn) # and set label back ibgp_label_to_level = {0: "None", 3: "RR", 1: "RRC", 2: "HRR"} # Explicitly set role to "None" -> Not in iBGP for node in g_bgp: node.ibgp_role = ibgp_label_to_level[node.ibgp_level] ebgp_nodes = [d for d in g_bgp if any(edge.type == "ebgp" for edge in d.edges())] g_bgp.update(ebgp_nodes, ebgp=True) for ebgp_edge in g_bgp.edges(type="ebgp"): for interface in ebgp_edge.interfaces(): interface.ebgp = True for edge in g_bgp.edges(type="ibgp"): # TODO: need interface querying/selection. rather than hard-coded ids edge.bind_interface(edge.src, 0) # TODO: need to initialise interface zero to be a loopback rather than physical type for node in g_bgp: for interface in node.interfaces(): interface.multipoint = any(e.multipoint for e in interface.edges())
def build_ibgp(anm): g_in = anm['input'] g_bgp = anm['bgp'] # TODO: build direct to ibgp graph - can construct combined bgp for vis #TODO: normalise input property ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_role") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l2_cluster", "hrr_cluster", default=None) ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l3_cluster", "rr_cluster", default=None) # TODO: add more detailed logging for n in g_bgp: # Tag with label to make logic clearer if n.ibgp_role is None: n.ibgp_role = "Peer" # TODO: if top-level, then don't mark as RRC ibgp_nodes = [n for n in g_bgp if not n.ibgp_role is "Disabled"] # Notify user of non-ibgp nodes non_ibgp_nodes = [n for n in g_bgp if n.ibgp_role is "Disabled"] if 0 < len(non_ibgp_nodes) < 10: log.info("Skipping iBGP for iBGP disabled nodes: %s", non_ibgp_nodes) elif len(non_ibgp_nodes) >= 10: log.info("Skipping iBGP for more than 10 iBGP disabled nodes:" "refer to visualization for resulting topology.") # warn for any nodes that have RR set but no rr_cluster, or HRR set and no # hrr_cluster rr_mismatch = [ n for n in ibgp_nodes if n.ibgp_role == "RR" and n.rr_cluster is None ] if len(rr_mismatch): log.warning( "Some routers are set as RR but have no rr_cluster: %s. Please specify an rr_cluster for peering." % ", ".join(str(n) for n in rr_mismatch)) hrr_mismatch = [ n for n in ibgp_nodes if n.ibgp_role == "HRR" and n.hrr_cluster is None ] if len(hrr_mismatch): log.warning( "Some routers are set as HRR but have no hrr_cluster: %s. Please specify an hrr_cluster for peering." % ", ".join(str(n) for n in hrr_mismatch)) for _, asn_devices in ank_utils.groupby("asn", ibgp_nodes): asn_devices = list(asn_devices) # iBGP peer peers with peers = [n for n in asn_devices if n.ibgp_role == "Peer"] rrs = [n for n in asn_devices if n.ibgp_role == "RR"] hrrs = [n for n in asn_devices if n.ibgp_role == "HRR"] rrcs = [n for n in asn_devices if n.ibgp_role == "RRC"] over_links = [] up_links = [] down_links = [] # 0. RRCs can only belong to either an rr_cluster or a hrr_cluster invalid_rrcs = [ r for r in rrcs if r.rr_cluster is not None and r.hrr_cluster is not None ] if len(invalid_rrcs): message = ", ".join(str(r) for r in invalid_rrcs) log.warning( "RRCs can only have either a rr_cluster or hrr_cluster set. " "The following have both set, and only the rr_cluster will be used: %s", message) # TODO: do we also want to warn for RRCs with no cluster set? Do we also exclude these? # TODO: do we also want to warn for HRRs and RRs with no cluster set? # Do we also exclude these? # 1. Peers: # 1a. Peers connect over to peers over_links += [(s, t) for s in peers for t in peers] # 1b. Peers connect over to RRs over_links += [(s, t) for s in peers for t in rrs] # 2. RRs: # 2a. RRs connect over to Peers over_links += [(s, t) for s in rrs for t in peers] # 2b. RRs connect over to RRs over_links += [(s, t) for s in rrs for t in rrs] # 2c. RRs connect down to RRCs in same rr_cluster down_links += [(s, t) for s in rrs for t in rrcs if s.rr_cluster == t.rr_cluster != None] # 2d. RRs connect down to HRRs in the same rr_cluster down_links += [(s, t) for s in rrs for t in hrrs if s.rr_cluster == t.rr_cluster != None] # 3. HRRs # 3a. HRRs connect up to RRs in the same rr_cluster up_links += [(s, t) for s in hrrs for t in rrs if s.rr_cluster == t.rr_cluster != None] # 3b. HRRs connect down to RRCs in same hrr_cluster (providing RRC has # no rr_cluster set) down_links += [ (s, t) for s in hrrs for t in rrcs if s.hrr_cluster == t.hrr_cluster != None and t.rr_cluster is None ] # 4. RRCs # 4a. RRCs connect up to RRs in the same rr_cluster (regardless if RRC # has hrr_cluster set) up_links += [(s, t) for s in rrcs for t in rrs if s.rr_cluster == t.rr_cluster != None] # 3b. RRCs connect up to HRRs in same hrr_cluster (providing RRC has no # rr_cluster set) up_links += [ (s, t) for s in rrcs for t in hrrs if s.hrr_cluster == t.hrr_cluster != None and s.rr_cluster is None ] # Remove self-links over_links = [(s, t) for s, t in over_links if s != t] up_links = [(s, t) for s, t in up_links if s != t] down_links = [(s, t) for s, t in down_links if s != t] g_bgp.add_edges_from(over_links, type='ibgp', direction='over') g_bgp.add_edges_from(up_links, type='ibgp', direction='up') g_bgp.add_edges_from(down_links, type='ibgp', direction='down')
def build_bgp(anm): """Build iBGP end eBGP overlays""" # eBGP g_in = anm['input'] g_bgp = anm.add_overlay("bgp", directed=True) g_bgp.add_nodes_from(g_in.nodes("is_router")) ebgp_edges = [edge for edge in g_in.edges() if not edge.attr_equal("asn")] g_bgp.add_edges_from(ebgp_edges, bidirectional=True, type='ebgp') # now iBGP ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_level") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l2_cluster") ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_l3_cluster") for node in g_bgp: # set defaults if node.ibgp_level is None: node.ibgp_level = 1 if node.ibgp_level == "None": # if unicode string from yEd node.ibgp_level = 1 node.ibgp_level = int(node.ibgp_level) # ensure is numeric if not node.ibgp_l2_cluster or node.ibgp_l2_cluster == "None": # ibgp_l2_cluster defaults to region node.ibgp_l2_cluster = node.region or "default_l2_cluster" if not node.ibgp_l3_cluster or node.ibgp_l3_cluster == "None": # ibgp_l3_cluster defaults to ASN node.ibgp_l3_cluster = node.asn for _, devices in ank_utils.groupby("asn", g_bgp): # group by nodes in phy graph routers = list(g_bgp.node(n) for n in devices if n.is_router) # list of nodes from bgp graph ibgp_levels = {int(r.ibgp_level) for r in routers} max_level = max(ibgp_levels) # all possible edge src/dst pairs all_pairs = [(s, t) for s in routers for t in routers if s != t] if max_level == 3: up_links, down_links, over_links = three_tier_ibgp_edges(routers) if max_level == 2: up_links, down_links, over_links = build_two_tier_ibgp(routers) elif max_level == 1: up_links = [] down_links = [] over_links = [(s, t) for (s, t) in all_pairs if s.ibgp_l3_cluster == t.ibgp_l3_cluster] g_bgp.add_edges_from(up_links, type='ibgp', direction='up') g_bgp.add_edges_from(down_links, type='ibgp', direction='down') g_bgp.add_edges_from(over_links, type='ibgp', direction='over') # and set label back ibgp_label_to_level = { 0: "None", # Explicitly set role to "None" -> Not in iBGP 3: "RR", 1: "RRC", 2: "HRR", } for node in g_bgp: node.ibgp_role = ibgp_label_to_level[node.ibgp_level] ebgp_nodes = [d for d in g_bgp if any( edge.type == 'ebgp' for edge in d.edges())] g_bgp.update(ebgp_nodes, ebgp=True) for edge in g_bgp.edges(type='ibgp'): # TODO: need interface querying/selection. rather than hard-coded ids edge.bind_interface(edge.src, 0)