def update_bier_encap_entry(self, switch=None): """ Add bier encap entry for given prefix :param switch: switch where rules should be installed :return: """ valid_entries = [] for mc_addr in GroupManager.get_mc_addresses(): for domain in GroupManager.get_domains_for_mc_addr(mc_addr): domain = int(domain) bitstring = BierComputation.compute_bier_header( mc_addr=mc_addr, domain=domain) entry = TableEntry(switch=switch, match_fields={ "hdr.ipv4.dstAddr": mc_addr, }, action_name="ingress.ipv4_c.add_bier", action_params={"bs": bitstring}) if TableEntryManager.handle_table_entry( manager=self.table_manager, table_name="ingress.ipv4_c.encap_ipv4", table_entry=entry): Log.async_debug("Installed encap ipv4 rule for", switch, mc_addr, bitstring) valid_entries.append(entry.match_fields) self.table_manager.remove_invalid_entries( switch=switch, table_name="ingress.ipv4_c.encap_ipv4", valid_entries=valid_entries)
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 handle_cli_command(arg1, arg2): if arg1 == 'describe': TableEntryManager.describe(arg2) elif arg1 == 'show_tables': TableEntryManager.show_tables(arg2) else: Log.echo("Command not found.")
def trigger(event, *args, **kw): """ Trigger an event. The corresponding handlers will be executed :param event: event name :param args: arguments without key :param kw: arguments with key :return: """ if not Event.activated: return callbacks = list(Event.events.get(event, [])) if not callbacks: return False Event.lock.acquire() for cb in callbacks: try: cb(*args, **kw) except Exception as e: Log.error(str((traceback.format_exc()))) # release this method Event.lock.release() return True
def message_in(*args, **kwargs): """ Generic message_in handle function Triggers corresponding event :param kwargs: :return: """ packet = kwargs.get('packet') switch = kwargs.get('switch') try: pkt = Ether(packet.packet.payload) try: Configuration.get('system_done') except ConfigurationNotFound: return if pkt.type == 0xEE00: # its an port down packet Log.info("Port:", pkt[PortDown].port_num, "down", "on ingress", pkt[PortDown].pad1, "on pipe") Event.trigger("port_down", pkt=pkt) if pkt.type == 0xDD00: # its an topology packet Event.trigger("topology_packet_in", packet=pkt, switch=switch) if pkt.type == 0x800 and pkt[IP].proto == 2: # its an igmp packet igmp = pkt.payload pkt = proto.connection_pb2.GroupPacket(type=int(igmp.type), mc_address=str(igmp.gaddr), src_ip=str(igmp.src), switch=Configuration.get('name')) Event.trigger("igmp_packet_to_controller", pkt=pkt) Log.debug("Send igmp packet to controller") except Exception as e: # it's not an ethernet frame pass
def setupProtection(self, data=None): self.data = data Log.info("Setup port protection") dt = DevTarget_t(0, hex_to_i16(0xFFFF)) p = port_down_packet() pktlen = self.port_pkt_len = len(p) self.tc.conn_mgr.pktgen_write_pkt_buffer(self.tc.hdl, dt, 0, pktlen, str(p)) offset = 0 # enable on all pipes for pipe in range(0, self.pal.pal_num_pipes_get(0)): port = (pipe << 7 | 68) self.tc.conn_mgr.pktgen_enable(self.tc.hdl, 0, (pipe << 7 | 68)) Log.debug("Enable pkt gen on port", port) config = PktGenAppCfg_t(trigger_type=PktGenTriggerType_t.PORT_DOWN, timer=0, src_port=68, buffer_offset=offset, length=pktlen) self.tc.conn_mgr.pktgen_cfg_app(self.tc.hdl, dt, 0, config) self.tc.conn_mgr.pktgen_app_enable(self.tc.hdl, dt, 0) offset=pktlen
def update(self, *args, **kwargs): """ Update bier tables, bift Is called on certain events :return: """ if "port_update" in kwargs: try: d = Configuration.get('update') if "bier" not in d: return if "sleep" in kwargs: time.sleep(kwargs.get("sleep")) except ConfigurationNotFound: pass srcDevice = kwargs.get('src_device') for switch in self._baseController.get_connections(): self.update_bier_forwarding_entries(switch=switch) Log.async_info("Updated BIER entries.")
def accept(event=None): """ This method is called wenn enter is hit :param event: :return: """ user_input = input_field.text # get user_input, split on blank, user_input[0] is command, user_input[i>0] are arguments user_input = user_input.split(" ") if user_input[0] in CLI.prompt_to_execution: command = CLI.prompt_to_execution.get(user_input[0], lambda: 'Invalid')[0] args = CLI.prompt_to_execution.get(user_input[0], lambda: 'Invalid')[1] # currently only up to 2 arguments are supported (for send command in host cli) if len(user_input) == 2: command(user_input[1]) elif len(user_input) == 3: command(user_input[1], user_input[2]) elif args is not None: command(args) else: command() else: Log.error("Command not found") input_field.text = ''
def purge(self): """ Delete all current table entries """ [self.delete_table_entry(table_name=e[0], entry=e[1]) for e in self.entries] Log.info("Entries purged") self.entries = []
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 main(): # without this line, no events would be fired, no topology discovered and no entries computed Event.activate() # base controller controller = BaseController(p4info_file_path=Configuration.get('p4info'), bmv2_path=Configuration.get('bmv2_json'), prog_name=Configuration.get('prog_name'), bin_path=Configuration.get('bin_path'), cxt_json_path=Configuration.get('cxt_path')) # register event for new switch connections, this will add switches to device list Event.on('new_switch_connection', TopologyManager.add_device) # register events for static classes Event.on("packet_in", MessageInHandler.message_in) # handles generic packet in Event.on("topology_to_controller", GlobalConnection.send_topology_packet ) # triggers the send routine to server Event.on("igmp_packet_to_controller", GlobalConnection.send_group_packet ) # triggers the send routine to server Event.on( "port_msg_to_controller", GlobalConnection.send_port_info) # triggers the send routine to server topology = TopologyController(controller) # Create instances of sub controller mac = MacController(controller) port = PortController(controller=controller) pd = PDSetup() mc = MulticastController(pd=pd, base=controller) # start connection procedure init_switches(controller=controller, topology_controller=topology, pd=pd) bier = BierController(controller) # start grpc server for connection to main controller grpc_server = GRPCServer(listen_port=Configuration.get('listen_port')) # set controller in local server for table entry LocalServer.controller = controller # start grpc server grpc_server.start() # start port monitor #threading.Thread(target=port.monitor_messages()).start() try: while True: time.sleep(1) except KeyboardInterrupt: pd.end() Log.info("Shutting down") os._exit(0)
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 addTableEntry(self, tableEntry=None): """ Add a table entry to the switch """ response = self.stub.AddEntry(tableEntry) if response.code == 0: Log.error("Error for entry:", tableEntry, "on switch", self.name)
def start(self): """ Start grpc server This grpc server will be used to connect the local controller with the global """ proto.connection_pb2_grpc.add_GlobalServerServicer_to_server(GlobalServer(), self.server) self.server.add_insecure_port('[::]:' + str(self.listen_port)) Log.async_info("Start GRPC Server on port", self.listen_port) self.server.start()
def start(self): """ Start grpc server This grpc server will be used for the direction global-controller ----> local-controller """ proto.connection_pb2_grpc.add_LocalServerServicer_to_server(LocalServer(), self.server) self.server.add_insecure_port('0.0.0.0:' + str(self.listen_port)) Log.info("Start GRPC Server on port", self.listen_port) self.server.start()
def __init__(self, ip=None, port=0): self.channel = grpc.insecure_channel(ip + ":" + str(port)) self.stub = proto.connection_pb2_grpc.GlobalServerStub(self.channel) reponse = self.stub.CheckConnection(proto.connection_pb2.Empty()) Log.info("Global connection to", ip + ":" + str(port)) # remove possible old connection when a new global connection is initialized Event.on('global_connection', self.close)
def removeTableEntry(self, tableEntry=None): """ Remove a table entry from the switch """ response = self.stub.RemoveEntry(tableEntry) if response.code == 0: Log.error("Error while removing entry:", tableEntry, "on switch", self.name)
def setPorts(self, data=None): for configuration in data[Configuration.get('name')]: p_id = self.pal.pal_port_front_panel_port_to_dev_port_get(0, configuration['port'], configuration['channel']) self.pal.pal_port_add(0, p_id, configuration['speed'], pal_fec_type_t.BF_FEC_TYP_NONE) self.pal.pal_port_an_set(0, p_id, 2) self.pal.pal_port_enable(0, p_id) if 'loopback' in configuration and configuration['loopback']: self.pal.pal_port_loopback_mode_set(0, p_id, 1) Log.debug("Set port", p_id, "to loopback")
def stream_iterator(self): while True: try: p = self.stream_requests.get() except Exception as e: Log.error("Error in stream_iterator", e) if p is None: break yield p
def describe(manager): data = PrettyTable() manager = TableEntryManager.get(manager=manager) data.field_names = ["Table name", "# table entries"] for table in manager.tables.keys(): t = manager.get_table_entries(table_name=table) data.add_row([table, len(t)]) Log.echo(data)
def AddEntry(self, request, context): """ Add a table entry to the switch """ if LocalServer.controller.add_entry(entry=request): if Configuration.get("name") == "s1": Log.log_to_file(round((time.time() * 1000) % 1000000), request.table_name, "\r\n", file="logs/entry_info.txt") return proto.connection_pb2.Status(code=1, message="all good")
def set_forwarding_pipeline_config(self): """ Set forwarding pipeline on the switch based on p4info file :return: """ try: self.__connection.SetForwardingPipelineConfig(p4info=self.__p4info_helper.p4info, prog_name=self.prog_name, bin_path=self.bin_path, cxt_json_path=self.cxt_json_path) Event.trigger("switch_arbitrated") except Exception as e: Log.error("Error in forwarding pipeline", e) Log.info("Forwarding pipeline set.")
def describe(): data = PrettyTable() data.field_names = ["MC Address", "Subscribed BFRs", "Domains"] for mc_addr in GroupManager.get_mc_addresses(): data.add_row([ mc_addr, GroupManager.get_bfr_by_mc_addr(mc_addr), GroupManager.get_domains_for_mc_addr(mc_addr) ]) Log.echo(data)
def PortMessage(self, request, context): """ This method receives a port message """ Log.async_info("Got port message") Log.async_debug(request) # this event is not catched yet # for demonstration purpose, the topology doesn't get updated # on a link failure Event.trigger("port_message", message=request) return proto.connection_pb2.Status(code=1, message="Accepted")
def set_forwarding_pipeline_config(self): """ Set forwarding pipeline on the switch based on p4info file :return: """ try: self.__connection.SetForwardingPipelineConfig(p4info=self.__p4info_helper.p4info, bmv2_json_file_path=self.__bmv2_file_path.encode()) Event.trigger("switch_arbitrated") except Exception as e: Log.error("Error in forwarding pipeline", e) Log.info("Forwarding pipeline set.")
def handle_packet_in(pkt): switch = pkt.switch.encode('utf-8') mc_addr = pkt.mc_address.encode('utf-8') src_ip = pkt.src_ip.encode('utf-8') if pkt.type == 0x16: GroupManager.add_to_group(switch, src_ip, mc_addr) elif pkt.type == 0x17: GroupManager.remove_from_group(switch, src_ip, mc_addr) Event.trigger("group_update") Log.event("Got igmp packet with type", hex(pkt.type), "and src", src_ip, "for group", mc_addr, "from", switch)
def list_commands(): """ List all commands with info text for help output :return: """ data = PrettyTable() data.field_names = ["Command", "Info"] for k, v in sorted(CLI.prompt_to_info.iteritems()): data.add_row([k, v]) Log.echo(data)
def remove_from_group(switch, member, group): """ Removes a host from a given multicast group :param switch: switch which got the unsub message :param member: host which unsubscribes :param group: multicast group :return: """ try: GroupManager.group_to_member.get(switch, {}).get(group, []).remove(member) except ValueError as e: Log.error("try to remove", member, "from", switch, "and group", group, e, GroupManager.group_to_member) pass
def add_flood_node(self): """ Add mc mc nodes """ p = Popen([self.cli, '--thrift-port', str(self.thrift_port)], stdout=PIPE, stdin=PIPE, stderr=STDOUT) out, err = p.communicate( input="mc_node_create 0 " + " ".join(map(str, list(range(self.max_port))))) if err: Log.error(err)
def show_tables(manager): manager = TableEntryManager.get(manager=manager) prettyTables = [] for table in manager.tables.keys(): t = manager.get_table_entries(table_name=table) data = PrettyTable() data.field_names = ["Switch", "Table Name", "Match fields", "Action name", "Action params", "Priority"] for entry in t: data.add_row([entry.switch, table, entry.match_fields, entry.action_name, entry.action_params, entry.priority]) prettyTables.append(data) for table in prettyTables: Log.echo(table)