def __init__(self, network, services, igp="ospf", target=None, olive_qemu_patched=False): self.network = network self.services = services self.igp = igp self.target = target self.olive_qemu_patched = olive_qemu_patched self.interface_limit = 0 #TODO: tidy up platform: Olive/Junosphere between the vmm and the device configs self.junosphere = False self.junosphere_olive = False if target in ['junosphere', 'junosphere_olive']: self.junosphere = True self.int_id_em = ank.naming.junos_int_id_em self.junosphere_platform = config.settings['Junosphere']['platform'] if self.junosphere_platform == "Olive": self.junosphere_olive = True self.target = "junosphere_olive" self.olive_qemu_patched = config.settings['Junosphere']['olive_qemu_patched'] self.int_id_em = ank.interface_id(self.target, olive_qemu_patched=olive_qemu_patched) else: self.interface_limit = 256 # TODO: check upper bound for VJX self.int_id = ank.interface_id(self.target, olive_qemu_patched=olive_qemu_patched) self.olive = False if self.target in ['olive', 'junosphere_olive']: self.olive = True if self.olive: self.interface_limit = 7 if self.olive_qemu_patched: self.interface_limit = 8 # Patch allows 8 interfaces
def configure_interfaces(self, device): LOG.debug("Configuring interfaces for %s" % self.network.fqdn(device)) """Interface configuration""" lo_ip = self.network.lo_ip(device) interfaces = [] interfaces.append({ 'id': 'lo0', 'ip': str(lo_ip.ip), 'netmask': str(lo_ip.netmask), 'prefixlen': str(lo_ip.prefixlen), 'net_ent_title': ank.ip_to_net_ent_title(lo_ip.ip), 'description': 'Loopback', }) for src, dst, data in self.network.graph.edges(device, data=True): subnet = data['sn'] int_id = self.int_id(data['id']) description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) # Interface information for router config interfaces.append({ 'id': int_id, 'ip': str(data['ip']), 'prefixlen': str(subnet.prefixlen), 'broadcast': str(subnet.broadcast), 'description': description, }) return interfaces
def load(self, filename): """Loads the network description from a graph file. Note this is done automatically if a filename is given to the Internet constructor. Args: filename: The file to load from Returns: None Example usage: >>> inet = ank.internet.Internet() >>> inet.load("simple") >>> sorted(inet.network.graph.nodes()) [RouterB.AS1, RouterA.AS1, RouterD.AS2, RouterC.AS1, RouterA.AS2, RouterA.AS3, RouterB.AS2, RouterC.AS2] >>> inet = ank.internet.Internet() >>> inet.load("singleas") >>> sorted(inet.network.graph.nodes()) [1a.AS1, 1b.AS1, 1d.AS1, 1c.AS1] >>> inet = ank.internet.Internet() >>> inet.load("multias") >>> sorted(inet.network.graph.nodes()) [1b.AS1, 1a.AS1, 2d.AS2, 1c.AS1, 2a.AS2, 3a.AS3, 2b.AS2, 2c.AS2] """ LOG.info("Loading") ext = os.path.splitext(filename)[1] if ext == "": #TODO: use try/except block here self.network.graph = ank.load_example(filename) #TODO: allow url to be entered, eg from zoo, if so then download the file and proceed on as normal elif ext == ".gml": # GML file from Topology Zoo ank.load_zoo(self.network, filename) elif ext == ".graphml": self.network.graph = ank.load_graphml(filename) elif ext == ".pickle": LOG.warn("AutoNetkit no longer supports pickle file format, please use GraphML") elif ext == ".yaml": # Legacy ANK file format LOG.warn("AutoNetkit no longer supports YAML file format, please use GraphML") else: LOG.warn("AutoNetkit does not support file format %s" % ext) #TODO: check that loaded network has at least one node, if not throw exception self.network.instantiate_nodes()
def dump(self): """Dumps overlay graphs to file .. note:: Doesn't currently support saving graphs - NetworkX cannot save nodes/edges with dictionary attributes """ with open( os.path.join(config.log_dir, "physical.txt"), 'w') as f_pol_dump: f_pol_dump.write(ank.debug_nodes(self.network.graph)) f_pol_dump.write(ank.debug_edges(self.network.graph)) #nx.write_graphml(self.network.graph, os.path.join(config.log_dir, "physical.graphml")) with open( os.path.join(config.log_dir, "bgp.txt"), 'w') as f_pol_dump: f_pol_dump.write(ank.debug_nodes(self.network.g_session)) f_pol_dump.write(ank.debug_edges(self.network.g_session)) #nx.write_graphml(self.network.g_session, os.path.join(config.log_dir, "bgp.graphml")) with open( os.path.join(config.log_dir, "dns.txt"), 'w') as f_pol_dump: f_pol_dump.write(ank.debug_nodes(self.network.g_dns)) f_pol_dump.write(ank.debug_edges(self.network.g_session)) #nx.write_graphml(self.network.g_session, os.path.join(config.log_dir, "dns.graphml")) with open( os.path.join(config.log_dir, "dns_auth.txt"), 'w') as f_pol_dump: f_pol_dump.write(ank.debug_nodes(self.network.g_dns_auth)) f_pol_dump.write(ank.debug_edges(self.network.g_dns_auth))
def configure_junos(self): """ Configures Junos""" LOG.info("Configuring Junos: %s" % self.target) junos_template = lookup.get_template("junos/junos.mako") ank_version = pkg_resources.get_distribution("AutoNetkit").version date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) physical_graph = self.network.graph igp_graph = ank.igp_graph(self.network) ibgp_graph = ank.get_ibgp_graph(self.network) ebgp_graph = ank.get_ebgp_graph(self.network) #TODO: correct this router type selector for router in self.network.routers(): #check interfaces feasible if self.network.graph.in_degree(router) > self.interface_limit: LOG.warn("%s exceeds interface count: %s (max %s)" % (self.network.label(router), self.network.graph.in_degree(router), self.interface_limit)) asn = self.network.asn(router) network_list = [] lo_ip = self.network.lo_ip(router) interfaces,static_routes = self.configure_interfaces(router) igp_interfaces = self.configure_igp(router, igp_graph,ebgp_graph) (bgp_groups, policy_options) = self.configure_bgp(router, physical_graph, ibgp_graph, ebgp_graph) # advertise AS subnet adv_subnet = self.network.ip_as_allocs[asn] if not adv_subnet in network_list: network_list.append(adv_subnet) juniper_filename = router_conf_path(self.network, router) with open( juniper_filename, 'wb') as f_jun: f_jun.write( junos_template.render( hostname = router.rtr_folder_name, username = '******', interfaces=interfaces, static_routes=static_routes, igp_interfaces=igp_interfaces, igp_protocol = self.igp, asn = asn, lo_ip=lo_ip, router_id = lo_ip.ip, network_list = network_list, bgp_groups = bgp_groups, policy_options = policy_options, ank_version = ank_version, date = date, ))
def configure_igp(self, router, igp_graph, ebgp_graph): """igp configuration""" LOG.debug("Configuring IGP for %s" % self.network.label(router)) #TODO: get area from router default_area = 0 igp_interfaces = [] if igp_graph.degree(router) > 0: # Only start IGP process if IGP links #TODO: make loopback a network mask so don't have to do "0.0.0.0" igp_interfaces.append({ 'id': 'lo0', 'wildcard': router.lo_ip.hostmask, 'passive': False, 'network': router.lo_ip.network, 'area': default_area, 'weight': self.default_weight, }) for src, dst, data in igp_graph.edges(router, data=True): int_id = self.int_id(data['id']) subnet = self.network.graph[src][dst]['sn'] description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) igp_interfaces.append({ 'id': int_id, 'weight': data.get('weight', self.default_weight), 'area': data.get('area', default_area), 'network': str(subnet.network), 'description': description, 'wildcard': str(subnet.hostmask), }) # Need to add eBGP edges as passive interfaces for src, dst in ebgp_graph.edges(router): # Get relevant edges from ebgp_graph, and edge data from physical graph data = self.network.graph[src][dst] int_id = self.int_id(data['id']) subnet = self.network.graph[src][dst]['sn'] description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) igp_interfaces.append({ 'id': int_id, 'weight': data.get('weight', self.default_weight), 'area': data.get('area', default_area), 'description': description, 'passive': True, 'network': str(subnet.network), 'wildcard': str(subnet.hostmask), }) return igp_interfaces
def configure_ios(self): """ Configures IOS""" LOG.info("Configuring IOS") ios_template = lookup.get_template("cisco/ios.mako") ank_version = pkg_resources.get_distribution("AutoNetkit").version date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) physical_graph = self.network.graph igp_graph = ank.igp_graph(self.network) ibgp_graph = ank.get_ibgp_graph(self.network) ebgp_graph = ank.get_ebgp_graph(self.network) for router in self.network.routers(): #check interfaces feasible #TODO: make in_degree a property eg link_count asn = self.network.asn(router) network_list = [] lo_ip = self.network.lo_ip(router) interfaces = self.configure_interfaces(router) igp_interfaces = self.configure_igp(router, igp_graph,ebgp_graph) (bgp_groups, policy_options) = self.configure_bgp(router, physical_graph, ibgp_graph, ebgp_graph) # advertise AS subnet adv_subnet = self.network.ip_as_allocs[asn] if not adv_subnet in network_list: network_list.append(adv_subnet) juniper_filename = router_conf_path(self.network, router) with open( juniper_filename, 'wb') as f_jun: f_jun.write( ios_template.render( hostname = router.rtr_folder_name, username = '******', interfaces=interfaces, igp_interfaces=igp_interfaces, igp_protocol = self.igp, # explicit protocol use_isis = self.igp == 'isis', asn = asn, lo_ip=lo_ip, #TODO: make router have property "identifier" which maps to lo_ip router_id = lo_ip.ip, network_list = network_list, bgp_groups = bgp_groups, policy_options = policy_options, ank_version = ank_version, date = date, ))
def __init__(self, network, services, zebra_password="******"): self.network = network self.services = services self.zebra_password = zebra_password self.interface_id = ank.interface_id('netkit') self.tap_interface_id = ank.tap_interface_id self.lo_interface = lo_interface self.default_weight = 1
def dump_identifiers(network, filename): with open( filename, 'w') as f_dump: # writes out lo_ip for routers, and identifying IP for servers for my_as in ank.get_as_graphs(network): for router in sorted(network.routers(my_as.asn), key = lambda x: x.fqdn): f_dump.write( "%s\t%s\n" % (router, router.lo_ip.ip)) for server in sorted(network.servers(my_as.asn), key = lambda x: x.fqdn): f_dump.write( "%s\t%s\n" % (server, server_ip(server))) f_dump.write("\n")
def configure_interfaces(self, device): LOG.debug("Configuring interfaces for %s" % self.network.fqdn(device)) """Interface configuration""" lo_ip = self.network.lo_ip(device) interfaces = [] static_routes = [] interfaces.append({ 'id': 'lo0', 'ip': str(lo_ip.ip), 'netmask': str(lo_ip.netmask), 'prefixlen': str(lo_ip.prefixlen), 'net_ent_title': ank.ip_to_net_ent_title(lo_ip.ip), 'description': 'Loopback', }) for src, dst, data in self.network.graph.edges(device, data=True): neighbor = ank.fqdn(self.network, dst) subnet = data['sn'] int_id = self.int_id(data['id']) description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) # Interface information for router config interfaces.append({ 'id': int_id, 'ip': str(data['ip']), 'prefixlen': str(subnet.prefixlen), 'netmask': str(subnet.netmask), 'broadcast': str(subnet.broadcast), 'description': description, }) #static routes for the dummy nodes for virtual in sorted(self.network.virtual_nodes(), key = lambda x: x.fqdn): virtual_hostname = virtual.hostname if neighbor == virtual_hostname: subnet = data['sn'] static_routes.append({ 'network': str(subnet.network), 'prefixlen': str(subnet.prefixlen), 'ip': str(data['ip']), }) return interfaces,static_routes
def configure_igp(self, router, igp_graph, ebgp_graph): """igp configuration""" LOG.debug("Configuring IGP for %s" % self.network.label(router)) default_weight = 1 igp_interfaces = [] if igp_graph.degree(router) > 0: # Only start IGP process if IGP links igp_interfaces.append({ 'id': 'lo0', 'passive': True}) for src, dst, data in igp_graph.edges(router, data=True): int_id = ank.junos_logical_int_id(self.int_id(data['id'])) description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) igp_interfaces.append({ 'id': int_id, 'weight': data.get('weight', default_weight), 'description': description, }) # Need to add eBGP edges as passive interfaces for src, dst in ebgp_graph.edges(router): # Get relevant edges from ebgp_graph, and edge data from physical graph data = self.network.graph[src][dst] int_id = ank.junos_logical_int_id(self.int_id(data['id'])) description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) igp_interfaces.append({ 'id': int_id, 'weight': data.get('weight', default_weight), 'description': description, 'passive': True, }) return igp_interfaces
def configure_interfaces(self, device): LOG.debug("Configuring interfaces for %s" % self.network.fqdn(device)) """Interface configuration""" lo_ip = self.network.lo_ip(device) interfaces = [] interfaces.append({ 'id': 'lo0', 'ip': lo_ip.ip, 'netmask': lo_ip.netmask, 'wildcard': lo_ip.hostmask, 'prefixlen': lo_ip.prefixlen, 'network': lo_ip.network, 'description': 'Loopback', }) for src, dst, data in self.network.graph.edges(device, data=True): subnet = data['sn'] int_id = self.interface_id(data['id']) description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) # Interface information for router config interfaces.append({ 'id': int_id, 'ip': data['ip'], 'network': subnet.network, 'prefixlen': subnet.prefixlen, 'netmask': subnet.netmask, 'wildcard': subnet.hostmask, 'broadcast': subnet.broadcast, 'description': description, 'weight': data.get('weight', self.default_weight), }) return interfaces
def plot(network, show=False, save=True): """ Plot the network """ try: import matplotlib.pyplot as plt except ImportError: LOG.warn("Matplotlib not found, not plotting using Matplotlib") return try: import numpy except ImportError: LOG.warn("Matplotlib plotting requires numpy for graph layout") return plot_dir = config.plot_dir if not os.path.isdir(plot_dir): os.mkdir(plot_dir) graph = network.graph pos=nx.spring_layout(graph) # Different node color for each AS. Use heatmap based on ASN plot_graph(graph, title="Network", pos=pos, show=show, save=save, node_color=cmap_index(network, graph)) graph = ank.get_ebgp_graph(network) labels = dict( (n, network.label(n)) for n in graph) plot_graph(graph, title="eBGP", pos=pos, labels=labels, show=show, save=save) graph = ank.get_ibgp_graph(network) labels = dict( (n, network.label(n)) for n in graph) plot_graph(graph, title="iBGP", pos=pos, labels=labels, show=show, save=save) graph = ank.get_dns_graph(network) labels = dict( (n, network.label(n)) for n in graph) plot_graph(graph, title="DNS", pos=pos, labels=labels, show=show, save=save)
def inv_cap_weights(network): """Updates link weights based on inverse of link speed.""" #TODO: rewrite this to be cleaner iteration and setting for graph in ank.get_as_graphs(network): for (src, dst, data) in graph.edges_iter(data=True): # only update if non default weight if 'speed' in data and 'weight' in data and data['weight'] == 1: # assume largest link is 10gb #TODO: use Cisco guidelines for this scale_speed = 100000 speed = float(data['speed']) weight = int((1/speed)*scale_speed) weight = max(weight, 1) if weight is 0: weight = 1 graph[src][dst]['weight'] = weight network.set_edge_property(src, dst, 'weight', weight) return
def load_example(filename): """ Load example network """ # No extension, see if filename is an included example Topology topology_dir = resource_filename("AutoNetkit", os.path.join("lib", "examples", "topologies")) test_filename = os.path.join(topology_dir, "%s.graphml" % filename) if os.path.isfile(test_filename): LOG.info("Loading example topology %s " % filename) return ank.load_graphml(test_filename) else: example_files = glob.glob(topology_dir + os.sep + "*.graphml") # Remove path example_files = (os.path.split(filename)[1] for filename in example_files) # Remove extension example_files = (os.path.splitext(filename)[0] for filename in example_files) LOG.warn("Unable to find example topology %s" % filename) LOG.info("Valid example topologies are: " + ", ".join(example_files))
def router_conf_file(network, router): """Returns filename for config file for router""" return "%s.conf" % ank.rtr_folder_name(network, router)
def dns_host_portion_only(self): return ank.dns_host_portion_only(self)
def configure_bgp(self, router, physical_graph, ibgp_graph, ebgp_graph): LOG.debug("Configuring BGP for %s" % self.network.fqdn(router)) """ BGP configuration""" #TODO: Don't configure iBGP or eBGP if no eBGP edges # need to pass correct blank dicts to templates then... #TODO: put comments in for junos bgp peerings # route maps bgp_groups = {} route_maps = [] if router in ibgp_graph: internal_peers = [] for peer in ibgp_graph.neighbors(router): if not peer.is_router: #no iBGP peering to non-routers continue route_maps_in = [route_map for route_map in self.network.g_session[peer][router]['ingress']] route_maps_out = [route_map for route_map in self.network.g_session[router][peer]['egress']] route_maps += route_maps_in route_maps += route_maps_out internal_peers.append({ 'id': self.network.lo_ip(peer).ip, 'route_maps_in': [r.name for r in route_maps_in], 'route_maps_out': [r.name for r in route_maps_out], }) bgp_groups['internal_peers'] = { 'type': 'internal', 'neighbors': internal_peers } ibgp_neighbor_list = [] ibgp_rr_client_list = [] if router in ibgp_graph: for src, neigh, data in ibgp_graph.edges(router, data=True): route_maps_in = [route_map for route_map in self.network.g_session[neigh][router]['ingress']] route_maps_out = [route_map for route_map in self.network.g_session[router][neigh]['egress']] route_maps += route_maps_in route_maps += route_maps_out description = data.get("rr_dir") + " to " + ank.fqdn(self.network, neigh) if data.get('rr_dir') == 'down': ibgp_rr_client_list.append( { 'id': self.network.lo_ip(neigh).ip, 'description': description, 'route_maps_in': [r.name for r in route_maps_in], 'route_maps_out': [r.name for r in route_maps_out], }) elif (data.get('rr_dir') in set(['up', 'over', 'peer']) or data.get('rr_dir') is None): ibgp_neighbor_list.append( { 'id': self.network.lo_ip(neigh).ip, 'description': description, 'route_maps_in': [r.name for r in route_maps_in], 'route_maps_out': [r.name for r in route_maps_out], }) bgp_groups['internal_peers'] = { 'type': 'internal', 'neighbors': ibgp_neighbor_list } if len(ibgp_rr_client_list): bgp_groups['internal_rr'] = { 'type': 'internal', 'neighbors': ibgp_rr_client_list, 'cluster': self.network.lo_ip(router).ip, } if router in ebgp_graph: external_peers = [] for peer in ebgp_graph.neighbors(router): if not peer.is_router: #no eBGP peering to non-routers continue route_maps_in = [route_map for route_map in self.network.g_session[peer][router]['ingress']] route_maps_out = [route_map for route_map in self.network.g_session[router][peer]['egress']] route_maps += route_maps_in route_maps += route_maps_out peer_ip = physical_graph[peer][router]['ip'] external_peers.append({ 'id': peer_ip, 'route_maps_in': [r.name for r in route_maps_in], 'route_maps_out': [r.name for r in route_maps_out], 'peer_as': self.network.asn(peer)}) bgp_groups['external_peers'] = { 'type': 'external', 'neighbors': external_peers} # Ensure only one copy of each route map, can't use set due to list inside tuples (which won't hash) # Use dict indexed by name, and then extract the dict items, dict hashing ensures only one route map per name route_maps = dict( (route_map.name, route_map) for route_map in route_maps).values() community_lists = {} prefix_lists = {} node_bgp_data = self.network.g_session.node.get(router) if node_bgp_data: community_lists = node_bgp_data.get('tags') prefix_lists = node_bgp_data.get('prefixes') policy_options = { 'community_lists': community_lists, 'prefix_lists': prefix_lists, 'route_maps': route_maps, } return (bgp_groups, policy_options)
def collect_data(self, commands): shell = self.server.get_shell() shell.setecho(False) collected_data_dir = config.collected_data_dir netkit_data_dir = os.path.join(collected_data_dir, "netkit") if not os.path.isdir(netkit_data_dir): os.mkdir(netkit_data_dir) host_data_dir = os.path.join(netkit_data_dir, self.host_alias) if not os.path.isdir(host_data_dir): os.mkdir(host_data_dir) collect_timestamp_dir = os.path.join(host_data_dir, time.strftime("%Y%m%d_%H%M%S", time.localtime())) if not os.path.isdir(collect_timestamp_dir): os.mkdir(collect_timestamp_dir) servers = set(self.network.servers()) #TODO: need to have way to allow privexec.... or just disable enable password? #TODO: Put term len 0 into configs for node in self.network.devices(): routername = ank.fqdn(self.network, node) full_routername = ank.rtr_folder_name(self.network, node) user_exec_prompt = "%s>" % node.dns_hostname priv_exec_prompt = "%s#" % node.dns_hostname for port_command in commands: LOG.info("%s: running %s" % (routername, port_command)) telnet_port, command = port_command.split(":") if telnet_port == 'ssh': self.server.connect_vm(node.tap_ip, shell) shell.sendline(command) shell.expect(self.server.NETKIT_PROMPT) command_output = shell.before self.server.disconnect_vm(shell) shell.prompt() # need to ssh into this machine else: if node in servers: # don't try telnet into as zebra not running continue # use telnet shell.sendline("telnet %s %s" % (node.tap_ip, telnet_port)) shell.expect("Password:"******"1234") shell.expect(user_exec_prompt) shell.sendline("en") i = shell.expect(["Password:"******"1234") else: # all good, in priv exec pass # just to be sure set_term_length = "term len 0" shell.sendline(set_term_length) shell.expect(priv_exec_prompt) shell.sendline(command) shell.expect(priv_exec_prompt) # Can be an issue with the telnet x zebra command (not for bgpd it seems) command_output = shell.before # If no command output, captured the previous command, try again if command_output.strip() == set_term_length: shell.expect(priv_exec_prompt) command_output = shell.before shell.sendline("exit") shell.prompt() # from http://stackoverflow.com/q/295135/ command_filename_format = (re.sub('[^\w\s-]', '', command).strip().lower()) filename = "%s_%s_%s.txt" % (full_routername, command_filename_format, time.strftime("%Y%m%d_%H%M%S", time.localtime())) filename = os.path.join(collect_timestamp_dir, filename) with open( filename, 'w') as f_out: f_out.write(command_output)
def configure_junosphere(self): """Configure Junosphere topology structure""" LOG.debug("Configuring Junosphere") vmm_template = lookup.get_template("junos/topology_vmm.mako") topology_data = {} # Generator for private0, private1, etc collision_to_bridge_mapping = {} private_bridges = [] junosphere_predefined_bridge_count = 124 # have to explicitly create bridges past 124 image_tuple = namedtuple('image', "alias, basedisk") if self.junosphere_olive: image = image_tuple("MY_DISK", config.settings['Junosphere']['basedisk']) else: image = image_tuple("VJX1000_LATEST", None) bridge_id_generator = (i for i in itertools.count(0)) def next_bridge_id(): bridge_id = bridge_id_generator.next() retval = "private%s" % bridge_id if bridge_id > junosphere_predefined_bridge_count: private_bridges.append(retval) return retval for device in sorted(self.network.devices(), key = lambda x: x.fqdn): hostname = device.hostname topology_data[hostname] = { 'image': image.alias, 'config': router_conf_file(self.network, device), 'interfaces': [], } for src, dst, data in sorted(self.network.graph.edges(device, data=True), key = lambda (s,t,d): t.fqdn): subnet = data['sn'] description = 'Interface %s -> %s' % ( ank.fqdn(self.network, src), ank.fqdn(self.network, dst)) # Bridge information for topology config if subnet in collision_to_bridge_mapping: # Use bridge allocated for this subnet bridge_id = collision_to_bridge_mapping[subnet] else: # Allocate a bridge for this subnet bridge_id = next_bridge_id() collision_to_bridge_mapping[subnet] = bridge_id if not self.junosphere_olive: description += "(%s)" % self.int_id(data['id']) topology_data[hostname]['interfaces'].append({ 'description': description, 'id': self.int_id_em(data['id']), 'id_ge': self.int_id(data['id']), 'bridge_id': bridge_id, }) if self.junosphere_olive: # em2 is dead on Olive Junosphere platform topology_data[hostname]['interfaces'].append({ 'description': "dead interface", 'id': "em2", 'bridge_id': "dead", }) vmm_file = os.path.join(lab_dir(), "topology.vmm") with open( vmm_file, 'wb') as f_vmm: f_vmm.write( vmm_template.render( topology_data = topology_data, private_bridges = private_bridges, image = image, olive_based = self.junosphere_olive, ))
def configure(self): """Configure C-BGP""" LOG.info("Configuring C-BGP") self.initialise() default_weight = 1 template = lookup.get_template("cbgp/cbgp.mako") physical_graph = self.network.graph igp_graph = ank.igp_graph(self.network) ibgp_graph = ank.get_ibgp_graph(self.network) ebgp_graph = ank.get_ebgp_graph(self.network) as_graphs = ank.get_as_graphs(self.network) ip_as_allocs = ank.get_ip_as_allocs(self.network) # Allocs for ebgp announcements physical_topology = defaultdict(dict) ibgp_topology = {} igp_topology = {} ebgp_topology = {} ebgp_prefixes = {} bgp_routers = {} # Fast lookup of loopbacks - the unique router ID for cBGP loopback = dict( (n, self.network.lo_ip(n).ip) for n in physical_graph) # Physical topology for as_graph in as_graphs: asn = as_graph.name physical_topology[asn]['nodes'] = [loopback[n] for n in as_graph] physical_topology[asn]['links'] = [ (loopback[s], loopback[t]) for (s,t) in unidirectional(as_graph.edges())] # Interdomain links interdomain_links = [ (loopback[s], loopback[t]) for (s,t) in unidirectional(ebgp_graph.edges())] #IGP configuration for as_graph in as_graphs: asn = as_graph.name igp_topology[asn] = {} # Subgraph of IGP graph for this AS as_igp_graph = igp_graph.subgraph(as_graph.nodes()) igp_topology[asn]['nodes'] = [loopback[n] for n in as_igp_graph] igp_topology[asn]['links'] = [ (loopback[s], loopback[t], data.get('weight', default_weight)) for (s,t,data) in (as_graph.edges(data=True))] # iBGP configuration #TODO: if ibgp graph is a clique then use "bgp domain 1 full-mesh" where 1 is asn # use nx.graph_clique_number(G) and compare to size of G, if same then is a clique # otherwise create ibgp session by session #TODO: add support for non full-mesh (need to find documentation on this) for as_graph in as_graphs: asn = as_graph.name for router in as_graph: if not router.is_router: continue if router not in ibgp_graph: # likely single node AS continue ibgp_topology[router] = [] for peer in ibgp_graph.neighbors(router): ibgp_topology[router].append(peer) bgp_routers[asn] = [n.lo_ip.ip for n in ank.bgp_routers(self.network) if n.asn == asn] # eBGP configuration for node in ebgp_graph.nodes(): node_id = loopback[node] peers = [] for peer in ebgp_graph.neighbors(node): peers.append( (self.network.asn(peer), loopback[peer])) ebgp_topology[node_id] = peers # Prefixes to originate adv_subnet = ip_as_allocs[self.network.asn(node)] ebgp_prefixes[node_id] = adv_subnet #TODO: see if can just do for node in ebgp_graph ie without the .nodes() on end # bgp policy bgp_policy = {} for router in self.network.routers(): for peer in self.network.g_session.neighbors(router): pol_egress = self.network.g_session[router][peer]['egress'] pol_ingress = self.network.g_session[peer][router]['ingress'] if len(pol_ingress) or len(pol_egress): try: bgp_policy[router][peer] = { 'ingress': pol_ingress, 'egress': pol_egress, } except KeyError: bgp_policy[router] = {} bgp_policy[router][peer] = { 'ingress': pol_ingress, 'egress': pol_egress, } # tags dict for mapping from tag to community value, and for prefixes tags = self.network.g_session.graph['tags'] prefixes = self.network.g_session.graph['prefixes'] with open( cbgp_file(), 'w') as f_cbgp: f_cbgp.write( template.render( physical_topology = physical_topology, interdomain_links = interdomain_links, igp_topology = igp_topology, ibgp_topology = ibgp_topology, ebgp_topology = ebgp_topology, ebgp_prefixes = ebgp_prefixes, bgp_routers = bgp_routers, bgp_policy = bgp_policy, tags = tags, prefixes = prefixes, ))
def configure_dns(self): """Generates BIND configuration files for DNS Can check configs eg: Forward:: bash-3.2$ named-checkzone -d AS3 ank_lab/netkit_lab/AS3_l3_3_dns_1/etc/bind/db.AS3 loading "AS3" from "ank_lab/netkit_lab/AS3_l3_3_dns_1/etc/bind/db.AS3" class "IN" zone AS3/IN: loaded serial 2008080101 OK Reverse:: bash-3.2$ named-checkzone -d 0.10.in-addr.arpa ank_lab/netkit_lab/AS3_l3_3_dns_1/etc/bind/db.0.10.in-addr.arpa. loading "0.10.in-addr.arpa" from "ank_lab/netkit_lab/AS3_l3_3_dns_1/etc/bind/db.0.10.in-addr.arpa." class "IN" zone 0.10.in-addr.arpa/IN: loaded serial 2008080101 OK named:: bash-3.2$ named-checkconf ank_lab/netkit_lab/AS3_l3_3_dns_1/etc/bind/named.conf """ import netaddr ip_localhost = netaddr.IPAddress("127.0.0.1") linux_bind_dir = "/etc/bind" resolve_template = lookup.get_template("linux/resolv.mako") forward_template = lookup.get_template("bind/forward.mako") named_template = lookup.get_template("bind/named.mako") reverse_template = lookup.get_template("bind/reverse.mako") root_template = lookup.get_template("bind/root.mako") root_dns_template = lookup.get_template("bind/root_dns.mako") root_dns_named_template = lookup.get_template("bind/root_dns_named.mako") ip_as_allocs = ank.get_ip_as_allocs(self.network) dns_servers = ank.dns_servers(self.network) root_servers = list(ank.root_dns_servers(self.network)) auth_servers = ank.dns.dns_auth_servers(self.network) caching_servers = ank.dns.dns_cache_servers(self.network) clients = ank.dns.dns_clients(self.network) routers = set(self.network.routers()) #TODO: use with for opening files for server in root_servers: children = ank.dns.dns_hiearchy_children(server) child_servers = [] for child in children: advertise_block = ip_as_allocs[child.asn] reverse_identifier = ank.rev_dns_identifier(advertise_block) child_servers.append( (child.domain, reverse_identifier, ank.server_ip(child))) f_root_db = open(os.path.join(bind_dir(self.network, server), "db.root"), 'wb') f_root_db.write( root_dns_template.render( dns_servers = child_servers, server = server, )) f_named = open( os.path.join(bind_dir(self.network, server), "named.conf"), 'wb') f_named.write(root_dns_named_template.render( logging = False, )) for server in caching_servers: #root_db_hint = ( ("ns.AS%s" % n.asn, ank.server_ip(n)) for n in ank.dns_hiearchy_parents(server)) root_db_hint = ( ("ROOT-SERVER", ank.server_ip(n)) for n in root_servers) root_db_hint = list(root_db_hint) #TODO: make caching use parent rather than global root f_root = open( os.path.join(bind_dir(self.network, server), "db.root"), 'wb') f_root.write( root_template.render( root_servers = root_db_hint)) f_named = open( os.path.join(bind_dir(self.network, server), "named.conf"), 'wb') f_named.write(named_template.render( entry_list = [], bind_dir = linux_bind_dir, logging = False, )) f_named.close() for server in auth_servers: named_list = [] advertise_links = list(ank.advertise_links(server)) advertise_hosts = list(ank.dns_auth_children(server)) LOG.debug("DNS server %s advertises %s" % (server, advertise_links)) #TODO: make reverse dns handle domains other than /8 /16 /24 advertise_block = ip_as_allocs[server.asn] # remove trailing fullstop reverse_identifier = ank.rev_dns_identifier(advertise_block).rstrip(".") #TODO: look at using advertise_block.network.reverse_dns - check what Bind needs named_list.append(reverse_identifier) f_named = open( os.path.join(bind_dir(self.network, server), "named.conf"), 'wb') f_named.write(named_template.render( domain = server.domain, entry_list = named_list, bind_dir = linux_bind_dir, logging = False, )) f_named.close() for_entry_list = list( (self.interface_id(link.id), link.local_host.dns_host_portion_only, link.ip) for link in advertise_links) # Add loopbacks for routers for_entry_list += ( (self.lo_interface(0), host.dns_host_portion_only, host.lo_ip.ip) #TODO: make thise check l3 group rather than asn (generalise) for host in advertise_hosts if host.is_router and host.asn == server.asn) rev_entry_list = list( (ank.reverse_subnet(link.ip, advertise_block.prefixlen), self.interface_id(link.id), link.local_host.dns_hostname) for link in advertise_links) # Add loopbacks for routers rev_entry_list += ( (ank.reverse_subnet(host.lo_ip.ip, advertise_block.prefixlen), self.lo_interface(0), host.dns_host_portion_only) #TODO: make thise check l3 group rather than asn (generalise) for host in advertise_hosts if host.is_router and host.asn == server.asn) #TODO: provide better way to get eg eth0.host than string concat inside the template host_cname_list = [] for host in advertise_hosts: if host.asn != server.asn: # host is from another asn, skip. #TODO: extend this to make sure matches same asn, l3group and l2group continue if host.is_router: # has lo_ip cname = "%s.%s" % (self.lo_interface(), host.dns_host_portion_only) else: # choose an interface - arbitrary choice, choose first host link interface = self.interface_id(ank.server_interface_id(host)) cname = "%s.%s" % (interface, host.dns_host_portion_only) host_cname_list.append( (host.dns_host_portion_only, cname)) #Sort to make format nicer host_cname_list = sorted(host_cname_list, key = lambda x: x[1]) for_entry_list = sorted(for_entry_list) for_entry_list = sorted(for_entry_list, key = lambda x: x[1]) f_forward = open ( os.path.join(bind_dir(self.network, server), "db.%s" % server.domain), 'wb') f_forward.write(forward_template.render( domain = server.domain, entry_list = for_entry_list, host_cname_list = host_cname_list, dns_server = server.dns_hostname, dns_server_ip = ank.server_ip(server), )) f_reverse = open(os.path.join(bind_dir(self.network, server), "db.%s" % reverse_identifier), 'wb') f_reverse.write(reverse_template.render( domain = server.domain, identifier = reverse_identifier, entry_list = rev_entry_list, dns_server= server.dns_hostname, )) #TODO: make l2 use l3 for caching #TODO: ROOT-SERVER can't be part of a domain... - need to correctly handle case of multiple root servers # and also need to handle this for case of single root server (ie no hiearchy) probably ok as /etc/resolv.conf points to server itself, not through dns hints root_db_hint = ( ("ROOT-SERVER", ank.server_ip(n)) for n in ank.dns_hiearchy_parents(server)) f_root = open( os.path.join(bind_dir(self.network, server), "db.root"), 'wb') f_root.write( root_template.render( root_servers = root_db_hint)) for server in dns_servers: f_resolv = open( os.path.join(etc_dir(self.network, server), "resolv.conf"), 'wb') f_resolv.write ( resolve_template.render( nameservers = [ank.server_ip(server)], domain = server.domain)) # Configure clients for client in clients: server_ips = (ank.server_ip(server) for server in ank.dns_hiearchy_parents(client)) server_ips = list(server_ips) f_resolv = open( os.path.join(etc_dir(self.network, client), "resolv.conf"), 'wb') f_resolv.write ( resolve_template.render( nameservers = server_ips, domain = client.domain)) return
def compile(self): """Compile into device configuration files. Args: None Returns: None Example usage: >>> inet = ank.internet.Internet() >>> inet.compile() >>> inet = ank.internet.Internet() >>> inet.compile() """ #TODO: fix import order problem with doctests: #No handlers could be found for logger "ANK" LOG.info("Compiling") # Sanity check if self.network.graph.number_of_nodes() == 0: LOG.warn("Cannot compile empty network") return # Clean up old archives ank.tidy_archives() #TODO: #config.get_plugin("Inv Cap").run(self.network) #ank.inv_cap_weights(self.network) #config.get_plugin("Test").run() ank.initialise_bgp(self.network) # Ensure nodes have a type set self.network.update_node_type(default_type="netkit_router") ank.allocate_dns_servers(self.network) # Allocations ank.allocate_subnets(self.network, IPNetwork("10.0.0.0/8")) ank.alloc_interfaces(self.network) ank.alloc_tap_hosts(self.network, self.tapsn) if self.policy_file: LOG.info("Applying BGP policy from %s" % self.policy_file) pol_parser = ank.BgpPolicyParser(self.network) pol_parser.apply_policy_file(self.policy_file) if self.rpki_file: LOG.info("Applying RPKI structure from %s" % self.rpki_file) rpki_parser = ank.RpkiSetsParser(self.network) rpki_parser.apply_rpki_file(self.rpki_file) if self.will_deploy and not self.compile_targets['netkit']: auto_compile = any( data.get("active") for data in config.settings['Netkit Hosts'].values()) if auto_compile: LOG.info("Active Netkit deployment target, automatically compiling") self.compile_targets['netkit'] = True if self.compile_targets['netkit']: nk_comp = ank.NetkitCompiler(self.network, self.services) nk_comp.initialise() nk_comp.configure() auto_compile = any( data.get("active") for data in config.settings['Dynagen Hosts'].values()) if auto_compile: LOG.info("Active Dynagen deployment target, automatically compiling") self.compile_targets['dynagen'] = True if self.compile_targets['dynagen']: dynagen_comp = ank.dynagenCompiler(self.network, services = self.services, igp = self.igp, image = config.settings['Dynagen']['image'], hypervisor_server = config.settings['Dynagen']['Hypervisor']['server'], hypervisor_port = config.settings['Dynagen']['Hypervisor']['port'], ) dynagen_comp.initialise() dynagen_comp.configure() if self.compile_targets['junosphere']: junos_comp = ank.JunosCompiler(self.network, self.services, self.igp, target="junosphere") junos_comp.initialise() junos_comp.configure() if self.compile_targets['junosphere_olive']: LOG.warn("Junosphere Olive not currently supported") #junos_comp = ank.JunosCompiler(self.network, self.services, self.igp, target="junosphere_olive") #junos_comp.initialise() #junos_comp.configure() if self.will_deploy and not self.compile_targets['olive']: auto_compile = any( data.get("active") for data in config.settings['Olive Hosts'].values()) if auto_compile: self.compile_targets['olive'] = True LOG.info("Active Olive deployment target, automatically compiling") if self.compile_targets['olive']: olive_qemu_patched = self.compile_targets['olive_qemu_patched'] junos_comp = ank.JunosCompiler(self.network, self.services, self.igp, target="olive", olive_qemu_patched = olive_qemu_patched) junos_comp.initialise() junos_comp.configure() if self.will_deploy and not self.compile_targets['cbgp']: auto_compile = any( data.get("active") for data in config.settings['cBGP Hosts'].values()) if auto_compile: self.compile_targets['cbgp'] = True LOG.info("Active cBGP deployment target, automatically compiling") if self.compile_targets['cbgp']: cbgp_comp = ank.CbgpCompiler(self.network, self.services) cbgp_comp.configure()
def configure_netkit(self): """Generates Netkit and Zebra/Quagga specific configuration files.""" # Sets up netkit related files tap_host = ank.get_tap_host(self.network) ank_version = pkg_resources.get_distribution("AutoNetkit").version date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) lab_template = lookup.get_template("netkit/lab.mako") startup_template = lookup.get_template("netkit/startup.mako") zebra_daemons_template = lookup.get_template( "quagga/zebra_daemons.mako") zebra_template = lookup.get_template("quagga/zebra.mako") sshd_template = lookup.get_template("linux/sshd.mako") motd_template = lookup.get_template("quagga/motd.mako") # Shared (common) configuration startup_daemon_list = [] #Setup ssh shutil.copy(resource_filename("AutoNetkit","lib/shadow"), shared_etc_dir()) startup_daemon_list.append("ssh") # Need to chown root dir for ssh keys # refer http://list.dia.uniroma3.it/pipermail/netkit.users/2010-February/000552.html use_ssh_key = False if config.settings['Netkit']['ssh key']: #chown root:root /root use_ssh_key = True f_startup = open( os.path.join(lab_dir(), "shared.startup"), 'wb') f_startup.write(startup_template.render( interfaces=[], add_localhost=True, #don't send out the tap interface del_default_route=True, daemons=startup_daemon_list, use_ssh_key = use_ssh_key, )) f_startup.close() # Files for indvidual node configuration #TODO: this needs to be created for each netkit host machine f_lab = open(os.path.join(lab_dir(), "lab.conf"), 'wb') lab_conf = {} tap_list_strings = {} ibgp_routers = ank.ibgp_routers(self.network) ebgp_routers = ank.ebgp_routers(self.network) igp_graph = ank.igp_graph(self.network) dns_servers = set(self.network.dns_servers()) routers = set(self.network.routers()) for node in self.network.devices(): #TODO: see if rtr label is still needed, if so replace with # appropriate naming module function rtr_folder_name = ank.rtr_folder_name(self.network, node) # sshd options f_sshd = open( os.path.join(sshd_dir(self.network, node), "sshd_config"), 'wb') f_sshd.write(sshd_template.render()) f_sshd.close() lab_conf[rtr_folder_name] = [] startup_daemon_list = ["zebra"] startup_int_list = [] # convert tap list from ips into strings # tap_int_id cannot conflict with already allocated interfaces # assume edges number sequentially, so next free int id is number of # edges node_tap_id = self.tap_interface_id(self.network, node) tap_list_strings[rtr_folder_name] = (node_tap_id, self.network[node].get('tap_ip')) if node in dns_servers: startup_daemon_list.append("bind") dns_memory = 64 # Allocate more memory to DNS server #TODO: remove key, val and make it just key: val lab_conf[rtr_folder_name].append( ('mem', dns_memory)) if config.settings['Netkit']['ssh key']: f_auth_keys = open(os.path.join(dot_ssh_dir(self.network, node), "authorized_keys"), "wb") f_auth_keys.write(config.settings['Netkit']['ssh key']) f_auth_keys.close() # Zebra Daemons zebra_daemon_list = [] f_zdaemons = open( os.path.join(zebra_dir(self.network, node), "daemons"), 'wb') # Always start Zebra zebra_daemon_list.append("zebra") if igp_graph.degree(node) > 0: zebra_daemon_list.append("ospfd") # Only start IGP process if IGP links if (node in ibgp_routers) or (node in ebgp_routers): zebra_daemon_list.append("bgpd") f_zdaemons.write(zebra_daemons_template.render( entryList = zebra_daemon_list, )) f_zdaemons.close() # MOTD f_zmotd = open( os.path.join(zebra_dir(self.network, node), "motd.txt"), 'wb') f_zmotd.write(motd_template.render( date = date, version = ank_version, password = self.zebra_password, )) # Main Zebra config f_z = open( os.path.join(zebra_dir(self.network, node), "zebra.conf"), 'wb') f_z.write( zebra_template.render( hostname = node.device_hostname, password = self.zebra_password, enable_password = self.zebra_password, use_snmp = True, use_debug = True, )) f_z.close() # Loopback interface lo_ip = self.network.lo_ip(node) startup_int_list.append({ 'int': 'lo:1', 'ip': str(lo_ip.ip), 'netmask': str(lo_ip.netmask), }) # Ethernet interfaces for link in self.network.links(node): int_id = self.interface_id(link.id) subnet = link.subnet # replace the / from subnet label collision_domain = "%s.%s" % (subnet.ip, subnet.prefixlen) # lab.conf has id in form host[0]=... for eth0 of host lab_conf[rtr_folder_name].append((link.id, collision_domain)) startup_int_list.append({ 'int': int_id, 'ip': str(link.ip), 'netmask': str(subnet.netmask), 'broadcast': str(subnet.broadcast), }) default_route = None if node.is_server: default_route = ank.default_route(node) # add default_route for server to router chown_bind = False if node in ank.dns_servers(self.network): chown_bind = True #Write startup file for this router f_startup = open( os.path.join(netkit_dir(self.network, node), "{0}.startup".format(rtr_folder_name)), 'wb') f_startup.write(startup_template.render( interfaces=startup_int_list, add_localhost=True, #don't send out the tap interface del_default_route=True, default_route = default_route, daemons=startup_daemon_list, chown_bind = chown_bind, )) f_startup.close() # Write lab file for whole lab f_lab.write(lab_template.render( conf = lab_conf, tapHost = tap_host, tapList = tap_list_strings, lab_description = "AutoNetkit generated lab", lab_version = date, #TODO: get this from config file lab_email = "*****@*****.**", lab_author = "AutoNetkit %s" % ank_version, #TODO: get this from config file lab_web = "www.autonetkit.org", ))
def plot(self, matplotlib=False): """Plot the network topology Args: None Returns: None Example usage: >>> inet = ank.internet.Internet() >>> inet.plot() """ LOG.info("Plotting") matplotlib = matplotlib or config.settings['Plotting']['matplotlib'] if matplotlib: ank.plot(self.network) ank.jsplot(self.network) ank.summarydoc(self.network) ank.dump_graph(self.network.graph, os.path.join(config.log_dir, "physical")) physical_single_edge = nx.Graph(self.network.graph) ank.dump_graph(physical_single_edge, os.path.join(config.log_dir, "physical_single_edge")) ibgp_graph = ank.get_ibgp_graph(self.network) ebgp_graph = ank.get_ebgp_graph(self.network) ank.dump_graph(ibgp_graph, os.path.join(config.log_dir, "ibgp")) ank.dump_graph(ebgp_graph, os.path.join(config.log_dir, "ebgp")) g_dns = nx.Graph(self.network.g_dns) ank.dump_graph(g_dns, os.path.join(config.log_dir, "dns")) ank.dump_graph(self.network.g_dns_auth, os.path.join(config.log_dir, "dns_auth")) ank.dump_identifiers(self.network, os.path.join(config.log_dir, "identifiers.txt"))
def allocate_subnets(network, address_block=IPNetwork("10.0.0.0/8")): """Allocates subnets and IP addresses to links in the network. Args: address_block (IPNetwork): The address block to use. Returns: ip_as_allocs Example usage: >>> network = ank.example_multi_as() >>> allocate_subnets(network) >>> print ank.debug_nodes(network.graph, "lo_ip") {'1a.AS1': IPNetwork('10.0.0.32/32'), '1b.AS1': IPNetwork('10.0.0.33/32'), '1c.AS1': IPNetwork('10.0.0.34/32'), '2a.AS2': IPNetwork('10.1.0.64/32'), '2b.AS2': IPNetwork('10.1.0.65/32'), '2c.AS2': IPNetwork('10.1.0.66/32'), '2d.AS2': IPNetwork('10.1.0.67/32'), '3a.AS3': IPNetwork('10.2.0.0/32')} >>> print ank.debug_edges(network.graph, "ip") {('1a.AS1', '1b.AS1'): IPAddress('10.0.0.10'), ('1a.AS1', '1c.AS1'): IPAddress('10.0.0.22'), ('1b.AS1', '1a.AS1'): IPAddress('10.0.0.9'), ('1b.AS1', '1c.AS1'): IPAddress('10.0.0.26'), ('1b.AS1', '3a.AS3'): IPAddress('10.0.0.17'), ('1c.AS1', '1a.AS1'): IPAddress('10.0.0.21'), ('1c.AS1', '1b.AS1'): IPAddress('10.0.0.25'), ('1c.AS1', '2a.AS2'): IPAddress('10.0.0.29'), ('2a.AS2', '1c.AS1'): IPAddress('10.0.0.30'), ('2a.AS2', '2b.AS2'): IPAddress('10.1.0.10'), ('2a.AS2', '2d.AS2'): IPAddress('10.1.0.26'), ('2b.AS2', '2a.AS2'): IPAddress('10.1.0.9'), ('2b.AS2', '2c.AS2'): IPAddress('10.1.0.18'), ('2c.AS2', '2b.AS2'): IPAddress('10.1.0.17'), ('2c.AS2', '2d.AS2'): IPAddress('10.1.0.30'), ('2d.AS2', '2a.AS2'): IPAddress('10.1.0.25'), ('2d.AS2', '2c.AS2'): IPAddress('10.1.0.29'), ('2d.AS2', '3a.AS3'): IPAddress('10.1.0.33'), ('3a.AS3', '1b.AS1'): IPAddress('10.0.0.18'), ('3a.AS3', '2d.AS2'): IPAddress('10.1.0.34')} >>> print ank.debug_edges(network.graph, "sn") {('1a.AS1', '1b.AS1'): IPNetwork('10.0.0.8/30'), ('1a.AS1', '1c.AS1'): IPNetwork('10.0.0.20/30'), ('1b.AS1', '1a.AS1'): IPNetwork('10.0.0.8/30'), ('1b.AS1', '1c.AS1'): IPNetwork('10.0.0.24/30'), ('1b.AS1', '3a.AS3'): IPNetwork('10.0.0.16/30'), ('1c.AS1', '1a.AS1'): IPNetwork('10.0.0.20/30'), ('1c.AS1', '1b.AS1'): IPNetwork('10.0.0.24/30'), ('1c.AS1', '2a.AS2'): IPNetwork('10.0.0.28/30'), ('2a.AS2', '1c.AS1'): IPNetwork('10.0.0.28/30'), ('2a.AS2', '2b.AS2'): IPNetwork('10.1.0.8/30'), ('2a.AS2', '2d.AS2'): IPNetwork('10.1.0.24/30'), ('2b.AS2', '2a.AS2'): IPNetwork('10.1.0.8/30'), ('2b.AS2', '2c.AS2'): IPNetwork('10.1.0.16/30'), ('2c.AS2', '2b.AS2'): IPNetwork('10.1.0.16/30'), ('2c.AS2', '2d.AS2'): IPNetwork('10.1.0.28/30'), ('2d.AS2', '2a.AS2'): IPNetwork('10.1.0.24/30'), ('2d.AS2', '2c.AS2'): IPNetwork('10.1.0.28/30'), ('2d.AS2', '3a.AS3'): IPNetwork('10.1.0.32/30'), ('3a.AS3', '1b.AS1'): IPNetwork('10.0.0.16/30'), ('3a.AS3', '2d.AS2'): IPNetwork('10.1.0.32/30')} """ LOG.debug("Allocating subnets") # Initialise IP list to be graph edge format ip_as_allocs = {} # allocates subnets to the edges and loopback in network graph # Put into dictionary, indexed by ASN (the name attribute of each as graph) # for easy appending of eBGP links asgraphs = dict((my_as.asn, my_as) for my_as in ank.get_as_graphs(network)) # Simple method: break address_block into a /16 for each network #TODO: check this is feasible - ie against required host count subnet_list = address_block.subnet(16) ebgp_edges = ank.ebgp_edges(network) visited_ebgp_edges = set() for src, dst in sorted(ebgp_edges): # Add the dst (external peer) to AS of src node so they are allocated # a subnet. (The AS choice is arbitrary) if (dst, src) in visited_ebgp_edges: continue src_as = asgraphs[src.asn] src_as.add_edge(src, dst) # record for DNS purposes ank.dns_advertise_link(src, dst) visited_ebgp_edges.add( (src, dst)) for my_as in sorted(asgraphs.values(), key = lambda x: x.asn): asn = my_as.asn as_subnet = subnet_list.next() as_internal_nodes = [n for n in sorted(my_as.nodes()) if network.asn(n) == asn] host_count = my_as.number_of_nodes() # record this subnet ip_as_allocs[my_as.asn] = as_subnet # split into subnets for loopback and ptp ptp_count = my_as.number_of_edges() # Now subnet network into subnets of the larger of these two # TODO tidy up this comment # Note ptp subnets required a /30 ie 4 ips req_sn_count = max(host_count, 4*ptp_count) if req_sn_count == 0: # Nothing to allocate for this AS continue req_pref_len = int(32 - math.ceil(math.log(req_sn_count, 2)) ) # Subnet as subnet into subnets of this size sn_iter = as_subnet.subnet(req_pref_len) # And allocate a subnet for each ptp and loopback if ptp_count > 0: # Don't allocate a ptp subnet if there are no ptp links ptp_subnet = sn_iter.next() loopback_subnet = sn_iter.next() if ptp_count > 0: link_subnet = ptp_subnet.subnet(30) # Now iterate over edges in this as and apply for src, dst in sorted(my_as.edges()): # Note we apply this back to the main graph # not to the as graph! subnet = link_subnet.next() #TODO: fix the technique for accessing edges # as it breaks with multigraphs, as it creates a new edge if network.asn(dst) != asn: # eBGP link where dst has IP allocated from subnet of this AS network.graph[dst][src]['remote_as_sn_block'] = True network.graph[src][dst]['sn'] = subnet network.graph[dst][src]['sn'] = subnet # allocate an ip to each end network.graph[src][dst]['ip'] = subnet[1] network.graph[dst][src]['ip'] = subnet[2] # Allocate an loopback interface to each router #TODO: check if next step is necessary loopback_ips = loopback_subnet.subnet(32) for rtr in sorted(as_internal_nodes): lo_ip = loopback_ips.next() network.graph.node[rtr]['lo_ip'] = lo_ip network.ip_as_allocs = ip_as_allocs
def alloc_tap_hosts(network, address_block=IPNetwork("172.16.0.0/16")): """Allocates TAP IPs for connecting using Netkit >>> network = ank.example_multi_as() >>> alloc_tap_hosts(network) >>> print ank.debug_nodes(network.graph, "tap_ip") {'1a.AS1': IPAddress('172.16.1.1'), '1b.AS1': IPAddress('172.16.1.2'), '1c.AS1': IPAddress('172.16.1.3'), '2a.AS2': IPAddress('172.16.2.1'), '2b.AS2': IPAddress('172.16.2.2'), '2c.AS2': IPAddress('172.16.2.3'), '2d.AS2': IPAddress('172.16.2.4'), '3a.AS3': IPAddress('172.16.3.1')} """ LOG.debug("Allocating TAP hosts") network.tap_sn = address_block as_graph = ank.get_as_graphs(network) # Try allocating /24 to each subnet as cleaner # then check if this is feasible prefix_len = 24 # Check this will fit into provided network #TODO: check what these 2 lines of code are doing if prefix_len <= address_block.prefixlen: # Try with smaller prefix len prefix_len += 1 # Number of bits required for AS subnet (network bits) # need to add one on for tap host subnet, eg 172.16.0.0 and 172.16.0.1 hosts req_network_bits = int( math.ceil(math.log(len(as_graph) + 1, 2)) ) upper_bound = prefix_len # Find the subnet with the most hosts in it max_req_hosts = max(len(my_as) for my_as in as_graph) req_host_bits = int(math.ceil(math.log(max_req_hosts, 2))) #TODO: there is an off by one error here mking the tap subnets /9 rather than /8 # so end up with 172.16.64.1 and 172.16.128.1 not .1 .2 etc # Check subnetting is feasible lower_bound = address_block.prefixlen + req_network_bits upper_bound = lower_bound + req_host_bits if upper_bound > 32: #TODO: throw error print "Unfeasible tap subnet allocation" return else: prefix_len = lower_bound # Neatness: use a Class C, B, A (in order of preference) if feasible for x in [24, 16, 8]: if lower_bound < x < upper_bound: prefix_len = x elif lower_bound < upper_bound < x: # eg both fit inside a class A, B or C prefix_len = x def set_tap_ips(network, nodes, host_ips): # Allocate in order of node name for node in sorted(nodes, key=network.label): network.graph.node[node]['tap_ip'] = host_ips.next() return # assign /required subnet size to each as then append pops to list if len(as_graph) == 1: # Single AS, don't need to subnet the address block host_ips = address_block.iter_hosts() network.tap_host = host_ips.next() _ = host_ips.next() # IP of tap VM my_as = as_graph.pop() # Allocate directly from address block set_tap_ips(network, my_as.nodes(), host_ips) else: sn_iter = address_block.subnet(prefix_len) tap_host_subnet = sn_iter.next() #TODO: make consistent with previous section [1] vs .next() network.tap_host = tap_host_subnet[1] for my_as in as_graph: LOG.debug("Setting tap IPs for %s" % my_as.asn) host_ips = sn_iter.next().iter_hosts() set_tap_ips(network, my_as.nodes(), host_ips) #TODO: make this a generic function which allocates items from an # generator to each node in the specified network # rather than IP addresses specifically return
def configure_igp(self): """Generates IGP specific configuration files (eg ospfd)""" LOG.debug("Configuring IGP") template = lookup.get_template("quagga/ospf.mako") default_weight = 1 # configures IGP for each AS as_graphs = ank.get_as_graphs(self.network) for my_as in as_graphs: asn = my_as.asn LOG.debug("Configuring IGP for AS %s " % asn) if my_as.number_of_edges() == 0: # No edges, nothing to configure LOG.debug("Skipping IGP for AS%s as no internal links" % asn) continue for router in self.network.routers(asn): #TODO: can probably through most of these straight into the template and use properties there! interface_list = [] network_list = [] # Add loopback info lo_ip = router.lo_ip interface_list.append ( {'id': "lo", 'weight': 1, 'remote_router': "NA (loopback)", 'remote_int': "Loopback"}) network_list.append ( { 'cidr': lo_ip.cidr, 'ip': lo_ip.ip, 'netmask': lo_ip.netmask, 'area': 0, 'remote_ip': "Loopback" }) for link in self.network.links(router, my_as): int_id = self.interface_id(link.id) weight = link.weight or default_weight interface_list.append ({ 'id': int_id, 'weight': weight, 'remote_router': link.remote_host, } ) # fetch and format the ip details subnet = link.subnet local_ip = link.local_ip remote_ip = link.remote_ip network_list.append ( { 'cidr': subnet.cidr, 'ip': local_ip, 'netmask': subnet.netmask, 'remote_ip': remote_ip, 'area': 0, } ) #TODO: see if need to use router-id for ospfd in quagga f_handle = open( os.path.join(zebra_dir(self.network, router), "ospfd.conf"), 'wb') f_handle.write(template.render ( hostname = router.device_hostname, password = self.zebra_password, enable_password = self.zebra_password, interface_list = interface_list, network_list = network_list, routerID = router, use_igp = True, logfile = "/var/log/zebra/ospfd.log", use_debug = False, ))
def router_dir(network, rtr): """Returns path for router rtr""" foldername = ank.rtr_folder_name(network, rtr) return os.path.join(netkit_dir(network, rtr), foldername)
def configure_bgp(self): """Generates BGP specific configuration files""" ip_as_allocs = ank.get_ip_as_allocs(self.network) LOG.debug("Configuring BGP") template = lookup.get_template("quagga/bgp.mako") route_maps = {} ibgp_graph = ank.get_ibgp_graph(self.network) ebgp_graph = ank.get_ebgp_graph(self.network) physical_graph = self.network.graph for my_as in ank.get_as_graphs(self.network): asn = my_as.asn LOG.debug("Configuring IGP for AS %s " % asn) # get nodes ie intersection #H = nx.intersection(my_as, ibgp_graph) # get ibgp graph that contains only nodes from this AS for router in self.network.routers(asn): bgp_groups = {} route_maps = [] ibgp_neighbor_list = [] ibgp_rr_client_list = [] route_map_groups = {} if router in ibgp_graph: for src, neigh, data in ibgp_graph.edges(router, data=True): route_maps_in = self.network.g_session[neigh][router]['ingress'] rm_group_name_in = None if len(route_maps_in): rm_group_name_in = "rm_%s_in" % neigh.folder_name route_map_groups[rm_group_name_in] = [match_tuple for route_map in route_maps_in for match_tuple in route_map.match_tuples] route_maps_out = self.network.g_session[router][neigh]['egress'] rm_group_name_out = None if len(route_maps_out): rm_group_name_in = "rm_%s_out" % neigh.folder_name route_map_groups[rm_group_name_out] = [match_tuple for route_map in route_maps_out for match_tuple in route_map.match_tuples] description = data.get("rr_dir") + " to " + ank.fqdn(self.network, neigh) if data.get('rr_dir') == 'down': ibgp_rr_client_list.append( { 'id': self.network.lo_ip(neigh).ip, 'description': description, 'route_maps_in': rm_group_name_in, 'route_maps_out': rm_group_name_out, }) elif (data.get('rr_dir') in set(['up', 'over', 'peer']) or data.get('rr_dir') is None): ibgp_neighbor_list.append( { 'id': self.network.lo_ip(neigh).ip, 'description': description, 'route_maps_in': rm_group_name_in, 'route_maps_out': rm_group_name_out, }) bgp_groups['internal_peers'] = { 'type': 'internal', 'neighbors': ibgp_neighbor_list } if len(ibgp_rr_client_list): bgp_groups['internal_rr'] = { 'type': 'internal', 'neighbors': ibgp_rr_client_list, 'cluster': self.network.lo_ip(router).ip, } if router in ebgp_graph: external_peers = [] for peer in ebgp_graph.neighbors(router): route_maps_in = self.network.g_session[peer][router]['ingress'] rm_group_name_in = None if len(route_maps_in): rm_group_name_in = "rm_%s_in" % peer.folder_name route_map_groups[rm_group_name_in] = [match_tuple for route_map in route_maps_in for match_tuple in route_map.match_tuples] # Now need to update the sequence numbers for the flattened route maps route_maps_out = self.network.g_session[router][peer]['egress'] rm_group_name_out = None if len(route_maps_out): rm_group_name_out = "rm_%s_out" % peer.folder_name route_map_groups[rm_group_name_out] = [match_tuple for route_map in route_maps_out for match_tuple in route_map.match_tuples] peer_ip = physical_graph[peer][router]['ip'] external_peers.append({ 'id': peer_ip, 'route_maps_in': rm_group_name_in, 'route_maps_out': rm_group_name_out, 'peer_as': self.network.asn(peer)}) bgp_groups['external_peers'] = { 'type': 'external', 'neighbors': external_peers} # Ensure only one copy of each route map, can't use set due to list inside tuples (which won't hash) # Use dict indexed by name, and then extract the dict items, dict hashing ensures only one route map per name community_lists = {} prefix_lists = {} node_bgp_data = self.network.g_session.node.get(router) if node_bgp_data: community_lists = node_bgp_data.get('tags') prefix_lists = node_bgp_data.get('prefixes') policy_options = { 'community_lists': community_lists, 'prefix_lists': prefix_lists, 'route_maps': route_map_groups, } f_handle = open(os.path.join(zebra_dir(self.network, router), "bgpd.conf"),'wb') #TODO: remove community_lists and prefix_lists as they are put into policy_options f_handle.write(template.render( hostname = router.device_hostname, asn = self.network.asn(router), password = self.zebra_password, enable_password = self.zebra_password, router_id = self.network.lo_ip(router).ip, community_lists = community_lists, policy_options = policy_options, prefix_lists = prefix_lists, #TODO: see how this differs to router_id identifying_loopback = self.network.lo_ip(router), bgp_groups = bgp_groups, ibgp_neighbor_list = ibgp_neighbor_list, ibgp_rr_client_list = ibgp_rr_client_list, route_maps = route_maps, logfile = "/var/log/zebra/bgpd.log", debug=True, use_debug=True, dump=False, snmp=False, interfaces = self.configure_interfaces(router) ))