class RoutingController(object):
    def __init__(self):

        self.topo = Topology(db="topology.db")
        self.controllers = {}
        self.init()

    def init(self):
        self.connect_to_switches()
        self.reset_states()
        self.set_table_defaults()

    def reset_states(self):
        [controller.reset_state() for controller in self.controllers.values()]

    def connect_to_switches(self):
        for p4switch in self.topo.get_p4switches():
            thrift_port = self.topo.get_thrift_port(p4switch)
            self.controllers[p4switch] = SimpleSwitchAPI(thrift_port)

    def set_table_defaults(self):
        for controller in self.controllers.values():
            controller.table_set_default("ipv4_lpm", "drop", [])
            controller.table_set_default("ecmp_group_to_nhop", "drop", [])

    def add_mirroring_ids(self):

        for sw_name, controller in self.controllers.items():
            controller.mirroring_add(100, 1)

    def set_egress_type_table(self):

        for sw_name, controller in self.controllers.items():

            for intf, node in self.topo.get_interfaces_to_node(
                    sw_name).items():
                node_type = self.topo.get_node_type(node)
                port_number = self.topo.interface_to_port(sw_name, intf)

                if node_type == 'host':
                    node_type_num = 1
                elif node_type == 'switch':
                    node_type_num = 2

                print "table_add at {}:".format(sw_name)
                self.controllers[sw_name].table_add("egress_type",
                                                    "set_egress_type",
                                                    [str(port_number)],
                                                    [str(node_type_num)])

    def route(self):

        switch_ecmp_groups = {
            sw_name: {}
            for sw_name in self.topo.get_p4switches().keys()
        }

        for sw_name, controller in self.controllers.items():
            for sw_dst in self.topo.get_p4switches():

                #if its ourselves we create direct connections
                if sw_name == sw_dst:
                    for host in self.topo.get_hosts_connected_to(sw_name):
                        sw_port = self.topo.node_to_node_port_num(
                            sw_name, host)
                        host_ip = self.topo.get_host_ip(host) + "/32"
                        host_mac = self.topo.get_host_mac(host)

                        #add rule
                        print "table_add at {}:".format(sw_name)
                        self.controllers[sw_name].table_add(
                            "ipv4_lpm", "set_nhop", [str(host_ip)],
                            [str(host_mac), str(sw_port)])

                #check if there are directly connected hosts
                else:
                    if self.topo.get_hosts_connected_to(sw_dst):
                        paths = self.topo.get_shortest_paths_between_nodes(
                            sw_name, sw_dst)
                        for host in self.topo.get_hosts_connected_to(sw_dst):

                            if len(paths) == 1:
                                next_hop = paths[0][1]

                                host_ip = self.topo.get_host_ip(host) + "/24"
                                sw_port = self.topo.node_to_node_port_num(
                                    sw_name, next_hop)
                                dst_sw_mac = self.topo.node_to_node_mac(
                                    next_hop, sw_name)

                                #add rule
                                print "table_add at {}:".format(sw_name)
                                self.controllers[sw_name].table_add(
                                    "ipv4_lpm", "set_nhop", [str(host_ip)],
                                    [str(dst_sw_mac),
                                     str(sw_port)])

                            elif len(paths) > 1:
                                next_hops = [x[1] for x in paths]
                                dst_macs_ports = [
                                    (self.topo.node_to_node_mac(
                                        next_hop, sw_name),
                                     self.topo.node_to_node_port_num(
                                         sw_name, next_hop))
                                    for next_hop in next_hops
                                ]
                                host_ip = self.topo.get_host_ip(host) + "/24"

                                #check if the ecmp group already exists. The ecmp group is defined by the number of next
                                #ports used, thus we can use dst_macs_ports as key
                                if switch_ecmp_groups[sw_name].get(
                                        tuple(dst_macs_ports), None):
                                    ecmp_group_id = switch_ecmp_groups[
                                        sw_name].get(tuple(dst_macs_ports),
                                                     None)
                                    print "table_add at {}:".format(sw_name)
                                    self.controllers[sw_name].table_add(
                                        "ipv4_lpm", "ecmp_group",
                                        [str(host_ip)], [
                                            str(ecmp_group_id),
                                            str(len(dst_macs_ports))
                                        ])

                                #new ecmp group for this switch
                                else:
                                    new_ecmp_group_id = len(
                                        switch_ecmp_groups[sw_name]) + 1
                                    switch_ecmp_groups[sw_name][tuple(
                                        dst_macs_ports)] = new_ecmp_group_id

                                    #add group
                                    for i, (mac,
                                            port) in enumerate(dst_macs_ports):
                                        print "table_add at {}:".format(
                                            sw_name)
                                        self.controllers[sw_name].table_add(
                                            "ecmp_group_to_nhop", "set_nhop",
                                            [str(new_ecmp_group_id),
                                             str(i)],
                                            [str(mac), str(port)])

                                    #add forwarding rule
                                    print "table_add at {}:".format(sw_name)
                                    self.controllers[sw_name].table_add(
                                        "ipv4_lpm", "ecmp_group",
                                        [str(host_ip)], [
                                            str(new_ecmp_group_id),
                                            str(len(dst_macs_ports))
                                        ])

    def main(self):
        self.set_egress_type_table()
        self.add_mirroring_ids()
        self.route()
