def get_anns(self): c1 = Community("100:16") c2 = Community("100:17") c3 = Community("100:18") ann1 = Announcement(prefix='Prefix1', peer='Peer1', origin=BGP_ATTRS_ORIGIN.EBGP, as_path=[1, 2, 5, 7, 6], as_path_len=5, next_hop='Hop1', local_pref=100, med=10, communities={ c1: True, c2: False, c3: False }, permitted=True) ann2 = Announcement(prefix='Prefix2', peer='Peer2', origin=BGP_ATTRS_ORIGIN.EBGP, as_path=[9, 2, 5, 7, 8], as_path_len=VALUENOTSET, next_hop='Hop2', local_pref=VALUENOTSET, med=10, communities={ c1: False, c2: False, c3: VALUENOTSET }, permitted=True) return ann1, ann2
def get_anns(self, prefix): c1 = Community("100:16") c2 = Community("100:17") c3 = Community("100:18") ann1 = Announcement( prefix=prefix, peer='R1', origin=BGP_ATTRS_ORIGIN.EBGP, as_path=[100], as_path_len=1, next_hop='Hop1', local_pref=100, med=10, communities={c1: True, c2: False, c3: False}, permitted=True) return [ann1]
def get_announcements(self, num_anns, num_communities): # Communities all_communities = [ Community("100:%d" % i) for i in range(num_communities) ] cs = dict([(c, False) for c in all_communities]) anns = {} for i in range(num_anns): ann = Announcement(prefix='P_%s' % i, peer='ATT', origin=BGP_ATTRS_ORIGIN.EBGP, as_path=[1, 2, 5, 7, 6], as_path_len=5, next_hop='ATTHop', local_pref=100, communities=cs, permitted=True) anns["%s_%s" % (ann.peer, ann.prefix)] = ann return anns
def two_ebgp_nodes(export_path): """ Two routers connected via eBGP Very simple once router announces a single prefix and the other selects it """ graph = NetworkGraph() r1, r2 = 'R1', 'R2' graph.add_router(r1) graph.add_router(r2) graph.add_router_edge(r1, r2) graph.add_router_edge(r2, r1) # BGP configs graph.set_bgp_asnum(r1, 100) graph.set_bgp_asnum(r2, 200) # Establish peering # The actual network interfaces used for peering will be synthesized graph.add_bgp_neighbor(r1, r2, router_a_iface=VALUENOTSET, router_b_iface=VALUENOTSET) # Some internal network net = ip_network(u'128.0.0.0/24') prefix = '128_0_0_0' prefix_map = {prefix: net} lo0 = 'lo0' graph.set_loopback_addr( r1, lo0, ip_interface("%s/%d" % (net.hosts().next(), net.prefixlen))) # Announce the internal network graph.add_bgp_announces(r1, lo0) # The communities recognized by us comms = [Community("100:10"), Community("100:20")] # The announcement that will be propagated by R1 ann = Announcement(prefix=prefix, peer=r1, origin=BGP_ATTRS_ORIGIN.EBGP, next_hop='R1Hop', as_path=[100], as_path_len=1, local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) path = PathReq(Protocols.BGP, prefix, ['R2', 'R1'], False) reqs = [path] # Get SMT Context ctx = create_context(reqs, graph, [ann]) propagation = EBGPPropagation(reqs, graph, ctx) propagation.compute_dags() propagation.synthesize() # Synthesize all the interfaces and link configurations connecte_syn = ConnectedSyn([], graph, full=True) connecte_syn.synthesize() # SMT Solving solver = z3.Solver(ctx=ctx.z3_ctx) assert ctx.check(solver) == z3.sat, solver.unsat_core() # Update graph with the concrete values after solver propagation.update_network_graph() gns3 = GNS3Topo(graph=graph, prefix_map=prefix_map) gns3.write_configs('%s/ibgp-simple' % export_path)
def test_double_import(): """Unit test of Route maps""" graph = NetworkGraph() r1, r2 = 'R1', 'R2' graph.add_router(r1) graph.add_router(r2) graph.add_router_edge(r1, r2) graph.add_router_edge(r2, r1) # BGP configs graph.set_bgp_asnum(r1, 100) graph.set_bgp_asnum(r2, 200) # Establish peering # The actual network interfaces used for peering will be synthesized graph.add_bgp_neighbor(r1, r2, router_a_iface=VALUENOTSET, router_b_iface=VALUENOTSET) # Some internal network net = ip_network(u'128.0.0.0/24') prefix = '128_0_0_0' prefix_map = {prefix: net} lo0 = 'lo0' graph.set_loopback_addr( r1, lo0, ip_interface("%s/%d" % (net.hosts().next(), net.prefixlen))) # Announce the internal network graph.add_bgp_announces(r1, lo0) # The communities recognized by us comms = [Community("100:10"), Community("100:20")] # The announcement that will be propagated by R1 ann = Announcement(prefix=prefix, peer=r1, origin=BGP_ATTRS_ORIGIN.EBGP, next_hop='R1Hop', as_path=[100], as_path_len=1, local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) path = PathReq(Protocols.BGP, prefix, ['R2', 'R1'], False) reqs = [path] ctx = create_context(reqs, graph, [ann], create_as_paths=True) from synet.utils.fnfree_policy import SMTRouteMap rline1 = RouteMapLine(matches=None, actions=None, access=VALUENOTSET, lineno=10) deny_line1 = RouteMapLine(matches=None, actions=None, access=Access.deny, lineno=100) rmap1 = RouteMap(name='Rmap1', lines=[rline1, deny_line1]) rline2 = RouteMapLine(matches=None, actions=None, access=VALUENOTSET, lineno=10) deny_line2 = RouteMapLine(matches=None, actions=None, access=Access.deny, lineno=100) rmap2 = RouteMap(name='Rmap1', lines=[rline2, deny_line2]) sym = get_sym([ann], ctx) smt1 = SMTRouteMap(rmap1, sym, ctx) smt2 = SMTRouteMap(rmap2, smt1.announcements, ctx) print "Original permitted", sym.announcements[0].permitted print "SMT 1 permitted", smt1.announcements[0].permitted print "SMT 2 permitted", smt2.announcements[0].permitted ctx.register_constraint(smt1.announcements[0].permitted.var == True) ctx.register_constraint(smt2.announcements[0].permitted.var == False) solver = z3.Solver(ctx=ctx.z3_ctx) ret = ctx.check(solver) #print solver.to_smt2() assert ret == z3.sat, solver.unsat_core() #print solver.model() print "Original permitted", sym.announcements[0].permitted print "SMT 1 permitted", smt1.announcements[0].permitted print "SMT 2 permitted", smt2.announcements[0].permitted
def linear_ebgp(N, export_path): """ Routers connected in a line and each eBGP pair with it's direct neighbors """ # Topology g = get_ebgp_linear_topo(N) # Announce locally prefix = "Prefix0" net = ip_network(u'128.0.0.0/24') prefix_map = {prefix: net} g.set_loopback_addr( 'R1', 'lo0', ip_interface("%s/%d" % (net.hosts().next(), net.prefixlen))) g.add_bgp_announces('R1', 'lo0') # Announcement comms = [Community("100:10"), Community("100:20")] cs = dict([(c, False) for c in comms]) # The announcement that will be propagated by R1 ann = get_announcement(prefix=prefix, peer='R1', comms=cs) # Set up route maps for i in range(1, N + 1): first = 'R%d' % (i - 1) if i > 1 else None middle = 'R%d' % i last = 'R%d' % (i + 1) if i < N else None if last: matches = [MatchAsPath(VALUENOTSET)] #matches = None rline = RouteMapLine(matches, None, VALUENOTSET, 10) deny_line = RouteMapLine(None, None, Access.deny, 100) rmap = RouteMap('Exp_%s' % last, [rline, deny_line]) g.add_route_map(middle, rmap) g.add_bgp_export_route_map(middle, last, rmap.name) if first: matches = [MatchAsPath(VALUENOTSET)] #matches = None rline = RouteMapLine(matches, None, VALUENOTSET, 10) deny_line = RouteMapLine(None, None, Access.deny, 100) rmap = RouteMap('Imp_%s' % first, [rline, deny_line]) g.add_route_map(middle, rmap) g.add_bgp_import_route_map(middle, first, rmap.name) # nx.nx_pydot.write_dot(g, '/tmp/linear.xdot') req = PathReq(Protocols.BGP, dst_net='Prefix0', path=['R2', 'R1'], strict=False) ctx = create_context([req], g, [ann]) propagation = EBGPPropagation([req], g, ctx) propagation.compute_dags() propagation.synthesize() solver = z3.Solver(ctx=ctx.z3_ctx) ret = ctx.check(solver) assert ret == z3.sat, solver.unsat_core() propagation.update_network_graph() gns3 = GNS3Topo(g, prefix_map) gns3.write_configs('%s/linear-ebgp-%d' % (export_path, N))
def two_ebgp_nodes_route_map(export_path): """ Two routers connected via eBGP with route maps Very simple one router announces a single prefix and the other selects it """ graph = NetworkGraph() r1, r2 = 'R1', 'R2' graph.add_router(r1) graph.add_router(r2) graph.add_router_edge(r1, r2) graph.add_router_edge(r2, r1) # BGP configs graph.set_bgp_asnum(r1, 100) graph.set_bgp_asnum(r2, 200) # Establish peering # The actual network interfaces used for peering will be synthesized graph.add_bgp_neighbor(r1, r2, router_a_iface=VALUENOTSET, router_b_iface=VALUENOTSET) # Some internal network net = ip_network(u'128.0.0.0/24') prefix = '128_0_0_0' prefix_map = {prefix: net} lo0 = 'lo0' graph.set_loopback_addr( r1, lo0, ip_interface("%s/%d" % (net.hosts().next(), net.prefixlen))) # Announce the internal network graph.add_bgp_announces(r1, lo0) # The communities recognized by us comms = [Community("100:10"), Community("100:20")] # The announcement that will be propagated by R1 ann = Announcement(prefix=prefix, peer=r1, origin=BGP_ATTRS_ORIGIN.EBGP, next_hop='R1Hop', as_path=[100], as_path_len=1, local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) path = PathReq(Protocols.BGP, prefix, ['R2', 'R1'], False) reqs = [path] # Create a route map to export from R1 to R2 iplist = IpPrefixList(name='IpList1', access=Access.permit, networks=[prefix]) graph.add_ip_prefix_list(r1, iplist) ip_match = MatchIpPrefixListList(iplist) set_community = ActionSetCommunity([comms[0]]) rline = RouteMapLine(matches=[ip_match], actions=[set_community], access=Access.permit, lineno=10) export_map = RouteMap(name="Export_R1_to_R2", lines=[rline]) # Register the route map graph.add_route_map(r1, export_map) # Set the route map as an export route map graph.add_bgp_export_route_map(r1, r2, export_map.name) # Create a route map to import at R2 to from R1 comm_list = CommunityList(list_id=1, access=Access.permit, communities=[comms[0]]) graph.add_bgp_community_list(r2, comm_list) comm_match = MatchCommunitiesList(comm_list) set_local_pref = ActionSetLocalPref(200) rline = RouteMapLine(matches=[MatchNextHop(VALUENOTSET)], actions=[set_local_pref], access=Access.permit, lineno=10) import_map = RouteMap(name="Import_R2_from_R1", lines=[rline]) # Register the route map graph.add_route_map(r2, import_map) # Set the route map as an import route map graph.add_bgp_import_route_map(r2, r1, import_map.name) # Get SMT Context ctx = create_context(reqs, graph, [ann]) propagation = EBGPPropagation(reqs, graph, ctx) propagation.compute_dags() propagation.synthesize() # Synthesize all the interfaces and link configurations connecte_syn = ConnectedSyn([], graph, full=True) connecte_syn.synthesize() # SMT Solving solver = z3.Solver(ctx=ctx.z3_ctx) assert ctx.check(solver) == z3.sat, solver.unsat_core() # Update graph with the concrete values after solver propagation.update_network_graph() gns3 = GNS3Topo(graph=graph, prefix_map=prefix_map) gns3.write_configs('%s/ebgp-route-map' % export_path) graph.write_graphml('%s/ebgp-route-map/topology.graphml' % export_path)
def get_cust_peer_linear_topo(self): graph = gen_mesh(2, 100) r1, r2 = 'R1', 'R2' graph.enable_ospf(r1) graph.enable_ospf(r2) graph.add_ospf_network(r1, 'Fa0/0', '0.0.0.0') # Add two providers and one customer provider1 = 'Provider1' provider2 = 'Provider2' customer = 'Customer' graph.add_peer(provider1) graph.add_peer(provider2) graph.add_peer(customer) graph.set_bgp_asnum(provider1, 400) graph.set_bgp_asnum(provider2, 500) graph.set_bgp_asnum(customer, 600) graph.add_peer_edge(r1, provider1) graph.add_peer_edge(provider1, r1) graph.add_peer_edge(r1, provider2) graph.add_peer_edge(provider2, r1) graph.add_peer_edge(r2, customer) graph.add_peer_edge(customer, r2) # Establish BGP peering graph.add_bgp_neighbor(provider1, r1) graph.add_bgp_neighbor(provider2, r1) graph.add_bgp_neighbor(customer, r2) net1 = ip_network(u'128.0.0.0/24') net2 = ip_network(u'128.0.1.0/24') prefix1 = str(net1) prefix2 = str(net2) iface_addr1 = ip_interface("%s/%d" % (net1.hosts().next(), net1.prefixlen)) graph.set_loopback_addr(provider1, 'lo10', iface_addr1) graph.set_loopback_addr(provider2, 'lo10', iface_addr1) iface_addr2 = ip_interface("%s/%d" % (net2.hosts().next(), net2.prefixlen)) graph.set_loopback_addr(customer, 'lo10', iface_addr2) # Announce IGP internally graph.add_ospf_network(r1, 'lo100', area='0.0.0.0') graph.add_ospf_network(r2, 'lo100', area='0.0.0.0') # Known communities comms = [Community("100:{}".format(c)) for c in range(1, 4)] # The symbolic announcement injected by provider1 ann1 = Announcement(prefix1, peer=provider1, origin=BGP_ATTRS_ORIGIN.INCOMPLETE, as_path=[1000, 2000, 5000], # We assume it learned from other upstream ASes as_path_len=3, next_hop='{}Hop'.format(provider1), local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) # The symbolic announcement injected by provider1 # Note it has a shorter AS Path ann2 = Announcement(str(prefix1), peer=provider2, origin=BGP_ATTRS_ORIGIN.INCOMPLETE, as_path=[3000, 600, 9000, 5000], # We assume it learned from other upstream ASes as_path_len=4, next_hop='{}Hop'.format(provider2), local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) # The symbolic announcement injected by customer ann3 = Announcement(prefix2, peer=customer, origin=BGP_ATTRS_ORIGIN.INCOMPLETE, as_path=[], as_path_len=0, next_hop='{}Hop'.format(customer), local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) return graph, [ann1, ann2, ann3]
def bgp_example(output_dir): # Generate the basic network of three routers graph = gen_mesh(3, 100) r1, r2, r3, r4 = 'R1', 'R2', 'R3', 'R4' # Enable OSPF in the sketch for node in graph.local_routers_iter(): graph.enable_ospf(node, 100) # Edge weights are symbolic for src, dst in graph.edges(): graph.set_edge_ospf_cost(src, dst, VALUENOTSET) graph.set_loopback_addr(r3, 'lo100', VALUENOTSET) graph.set_loopback_addr(r2, 'lo100', VALUENOTSET) graph.add_ospf_network(r1, 'lo100', '0.0.0.0') graph.add_ospf_network(r2, 'lo100', '0.0.0.0') graph.add_ospf_network(r3, 'lo100', '0.0.0.0') graph.add_ospf_network(r1, 'Fa0/0', '0.0.0.0') graph.add_ospf_network(r2, 'Fa0/0', '0.0.0.0') graph.add_ospf_network(r3, 'Fa0/0', '0.0.0.0') # Add two providers and one customer provider1 = 'Provider1' provider2 = 'Provider2' customer = 'Customer' graph.add_peer(provider1) graph.add_peer(provider2) graph.add_peer(customer) graph.set_bgp_asnum(provider1, 400) graph.set_bgp_asnum(provider2, 500) graph.set_bgp_asnum(customer, 600) graph.add_peer_edge(r2, provider1) graph.add_peer_edge(provider1, r2) graph.add_peer_edge(r3, provider2) graph.add_peer_edge(provider2, r3) graph.add_peer_edge(r1, customer) graph.add_peer_edge(customer, r1) # Establish BGP peering graph.add_bgp_neighbor(provider1, r2) graph.add_bgp_neighbor(provider2, r3) graph.add_bgp_neighbor(customer, r1) # The traffic class announced by the two providers net1 = ip_network(u'128.0.0.0/24') # The traffic class announced by the customer net2 = ip_network(u'128.0.1.0/24') prefix1 = str(net1) prefix2 = str(net2) # Known communities comms = [Community("100:{}".format(c)) for c in range(1, 4)] # The symbolic announcement injected by provider1 ann1 = Announcement( prefix1, peer=provider1, origin=BGP_ATTRS_ORIGIN.INCOMPLETE, as_path=[5000], # We assume it learned from other upstream ASes as_path_len=1, #next_hop='0.0.0.0', next_hop='{}Hop'.format(provider1), local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) # The symbolic announcement injected by provider1 # Note it has a shorter AS Path ann2 = Announcement( prefix1, peer=provider2, origin=BGP_ATTRS_ORIGIN.INCOMPLETE, as_path=[3000, 5000], # We assume it learned from other upstream ASes as_path_len=2, next_hop='0.0.0.0', local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) # The symbolic announcement injected by customer ann3 = Announcement(prefix2, peer=customer, origin=BGP_ATTRS_ORIGIN.INCOMPLETE, as_path=[], as_path_len=0, next_hop='0.0.0.0', local_pref=100, med=100, communities=dict([(c, False) for c in comms]), permitted=True) graph.add_bgp_advertise(provider1, ann1, loopback='lo100') graph.set_loopback_addr(provider1, 'lo100', ip_interface(net1.hosts().next())) graph.add_bgp_advertise(provider2, ann2, loopback='lo100') graph.set_loopback_addr(provider2, 'lo100', ip_interface(net1.hosts().next())) graph.add_bgp_advertise(customer, ann3, loopback='lo100') graph.set_loopback_addr(customer, 'lo100', ip_interface(net2.hosts().next())) ########################## Configuration sketch ############################### for local, peer in [(r2, provider1), (r3, provider2)]: imp_name = "{}_import_from_{}".format(local, peer) exp_name = "{}_export_to_{}".format(local, peer) imp = RouteMap.generate_symbolic(name=imp_name, graph=graph, router=local) exp = RouteMap.generate_symbolic(name=exp_name, graph=graph, router=local) graph.add_bgp_import_route_map(local, peer, imp.name) graph.add_bgp_export_route_map(local, peer, exp.name) for local, peer in [(r2, r3), (r3, r2)]: # In Cisco the last line is a drop by default rline1 = RouteMapLine(matches=[], actions=[], access=VALUENOTSET, lineno=10) from tekton.bgp import Access rline2 = RouteMapLine(matches=[], actions=[], access=Access.deny, lineno=100) rmap_export = RouteMap(name='{}_export_{}'.format(local, peer), lines=[rline1, rline2]) graph.add_route_map(local, rmap_export) graph.add_bgp_export_route_map(local, peer, rmap_export.name) # Requirements path1 = PathReq(Protocols.BGP, prefix1, [customer, r1, r2, provider1], False) path2 = PathReq(Protocols.BGP, prefix1, [customer, r1, r3, r2, provider1], False) path3 = PathReq(Protocols.BGP, prefix1, [r3, r1, r2, provider1], False) path4 = PathReq(Protocols.BGP, prefix1, [customer, r1, r3, provider2], False) path5 = PathReq(Protocols.BGP, prefix1, [customer, r1, r2, r3, provider2], False) path6 = PathReq(Protocols.BGP, prefix1, [r2, r1, r3, provider2], False) reqs = [ PathOrderReq(Protocols.BGP, prefix1, [ KConnectedPathsReq(Protocols.BGP, prefix1, [path1, path2, path3], False), KConnectedPathsReq(Protocols.BGP, prefix1, [path4, path5, path6], False), ], False), PathOrderReq(Protocols.OSPF, "dummy", [ PathReq(Protocols.OSPF, "dummy", [r1, r2], False), PathReq(Protocols.OSPF, "dummy", [r1, r3, r2], False), ], False), PathOrderReq(Protocols.OSPF, "dummy", [ PathReq(Protocols.OSPF, "dummy", [r1, r3], False), PathReq(Protocols.OSPF, "dummy", [r1, r2, r3], False), ], False), ] external_anns = [ann1, ann2, ann3] netcomplete = NetComplete(reqs=reqs, topo=graph, external_announcements=external_anns) netcomplete.synthesize() netcomplete.write_configs(output_dir=output_dir)
def bgp(n, nreqs=10): req_file = './topos/cav/gridrand%d-bgp-%d-req.logic' % (n, nreqs) topo = gen_grid_topology(n, n, 0) static_reqs, ospf_reqs, bgp_reqs = read_reqs(req_file) seed = 159734782 path_gen = 200 ospfRand = random.Random(seed) for node in topo.routers_iter(): topo.enable_ospf(node, 100) topo.set_bgp_asnum(node, 100) # Initially all costs are empty topo.set_static_routes_empty(node) for src, dst in topo.edges(): topo.set_edge_ospf_cost(src, dst, VALUENOTSET) peer = 'ATT' egresses = set([p.path[-2] for p in bgp_reqs]) topo.add_peer(peer) topo.set_bgp_asnum(peer, 5000) for req in bgp_reqs: req.path.append(peer) for egress in egresses: topo.add_peer_edge(peer, egress) topo.add_peer_edge(egress, peer) topo.add_bgp_neighbor(peer, egress, VALUENOTSET, VALUENOTSET) for src in topo.local_routers_iter(): for dst in topo.local_routers_iter(): if src == dst or dst in topo.get_bgp_neighbors(src): continue topo.add_bgp_neighbor(src, dst, VALUENOTSET, VALUENOTSET) prefix = 'GOOGLE' communities = [Community("100:%d" % i) for i in range(5)] ann = Announcement(prefix=prefix, peer=peer, origin=BGP_ATTRS_ORIGIN.EBGP, as_path=[1, 2, 5000], as_path_len=3, next_hop='%sHop' % peer, local_pref=100, med=10, communities=dict([(c, False) for c in communities]), permitted=True) topo.add_bgp_advertise(peer, ann) conn = ConnectedSyn([], topo, full=True) conn.synthesize() static_syn = StaticSyn(static_reqs, topo) static_syn.synthesize() ospf = OSPFCEGIS(topo, gen_paths=path_gen, random_obj=ospfRand) for req in ospf_reqs: ospf.add_req(req) assert ospf.synthesize(allow_ecmp=True) assert not ospf.removed_reqs for router in topo.local_routers_iter(): count = itertools.count(1) for neighbor in topo.get_bgp_neighbors(router): if router == neighbor: continue comm_list = CommunityList( list_id=count.next(), access=Access.permit, communities=[VALUENOTSET, VALUENOTSET, VALUENOTSET]) topo.add_bgp_community_list(router, comm_list) match_comm = MatchCommunitiesList(comm_list) iplist = IpPrefixList(name='ip%s' % count.next(), access=Access.permit, networks=[VALUENOTSET]) topo.add_ip_prefix_list(router, iplist) match_ip = MatchIpPrefixListList(iplist) match_next_hop = MatchNextHop(VALUENOTSET) match_sel = MatchSelectOne([match_comm, match_next_hop, match_ip]) actions = [ ActionSetLocalPref(VALUENOTSET), ActionSetCommunity([VALUENOTSET], True) ] rline = RouteMapLine([match_sel], actions, VALUENOTSET, 10) dline = RouteMapLine(None, None, Access.deny, 100) rmap = RouteMap("Rimp_%s_from_%s" % (router, neighbor), lines=[rline, dline]) topo.add_route_map(router, rmap) topo.add_bgp_import_route_map(router, neighbor, rmap.name) ctx = create_context(bgp_reqs, topo, [ann]) p = EBGPPropagation(bgp_reqs, topo, ctx) p.compute_dags() p.synthesize() solver = z3.Solver() ret = ctx.check(solver) assert ret == z3.sat, solver.unsat_core() p.update_network_graph()