def build_birt(switch=None, domain=None): """ Build bit index routing table :param switch: switch for which the birt should be calculated :param domain: domain for which the birt should be build :return: birt in format {bfr-id: [dest prefix, nbr prefix] """ # {bfr-id: [dest prefix, nbr prefix] birt = {} for destination in TopologyManager.get_topology(domain).get_nodes(): if destination != switch: destination_device = TopologyManager.get_device(destination) if destination_device.get_type( ) == "Host": # dont calculate entry for host continue try: next_hop = TopologyManager.get_next_hop( switch, destination, domain) birt[destination_device.get_bfr_id(domain)] = [ destination_device.get_ip(), next_hop.get_ip() ] except NextHopNotFound: # maybe a path towards the destination is not yet learned continue return birt
def update_mac_entry(self): """ Add mac rewriting rule for switch->dst_dev via port Args: switch (str): switch where rule will be installed """ valid_entries = [] device = TopologyManager.get_device(Configuration.get('name')) for device_name in device.get_device_to_port_mapping(): dev = TopologyManager.get_device(device_name) port = device.get_device_to_port(device_name) entry = TableEntry( match_fields={"eg_intr_md.egress_port": int(port)}, action_name="egress.mac_c.set_mac", action_params={ "srcAddr": device.get_mac(), "dstAddr": dev.get_mac() }) TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="egress.mac_c.adjust_mac", table_entry=entry) valid_entries.append(entry.match_fields) # remove possible old entries self.table_manager.remove_invalid_entries( table_name="egress.mac_c.adjust_mac", valid_entries=valid_entries)
def update_frr_link_protection(self, switch=None, domain=None, port=None, to_id=None, fbm=None, next_hop=None): """ In BIER-FRR link protection, just use an IP tunnel to the NH behind the failed link """ switch_device = TopologyManager.get_device(name=switch) src_ip = switch_device.get_ip() dst_ip = TopologyManager.get_device(name=next_hop).get_ip() remainingBits = BierComputation.id_to_bitstring(to_id) entry = TableEntry(switch=switch, match_fields={ "meta.bier_md.remainingBits": (remainingBits, remainingBits), "meta.ports.status": (0, BierComputation.id_to_bitstring(port)) }, action_name="ingress.bier_c.forward_encap", action_params={ "fbm": fbm, "srcAddr": src_ip, "dstAddr": dst_ip }, priority=1) return entry
def handle_topology_answer(self, *args, **kwargs): """ Handle topology packet :param args: contains the topology packet :return: """ packet = kwargs.get('packet') switch = kwargs.get('switch') pkt = packet pkt = packet.payload Event.trigger("clear_port_down", port=int(pkt.port)) if pkt.device_type != 1: name = "s" + str(pkt.identifier) TopologyManager.add_device(name=name, device=Host(name=name, ip=pkt.ip, mac=pkt.mac)) else: # its a host name = "h" + str(pkt.identifier) TopologyManager.add_device(name=name, device=Host(name=name, ip=pkt.ip, mac=pkt.mac)) if TopologyManager.get_device(name=switch).add_device_to_port(device=name, port=int(pkt.port)): Event.trigger("topology_change", src_device=switch, dst_device=name, port=int(pkt.port)) #Log.info("Pkt in:", pkt.port) topology_packet = proto.connection_pb2.TopologyPacket(ip=pkt.ip, mac=pkt.mac, port=pkt.port, name=name, switch=Configuration.get('name')) Event.trigger("topology_to_controller", pkt=topology_packet)
def handle_topology_answer(self, pkt=None): """ Handle topology packet :param pkt: contains the topology packet :return: """ # if the controller is not yet connected to all local controllers # don't handle topology packets if not Configuration.get('system_done'): return ip = pkt.ip.encode('utf-8') mac = pkt.mac.encode('utf-8') name = pkt.name.encode('utf-8') port = pkt.port switch = pkt.switch.encode('utf-8') if name.startswith('h'): # it's a host TopologyManager.add_device(name=name, device=Host(name=name, ip=ip, mac=mac)) TopologyManager.get_device(name=name).add_device_to_port( device=switch, port=1) Log.event("topology packet with identifier", name, "from switch", switch, "on port", port, "with ip", ip) if TopologyManager.get_device(name=switch).add_device_to_port( device=name, port=int(port)): Event.trigger("topology_change", src_device=switch, dst_device=name, port=int(port))
def update_ipv4_entries(self, switch=None): """ Update ipv4 entries based on shortest path on switch :param switch: switch where ipv4 entries will be installed :return: """ paths = TopologyManager.get_paths(domain_id=0) valid_entries = [] cur_dev = TopologyManager.get_device(switch) for dst in [d for d in paths.get(switch, {}) if d != switch]: # don't check path from i->i # get the next_hop towards the destination along the shortest path dst_dev = TopologyManager.get_device(dst) next_hop = TopologyManager.get_next_hop(start_node=switch, destination_node=dst, domain_id=0) port = cur_dev.get_device_to_port(next_hop.get_name()) entry = TableEntry( switch=switch, match_fields={ "hdr.ipv4.dstAddr": (str(dst_dev.get_ip()), 32), "meta.ports.status": (BierComputation.id_to_bitstring(id=int(port)), BierComputation.id_to_bitstring(id=int(port))) }, action_name="ingress.ipv4_c.forward", action_params={"port": int(port)}, priority=1) if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="ingress.ipv4_c.ipv4", table_entry=entry): Log.async_debug("Installed IPv4 forwarding rule for", switch, "->", dst) valid_entries.append(entry.match_fields) # Add decap entry entry = TableEntry( switch=cur_dev.get_name(), match_fields={"hdr.ipv4.dstAddr": (str(cur_dev.get_ip()), 32)}, action_name="ingress.ipv4_c.decap", priority=1) if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="ingress.ipv4_c.ipv4", table_entry=entry): Log.async_debug("Installed IPv4 decap rule for", switch) valid_entries.append(entry.match_fields) return valid_entries
def update_frr_node_protection(self, switch=None, domain=None, port=None, to_id=None, fbm=None, next_hop=None): """ Implements BIER-FRR node protection """ switch_device = TopologyManager.get_device(name=switch) remainingBits = BierComputation.id_to_bitstring(id=to_id) # get bift for failed nh bift_nh = BierComputation.build_bift(switch=next_hop, domain=domain) next_hop_device = TopologyManager.get_device(name=next_hop) # send a copy of the packet to the broken node in case its only a link failure if next_hop_device.get_bfr_id(domain) == to_id: bift_nh[to_id] = [BierComputation.id_to_bitstring(to_id), next_hop] if to_id not in bift_nh: return # the backup fbm is the intersection of the old fbm and the fbm of the nh for this entry new_fbm = fbm & bift_nh.get(to_id)[0] # tunnel node is nh of bift from failed nh for this entry tunnel_node = TopologyManager.get_device(name=bift_nh.get(to_id)[1]) src_ip = switch_device.get_ip() dst_ip = tunnel_node.get_ip() entry = TableEntry(switch=switch, match_fields={ "meta.bier_md.remainingBits": (remainingBits, remainingBits), "meta.ports.status": (0, BierComputation.id_to_bitstring(port)) }, action_name="ingress.bier_c.forward_encap", action_params={ "fbm": new_fbm, "srcAddr": src_ip, "dstAddr": dst_ip }, priority=1) return entry
def configureTopologyPackets(self): """ Configure topology packet trigger """ timeout = 10 * 100 * 100 * 100 * 100 # 1 s dt = DevTarget_t(0, hex_to_i16(0xFFFF)) switch = TopologyManager.get_device(Configuration.get('name')) pkt = Ether(src='00:00:00:00:00:00', dst='ff:ff:ff:ff:ff:ff', type=0xDD00) pkt = pkt / TopologyDiscovery(identifier=switch.get_bfr_id(0), port=1, ip=str(switch.get_ip()), mac=str(switch.get_mac())) / IP() / IP() pktlen = len(pkt) offset = (int(self.port_pkt_len/16) + 5) * 16 self.tc.conn_mgr.pktgen_write_pkt_buffer(self.tc.hdl, dt, offset, pktlen, str(pkt)) config = PktGenAppCfg_t(trigger_type=PktGenTriggerType_t.TIMER_PERIODIC, timer=timeout, src_port=68, buffer_offset=offset, length=pktlen) self.tc.conn_mgr.pktgen_cfg_app(self.tc.hdl, dt, 1, config) self.tc.conn_mgr.pktgen_app_enable(self.tc.hdl, dt, 1)
def monitor_messages(self): """ Wait for port status message """ Log.info("Start port monitor") while True: msg = self.sub.recv() msg_type = struct.unpack('4s', msg[:4]) if msg_type[0] == 'PRT|': switch_id = struct.unpack('Q', msg[4:12]) num_statuses = struct.unpack('I', msg[16:20]) # wir betrachten immer nur den ersten Status port, status = struct.unpack('ii', msg[32:40]) self.port_status[port] = status if status == 0: # save port status time # timestamp type, type 2 is port info Log.log_to_file(round((time.time() * 1000) % 1000000), 2, "\r\n", file="logs/port_info.txt") device = TopologyManager.get_device(Configuration.get('name')) device.remove_port(port=port) Event.trigger("topology_change") bool_stat = (status == 1) Event.trigger("port_msg_to_controller", info=proto.connection_pb2.PortInfo(switch=Configuration.get('name'), port=port, status=bool_stat))
def Hello(self, request, context): Event.trigger('global_connection') GlobalConnection.global_connection = GlobalConnection(ip=request.ip, port=request.port) device = TopologyManager.get_device(Configuration.get('name')) return proto.connection_pb2.SwitchInfo(name=Configuration.get('name'), ip=device.get_ip(), mac=device.get_mac(), bfr_id=device.get_bfr_id(0))
def update_igmp(self, pkt): """ Update port information on ipmc groups """ switch = TopologyManager.get_device(name=Configuration.get('name')) mc_addr = pkt.mc_address.encode('utf-8') src_ip = pkt.src_ip.encode('utf-8') port = switch.get_device_to_port( TopologyManager.get_device_by_ip(ip=src_ip).get_name()) if pkt.type == 0x16: if port not in self.mcgrp_to_port[mc_addr]: self.mcgrp_to_port[mc_addr].append(port) elif pkt.type == 0x17: if port in self.mcgrp_to_port[mc_addr]: self.mcgrp_to_port[mc_addr].remove(port) self.update_mc_table()
def compute_bier_header(mc_addr=None, domain=None): """ Compute the bier header for a given mc_addr in a given domain :param mc_addr: multicast address :param domain: domain identifier :return: """ # only use bfrs in given domain all_bfrs = filter( lambda x: domain in TopologyManager.get_domain_for_device(x), GroupManager.get_bfr_by_mc_addr(mc_addr)) # get (domain specific) bfr ids bfrs = map(lambda x: TopologyManager.get_device(x).get_bfr_id(domain), all_bfrs) bier_header = reduce( ior, map(lambda x: BierComputation.id_to_bitstring(x), bfrs), 0) return bier_header
def updatePorts(self, pkt=None): port = pkt[PortDown].port_num device = TopologyManager.get_device(Configuration.get('name')) device.remove_port(port=port) Event.trigger("topology_change") Event.trigger("port_msg_to_controller", info=proto.connection_pb2.PortInfo( switch=Configuration.get('name'), port=port, status=False))
def update(self, **kwargs): device = TopologyManager.get_device(Configuration.get('name')) live_ports = filter(lambda x: self.port_status[x] == 1, device.get_device_to_port_mapping().values()) port_ids = map(lambda x: int(2**(x-1)), live_ports) # this prevents an empty sequence and forces liveport bitstring of 0 port_ids.append(0) port_string = reduce(ior, port_ids) self.write_port_entry(port_string=port_string)
def update_mac_entry(self): """ Add mac rewriting rule for switch->dst_dev via port :param switch: switch where rule will be installed :param dst_dev: next_hop :param port: port which is used towards next_hop :return: """ valid_entries = [] device = TopologyManager.get_device(Configuration.get('name')) for device_name in device.get_device_to_port_mapping(): dev = TopologyManager.get_device(device_name) port = device.get_device_to_port(device_name) Log.debug("Mac:", Configuration.get('name'), "->", device_name, "via", port) entry = TableEntry( match_fields={"standard_metadata.egress_port": int(port)}, action_name="egress.mac_c.set_mac", action_params={ "srcAddr": device.get_mac(), "dstAddr": dev.get_mac() }) TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="egress.mac_c.adjust_mac", table_entry=entry) valid_entries.append(entry.match_fields) Log.debug("Installed Mac rewriting rule for", Configuration.get('name')) # remove possible old entries self.table_manager.remove_invalid_entries( table_name="egress.mac_c.adjust_mac", valid_entries=valid_entries)
def build_bift(switch=None, domain=0): """ Generates the bit index forwarding table :param switch: Switch for which the table should be calculated :param domain: Domain for which the bift should be build :return: bift {bfr-id: [fbm, nextHop] """ birt = BierComputation.build_birt(switch=switch, domain=domain) # {bfr-id: [fbm, nextHop] bift = {} for dest in birt: fbm = BierComputation.get_fbm(birt=birt, next_hop_prefix=birt.get(dest)[1]) destination = TopologyManager.get_device_by_domain_and_id( domain, dest) next_hop = TopologyManager.get_next_hop(switch, destination, domain) bift[dest] = [fbm, next_hop.get_name()] return bift
def update_bier_decap_rule(self, switch=None): """ Updates the bier decap rules based on the current topology :param switch: switch where decap rules should be installed :return: """ valid_entries = [] # bier decap rules for domain in TopologyManager.get_domain_for_device(switch): domain = int(domain) bfr_id = BierComputation.id_to_bitstring( TopologyManager.get_device(switch).get_bfr_id(domain)) entry = TableEntry(switch=switch, match_fields={ "hdr.bier[0].BitString": (bfr_id, bfr_id), "hdr.bier[0].Domain": domain }, action_name="ingress.tunnel_c.bier_decap", action_params={"decapBit": bfr_id}, priority=1) if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="ingress.tunnel_c.decap_bier", table_entry=entry): Log.async_debug("BIER decap rule updated on", switch, "for domain", domain) valid_entries.append(entry.match_fields) self.table_manager.remove_invalid_entries( switch=switch, table_name="ingress.tunnel_c.decap_bier", valid_entries=valid_entries)
def get_domains_for_mc_addr(mc_addr): """ Get domains which have subscriptions on a given multicast address :param mc_addr: multicast address :return: list of domains """ bfrs = GroupManager.get_bfr_by_mc_addr(mc_addr) domains = [0] for bfr in bfrs: domains.extend(TopologyManager.get_domain_for_device(bfr)) domains = list(set(domains)) return domains
def add_ipv4_decap_rule(self, *args, **kwargs): """ Adds an ipv4 decap rule for the switch This event is triggered when a switch is arbitrated :return: """ device = TopologyManager.get_device(kwargs.get('name')) entry = TableEntry(switch=device.get_name(), match_fields={"hdr.ipv4.dstAddr": device.get_ip()}, action_name="ingress.tunnel_c.ipv4_decap") if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="ingress.tunnel_c.decap_ipv4", table_entry=entry): Log.async_debug("Ipv4 decap rule installed for", kwargs.get('name'))
def send_topology_packets(self): """ Send topology packet for given switch :return: """ sw = Configuration.get('name') # get bfr id off switch for identification in top packet switch = TopologyManager.get_device(sw) pkt = Ether(src='00:00:00:00:00:00', dst='ff:ff:ff:ff:ff:ff') pkt = pkt / TopologyDiscovery(identifier=switch.get_bfr_id(0), port=1, ip=str(switch.get_ip()), mac=str(switch.get_mac())) self.__baseController.get_connection().send_packet_out(str(pkt)) Log.debug("Send topology packet to switch", sw) # send topology packet periodically threading.Timer(2, self.send_topology_packets).start()
def update_bier_forwarding_entries(self, switch=None): """ Adds bier forwarding entry for given switch in specifc domain :param switch: :param domain: domain identifier :return: """ table_name = "ingress.bier_c.bift" valid_entries = [] for domain in TopologyManager.get_domain_for_device(switch): domain = int(domain) bift = BierComputation.build_bift(switch=switch, domain=domain) for entry in bift: bit_string = BierComputation.id_to_bitstring(id=entry) out_port = TopologyManager.get_device( switch).get_device_to_port(bift.get(entry)[1]) # generate default bier entry e = TableEntry( switch=switch, match_fields={ "meta.bier_md.remainingBits": (bit_string, bit_string), "meta.ports.status": (BierComputation.id_to_bitstring(id=out_port), BierComputation.id_to_bitstring(id=out_port)) }, action_name="ingress.bier_c.forward", action_params={ "fbm": bift.get(entry)[0], "port": out_port }, priority=1) if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name=table_name, table_entry=e): Log.async_debug("Installed BIER rule for", switch, "to bfr-id", entry) valid_entries.append(e.match_fields) # generate bier-frr link protection entry for this switch and bfr if Configuration.get('protection') == 'Link': frr_entry = self.update_frr_link_protection( switch=switch, domain=domain, port=out_port, to_id=entry, fbm=bift.get(entry)[0], next_hop=bift.get(entry)[1]) if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name=table_name, table_entry=frr_entry): Log.async_debug( "Installed BIER-FRR link protection rule for", switch) valid_entries.append(frr_entry.match_fields) # generate bier-frr node protection entry for this switch and bfr if Configuration.get('protection') == 'Node': frr_entry = self.update_frr_node_protection( switch=switch, domain=domain, port=out_port, to_id=entry, fbm=bift.get(entry)[0], next_hop=bift.get(entry)[1]) if frr_entry: if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name=table_name, table_entry=frr_entry): Log.async_debug( "Installed BIER-FRR node protection rule for", switch, "to", entry) valid_entries.append(frr_entry.match_fields) bfr_id = BierComputation.id_to_bitstring( TopologyManager.get_device(switch).get_bfr_id(0)) # Add decap entry entry = TableEntry( switch=switch, match_fields={"meta.bier_md.remainingBits": (bfr_id, bfr_id)}, action_name="ingress.bier_c.decap", action_params={"decapBit": bfr_id}, priority=1) if TableEntryManager.handle_table_entry(manager=self.table_manager, table_name=table_name, table_entry=entry): Log.async_debug("Installed BIER decap rule for", switch) valid_entries.append(entry.match_fields) self.table_manager.remove_invalid_entries(switch=switch, table_name=table_name, valid_entries=valid_entries)