Exemple #2
0
class P4CLI(CLI):

    def __init__(self, *args, **kwargs):
        self.conf_file = kwargs.get("conf_file", None)
        self.import_last_modifications = {}

        self.last_compilation_state = False

        if not self.conf_file:
            log.warn("No configuration given to the CLI. P4 functionalities are disabled.")
        else:
            self.config = load_conf(self.conf_file)
            # class CLI from mininet.cli does not have config parameter, thus remove it
            kwargs.__delitem__("conf_file")
        CLI.__init__(self, *args, **kwargs)

    def failed_status(self):
        self.last_compilation_state = False
        return FAILED_STATUS

    def do_load_topo_conf(self, line= ""):

        """
        Updates the topo config
        Args:
            line:

        Returns:

        """
        args = line.split()
        if args:
            conf_file = args[0]
            self.conf_file = conf_file

        #re-load conf file
        self.config = load_conf(self.conf_file)

    def do_set_p4conf(self, line=""):
        """Updates configuration file location, and reloads it."""
        args = line.split()
        conf = args[0]
        if not os.path.exists(conf):
            warn('Configuratuion file %s does not exist' % conf)
            return
        self.conf_file = conf
        self.config = load_conf(conf)

    def do_test_p4(self, line=""):
        """Tests start stop functionalities."""
        self.do_p4switch_stop("s1")
        self.do_p4switch_start("s1")
        self.do_p4switch_reboot("s1")
        self.do_p4switches_reboot()

    def do_p4switch_stop(self, line=""):
        """Stop simple switch from switch namespace."""
        switch_name = line.split()
        if not switch_name or len(switch_name) > 1:
            error('usage: p4switch_stop <p4switch name>\n')
        else:
            switch_name = switch_name[0]
            if switch_name not in self.mn:
                error("p4switch %s not in the network\n" % switch_name)
            else:
                p4switch = self.mn[switch_name]
                p4switch.stop_p4switch()

    def do_p4switch_start(self, line=""):
        """Start again simple switch from namespace."""
        args = line.split()

        # check args validity
        if len(args) > 5:
            error('usage: p4switch_start <p4switch name> [--p4src <path>] [--cmds path]\n')
            return self.failed_status()

        switch_name = args[0]

        if switch_name not in self.mn:
            error('usage: p4switch_start <p4switch name> [--p4src <path>] [--cmds path]\n')
            return self.failed_status()

        p4switch = self.mn[switch_name]

        # check if switch is running
        if p4switch.check_switch_started():
            error('P4 Switch already running, stop it first: p4switch_stop %s \n' % switch_name)
            return self.failed_status()

        #load default configuration
        # mandatory defaults if not defined we should complain
        default_p4 = self.config.get("program", None)
        default_options = self.config.get("options", None)

        # non mandatory defaults.
        default_compiler = self.config.get("compiler", DEFAULT_COMPILER)

        default_config = {"program": default_p4, "options": default_options, "compiler": default_compiler}
        #merge with switch conf
        switch_conf = default_config.copy()
        switch_conf.update(self.config['topology']['switches'][switch_name])

        if "--p4src" in args:
            p4source_path = args[args.index("--p4src")+1]
            switch_conf['program'] = p4source_path
            # check if file exists
            if not os.path.exists(p4source_path):
                warn('File Error: p4source %s does not exist\n' % p4source_path)
                return self.failed_status()
            #check if its not a file
            if not os.path.isfile(p4source_path):
                warn('File Error: p4source %s is not a file\n' % p4source_path)
                return self.failed_status()

        p4source_path_source = switch_conf['program']

        # generate output file name
        output_file = p4source_path_source.replace(".p4", "") + ".json"

        program_flag = last_modified(p4source_path_source, output_file)
        includes_flag = check_imports_last_modified(p4source_path_source,
                                                    self.import_last_modifications)

        log.debug("%s %s %s %s\n" % (p4source_path_source, output_file, program_flag, includes_flag))

        if program_flag or includes_flag or (not self.last_compilation_state):
            # compile program
            try:
                compile_p4_to_bmv2(switch_conf)
                self.last_compilation_state = True
            except CompilationError:
                log.error('Compilation failed\n')
                return self.failed_status()

            # update output program
            p4switch.json_path = output_file

        # start switch
        p4switch.start()

        # load command entries
        if "--cmds" in args:
            commands_path = args[args.index("--cmds")+1]
            # check if file exists

        else:
            commands_path = switch_conf.get('cli_input', None)

        if commands_path:
            if not os.path.exists(commands_path):
                error('File Error: commands file %s does not exist\n' % commands_path)
                return self.failed_status()
            entries = read_entries(commands_path)
            add_entries(p4switch.thrift_port, entries)

        return SUCCESS_STATUS

    def do_printSwitches(self, line=""):
        """Print names of all switches."""
        for sw in self.mn.p4switches:
            print((sw.name))   

    def do_p4switches_reboot(self, line=""):
        """Reboot all P4 switches with new program.

        Note:
            If you provide a P4 source code or cmd, all switches will have the same.
        """
        self.config = load_conf(self.conf_file)

        for sw in self.mn.p4switches:
            switch_name = sw.name
            self.do_p4switch_stop(line=switch_name)

            tmp_line = switch_name + " " +line
            self.do_p4switch_start(line=tmp_line)

        #run scripts
        if isinstance(self.config.get('exec_scripts', None), list):
            for script in self.config.get('exec_scripts'):
                if script["reboot_run"]:
                    info("Exec Script: {}\n".format(script["cmd"]))
                    run_command(script["cmd"])

    def do_p4switch_reboot(self, line=""):
        """Reboot a P4 switch with a new program."""
        self.config = load_conf(self.conf_file)
        if not line or len(line.split()) > 5:
            error('usage: p4switch_reboot <p4switch name> [--p4src <path>] [--cmds path]\n')
        else:
            switch_name = line.split()[0]
            self.do_p4switch_stop(line=switch_name)
            self.do_p4switch_start(line=line)

    def do_pingset(self ,line=""):
        hosts_names = line.strip().split()
        hosts = [x for x in self.mn.hosts if x.name in hosts_names]
        self.mn.ping(hosts=hosts, timeout=1)


    def do_printNetInfo(self, line=""):
        """Prints Topology Info"""

        self.topo = Topology(db="topology.db")
   
        print("\n*********************")
        print("Network Information:")
        print("*********************\n")
        
        switches = self.topo.get_switches()

        for sw in sorted(switches.keys()):
            
            # skip linux bridge
            if sw == "sw-cpu":
                continue

            thrift_port = self.topo.get_thrift_port(sw)
            switch_id = self.topo[sw].get("sw_id", "N/A")
            cpu_index = self.topo.get_cpu_port_index(sw, quiet=True)
            header = "{}(thirft->{}, cpu_port->{})".format(sw, thrift_port, cpu_index)

            header2 = "{:>4} {:>15} {:>8} {:>20} {:>16} {:>8} {:>8} {:>8} {:>8} {:>8}".format("port", "intf", "node", "mac", "ip", "bw", "weight", "delay", "loss","queue")                                                                                     

            print(header)
            print((len(header2)*"-")) 
            print(header2)
            
            for intf,port_number  in sorted(list(self.topo.get_interfaces_to_port(sw).items()), key=lambda x: x[1]):
                if intf == "lo":
                    continue
                
                other_node = self.topo.get_interfaces_to_node(sw)[intf]
                mac = self.topo[sw][other_node]['mac']
                ip = self.topo[sw][other_node]['ip'].split("/")[0]
                bw = self.topo[sw][other_node]['bw']
                weight = self.topo[sw][other_node]['weight']
                delay = self.topo[sw][other_node]['delay']
                loss = self.topo[sw][other_node]['loss']
                queue_length = self.topo[sw][other_node]['queue_length']
                print(("{:>4} {:>15} {:>8} {:>20} {:>16} {:>8} {:>8} {:>8} {:>8} {:>8}".format(port_number, intf, other_node, mac, ip, bw, weight, delay, loss, queue_length)))

            print((len(header2)*"-")) 
            print("")

        # HOST INFO
        print("Hosts Info")

        header = "{:>4} {:>15} {:>8} {:>20} {:>16} {:>8} {:>8} {:>8} {:>8} {:>8}".format(
            "name", "intf", "node", "mac", "ip", "bw", "weight", "delay", "loss","queue")    
        
        print((len(header)*"-")) 
        print(header)

        for host in sorted(self.topo.get_hosts()):           
            for intf,port_number  in sorted(list(self.topo.get_interfaces_to_port(host).items()), key=lambda x: x[1]):
                
                other_node = self.topo.get_interfaces_to_node(host)[intf]
                mac = self.topo[host][other_node]['mac']
                ip = self.topo[host][other_node]['ip'].split("/")[0]
                bw = self.topo[host][other_node]['bw']
                weight = self.topo[host][other_node]['weight']
                delay = self.topo[host][other_node]['delay']
                loss = self.topo[host][other_node]['loss']
                queue_length = self.topo[host][other_node]['queue_length']
                print(("{:>4} {:>15} {:>8} {:>20} {:>16} {:>8} {:>8} {:>8} {:>8} {:>8}".format(host, intf, other_node, mac, ip, bw, weight, delay, loss, queue_length)))

        print((len(header)*"-")) 
        print("")

#def describe(self, sw_addr=None, sw_mac=None):
#    print "**********"
#    print "Network configuration for: %s" % self.name
#    print "Default interface: %s\t%s\t%s" %(
#        self.defaultIntf().name,
#        self.defaultIntf().IP(),
#        self.defaultIntf().MAC()
#    )
#    if sw_addr is not None or sw_mac is not None:
#        print "Default route to switch: %s (%s)" % (sw_addr, sw_mac)
#    print "**********"
#    
#def describe(self):
#    print "%s -> Thrift port: %d" % (self.name, self.thrift_port)
Exemple #3
0
class RoutingController(object):
    def __init__(self):

        self.topo = Topology(db="topology.db")
        self.controllers = {}
        self.init()

    def init(self):
        self.connect_to_switches()
        self.reset_states()
        self.set_table_defaults()
        '''
        OPTIONS FOR DEMO
        '''
        self.apply_src_priority = True
        self.apply_dst_priority = False
        self.src_high_priority = 'h1'
        self.src_low_priority = 'h4'
        self.dst_high_priority = 'h5'
        self.dst_low_priority = 'h8'

    def reset_states(self):
        [controller.reset_state() for controller in self.controllers.values()]

    def connect_to_switches(self):
        for p4switch in self.topo.get_p4switches():
            thrift_port = self.topo.get_thrift_port(p4switch)
            self.controllers[p4switch] = SimpleSwitchAPI(thrift_port)

    def set_table_defaults(self):
        for controller in self.controllers.values():
            controller.table_set_default("ipv4_lpm", "drop", [])
            controller.table_set_default("ecmp_group_to_nhop", "drop", [])

    def set_tables(self):
        # From project 6
        # Function outside of route() that sets the egress type table

        # loops through all switches
        for sw_name, controller in self.controllers.items():

            # gets the interface and node type
            for interface, node in self.topo.get_interfaces_to_node(
                    sw_name).items():

                node_type = self.topo.get_node_type(node)
                port_number = self.topo.interface_to_port(sw_name, interface)

                # numerates the node types to be put in the table
                if node_type == 'host':
                    node_type_num = 1

                    # NEW - CODE TO SET PRIORITY BASED ON HOST NUMBER
                    host_ip = self.topo.get_host_ip(node) + "/24"
                    priority_num = 2
                    if str(
                            node
                    ) == self.src_high_priority and self.apply_src_priority:
                        priority_num = 1
                    elif str(
                            node
                    ) == self.src_low_priority and self.apply_src_priority:
                        priority_num = 3
                    elif str(
                            node
                    ) == self.dst_high_priority and self.apply_dst_priority:
                        priority_num = 1
                    elif str(
                            node
                    ) == self.dst_low_priority and self.apply_dst_priority:
                        priority_num = 3
                    print "Node name: {}, ip address: {}, priority: {}".format(
                        str(node), str(host_ip), str(priority_num))
                    self.controllers[sw_name].table_add(
                        "priority_type", "set_priority", [str(host_ip)],
                        [str(priority_num)])
                    if self.apply_dst_priority:
                        self.controllers[sw_name].table_add(
                            "priority_type_dst", "set_priority",
                            [str(host_ip)], [str(priority_num)])

                elif node_type == 'switch':
                    node_type_num = 2

                # fills the table
                self.controllers[sw_name].table_add("egress_type", "set_type",
                                                    [str(port_number)],
                                                    [str(node_type_num)])

    def add_mirroring_ids(self):

        for sw_name, controller in self.controllers.items():
            # adding port 1 (it seems like the first argument is standard)
            controller.mirroring_add(100, 1)

    def route(self):

        switch_ecmp_groups = {
            sw_name: {}
            for sw_name in self.topo.get_p4switches().keys()
        }

        for sw_name, controller in self.controllers.items():

            for sw_dst in self.topo.get_p4switches():

                #if its ourselves we create direct connections
                if sw_name == sw_dst:
                    for host in self.topo.get_hosts_connected_to(sw_name):
                        sw_port = self.topo.node_to_node_port_num(
                            sw_name, host)
                        host_ip = self.topo.get_host_ip(host) + "/32"
                        host_mac = self.topo.get_host_mac(host)

                        #add rule
                        print "table_add at {}:".format(sw_name)
                        self.controllers[sw_name].table_add(
                            "ipv4_lpm", "set_nhop", [str(host_ip)],
                            [str(host_mac), str(sw_port)])

                #check if there are directly connected hosts
                else:
                    if self.topo.get_hosts_connected_to(sw_dst):
                        paths = self.topo.get_shortest_paths_between_nodes(
                            sw_name, sw_dst)
                        for host in self.topo.get_hosts_connected_to(sw_dst):

                            if len(paths) == 1:
                                next_hop = paths[0][1]

                                host_ip = self.topo.get_host_ip(host) + "/24"
                                sw_port = self.topo.node_to_node_port_num(
                                    sw_name, next_hop)
                                dst_sw_mac = self.topo.node_to_node_mac(
                                    next_hop, sw_name)

                                #add rule
                                print "table_add at {}:".format(sw_name)
                                self.controllers[sw_name].table_add(
                                    "ipv4_lpm", "set_nhop", [str(host_ip)],
                                    [str(dst_sw_mac),
                                     str(sw_port)])

                            elif len(paths) > 1:
                                next_hops = [x[1] for x in paths]
                                dst_macs_ports = [
                                    (self.topo.node_to_node_mac(
                                        next_hop, sw_name),
                                     self.topo.node_to_node_port_num(
                                         sw_name, next_hop))
                                    for next_hop in next_hops
                                ]
                                host_ip = self.topo.get_host_ip(host) + "/24"

                                #check if the ecmp group already exists. The ecmp group is defined by the number of next
                                #ports used, thus we can use dst_macs_ports as key
                                if switch_ecmp_groups[sw_name].get(
                                        tuple(dst_macs_ports), None):
                                    ecmp_group_id = switch_ecmp_groups[
                                        sw_name].get(tuple(dst_macs_ports),
                                                     None)
                                    print "table_add at {}:".format(sw_name)
                                    self.controllers[sw_name].table_add(
                                        "ipv4_lpm", "ecmp_group",
                                        [str(host_ip)], [
                                            str(ecmp_group_id),
                                            str(len(dst_macs_ports))
                                        ])

                                #new ecmp group for this switch
                                else:
                                    new_ecmp_group_id = len(
                                        switch_ecmp_groups[sw_name]) + 1
                                    switch_ecmp_groups[sw_name][tuple(
                                        dst_macs_ports)] = new_ecmp_group_id

                                    #add group
                                    for i, (mac,
                                            port) in enumerate(dst_macs_ports):
                                        print "table_add at {}:".format(
                                            sw_name)
                                        self.controllers[sw_name].table_add(
                                            "ecmp_group_to_nhop", "set_nhop",
                                            [str(new_ecmp_group_id),
                                             str(i)],
                                            [str(mac), str(port)])

                                    #add forwarding rule
                                    print "table_add at {}:".format(sw_name)
                                    self.controllers[sw_name].table_add(
                                        "ipv4_lpm", "ecmp_group",
                                        [str(host_ip)], [
                                            str(new_ecmp_group_id),
                                            str(len(dst_macs_ports))
                                        ])

    def main(self):
        self.set_tables()
        self.add_mirroring_ids()
        self.route()
class RoutingController(object):

    def __init__(self):

        self.topo = Topology(db="topology.db")
        self.controllers = {}
        self.init()

    def init(self):
        self.connect_to_switches()
        self.reset_states()
        self.set_table_defaults()

    def reset_states(self):
        [controller.reset_state() for controller in self.controllers.values()]

    def connect_to_switches(self):
        for p4switch in self.topo.get_p4switches():
            thrift_port = self.topo.get_thrift_port(p4switch)
            self.controllers[p4switch] = SimpleSwitchAPI(thrift_port)

    def set_table_defaults(self):
        for controller in self.controllers.values():
            controller.table_set_default("ipv4_lpm", "drop", [])
            controller.table_set_default("ecmp_group_to_nhop", "drop", [])

    def set_icmp_ingress_port_table(self):        

        #TODO 2: Fill the icmp_ingress_port table for all the switches in the topology
        for p4switch, ss_api in self.controllers.items():
            intfs = self.topo.get_interfaces_to_node(p4switch)
            for intf, neigh in intfs.items():
                intf_ip = self.topo.node_to_node_interface_ip(p4switch, neigh)
                intf_ip = intf_ip[:-3]
                port = self.topo.interface_to_port(p4switch, intf)
                ss_api.table_add('icmp_ingress_port', 'set_src_icmp_ip', [str(port)] ,[str(intf_ip)])
                


    def get_conn_host_infos(self, p4switch):
        connected_hosts = self.topo.get_hosts_connected_to(p4switch)
        if connected_hosts:
            host = connected_hosts[0]
            switch_infos = self.topo.node(p4switch)
            host_mac = self.topo.get_host_mac(host)
            host_ip = self.topo.get_host_ip(host) + '/32'
            output_iface = self.topo.interface_to_port(p4switch,switch_infos[host]['intf'])
            return host_ip, host_mac, output_iface
        else:
            return None, None, None

    def add_ecmp_group(self, p4switch, ss_api, neigh, paths, ecmp_group):
        host_ip, host_mac, output_iface = self.get_conn_host_infos(neigh)
        if host_ip:
            next_hops = [path[1] for path in paths]
            dst_macs_ports = [(self.topo.node_to_node_mac(next_hop, p4switch),
                              self.topo.node_to_node_port_num(p4switch, next_hop))
                              for next_hop in next_hops]
            if ecmp_group.get(p4switch):
                if ecmp_group[p4switch].get(tuple(dst_macs_ports)):
                    ecmp_group[p4switch][tuple(dst_macs_ports)] = ecmp_group[p4switch][tuple(dst_macs_ports)] + 1
                else:
                    ecmp_group[p4switch][tuple(dst_macs_ports)] = 1
            else:
                ecmp_group[p4switch] = {}
                ecmp_group[p4switch][tuple(dst_macs_ports)] = 1

            print('Adding multipath entries')
            ss_api.table_add('ipv4_lpm', 'ecmp_group', [host_ip] ,[str(1), str(len(next_hops))])
            index = 0
            for dst_mac_port in dst_macs_ports:
                ss_api.table_add('ecmp_group_to_nhop', 'set_nhop', [str(1),str(index)] ,[dst_mac_port[0], str(dst_mac_port[1])])
                index = index + 1
            
        return None

    def add_route_via_best(self, p4switch, ss_api, neigh, path):
        host_ip, host_mac, output_iface = self.get_conn_host_infos(neigh)
        if host_ip:
            neigh_mac = self.topo.node_to_node_mac(neigh, p4switch)
            output_iface = self.topo.node_to_node_port_num(p4switch, neigh)
            print('Add route via best',host_ip, neigh_mac, output_iface)
            ss_api.table_add('ipv4_lpm', 'set_nhop', [host_ip] ,[neigh_mac, str(output_iface)])

    def add_directly_conn_host(self, p4switch, ss_api):
        host_ip, host_mac, output_iface = self.get_conn_host_infos(p4switch)
        if host_ip:
            print('Add directly connected route ',host_ip,host_mac,output_iface)
            ss_api.table_add('ipv4_lpm', 'set_nhop', [host_ip] ,[host_mac, str(output_iface)])

    def route(self):
        """implement this function"""
        ecmp_group = {}
        for p4switch, ss_api in self.controllers.items():
            for neigh in self.topo.get_p4switches():
                if p4switch == neigh:
                    # Check if we have connected hosts
                    self.add_directly_conn_host(p4switch, ss_api)
                else:
                    shortest_path = self.topo.get_shortest_paths_between_nodes(p4switch, neigh)
                    if len(shortest_path) < 2:
                        # There is only 1 path
                        self.add_route_via_best(p4switch, ss_api, neigh, shortest_path)
                    else: 
                        # multipath
                        self.add_ecmp_group(p4switch, ss_api, neigh, shortest_path, ecmp_group)


    def main(self):
        self.set_icmp_ingress_port_table()
        self.route()