class NCacheController(object): def __init__(self, sw_name): self.topo = Topology(db="./topology.db") self.sw_name = sw_name self.thrift_port = self.topo.get_thrift_port(self.sw_name) self.cpu_port = self.topo.get_cpu_port_index(self.sw_name) self.controller = SimpleSwitchAPI(self.thrift_port) self.custom_calcs = self.controller.get_custom_crc_calcs() self.sketch_register_num = len(self.custom_calcs) self.setup() def setup(self): if self.cpu_port: self.controller.mirroring_add(CONTROLLER_MIRROR_SESSION, self.cpu_port) # set a static allocation scheme for l2 forwarding where the mac address of # each host is associated with the port connecting this host to the switch def set_forwarding_table(self): for host in self.topo.get_hosts_connected_to(self.sw_name): port = self.topo.node_to_node_port_num(self.sw_name, host) host_mac = self.topo.get_host_mac(host) print str(host_mac) + str(port) self.controller.table_add("l2_forward", "set_egress_port", [str(host_mac)], [str(port)]) def main(self): self.set_forwarding_table()
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", []) def ipv4_lpm(self,sw): for host in self.topo.get_hosts_connected_to("s1"): dstip=self.topo.get_host_ip(host) shortestways=self.topo.get_shortest_paths_between_nodes(sw, host) nhopmac=self.topo.node_to_node_mac(shortestways[0][1],sw) nhopport=self.topo.node_to_node_port_num(sw, shortestways[0][1]) self.controllers[sw].table_add("ipv4_lpm", "set_nhop", [dstip+'/24'],[nhopmac, str(nhopport)]) print("From switch "+str(sw)+" to host "+str(dstip)+", next hop is "+str(shortestways[0][1])+", egress port is "+str(nhopport)+'\n') for sw2 in self.controllers.keys(): if sw==sw2 : continue shortestways=self.topo.get_shortest_paths_between_nodes(sw, sw2) nhopmac=self.topo.node_to_node_mac(shortestways[0][1],sw) nhopport=self.topo.node_to_node_port_num(sw, shortestways[0][1]) dstip="20.0."+str(sw2[1:])+".0/24" self.controllers[sw].table_add("ipv4_lpm", "set_nhop", [dstip],[nhopmac, str(nhopport)]) print("From switch "+str(sw)+" to switch "+str(sw2)+" at "+str(dstip)+", next hop is "+str(shortestways[0][1])+", egress port is "+str(nhopport)+'\n') def op(self,sw): self.controllers[sw].table_add("op","NetChain_insert",[str(3)],[]) self.controllers[sw].table_add("op","NetChain_delete",[str(4)],[]) def pkt_for_me(self,sw): self.controllers[sw].table_add("pkt_for_me", "NoAction", ["20.0."+str(sw[1:])+".0/24",'35678'],[]) def seq(self,sw): self.controllers[sw].table_add("seq","assignseq",[str(0)],[]) def read_write_trans(self,sw): self.controllers[sw].table_add("read_write_trans","NetChain_write",["20.0."+str(sw[1:])+".1",str(1)],[]) self.controllers[sw].table_add("read_write_trans","NetChain_read",["20.0."+str(sw[1:])+".1",str(2)],[]) self.controllers[sw].table_add("read_write_trans","NetChain_transfer",["20.0."+str(sw[1:])+".1",str(6)],[])
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()
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 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) #print(self.topo.node(p4switch)['interfaces_to_node']) #for iface, neigh in self.topo.node(p4switch)['interfaces_to_node'].items(): # print(self.topo.node_to_node_port_num(p4switch, neigh)) def main(self): 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() ''' 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 GenFault(object): def __init__(self, program): if program == "f": self.topo = Topology( db="../p4src_flowsize/topology.db") #set the topology elif program == "i": self.topo = Topology( db="../p4src_interval/topology.db") #set the topology self.controllers = {} #the switches self.init() def init(self): self.connect_to_switches() 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 loop(self): switches = raw_input( "type the switch's name to gen loop,seperated by ','\nmust be physically loop-able:\n" ).split(',') IPs = [] for sw_name in self.controllers.keys(): for host in self.topo.get_hosts_connected_to(sw_name): host_ip = self.topo.get_host_ip(host) + "/24" IPs.append(host_ip) for i in range(len(switches)): sw_name = switches[i] self.controllers[sw_name].table_clear("ecmp_group_to_nhop") self.controllers[sw_name].table_clear("ipv4_lpm") #next_hop=NULL if i == len(switches) - 1: next_hop = switches[0] else: next_hop = switches[i + 1] 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) #print "table_add at {}:".format(sw_name) for host_ip in IPs: self.controllers[sw_name].table_add("ipv4_lpm", "set_nhop", [str(host_ip)],\ [str(dst_sw_mac), str(sw_port)]) def blackhole(self, args): if args.sw_name == None: pass print "Not implemented yet,please specify the switch name" else: self.controllers[args.sw_name].table_clear("ecmp_group_to_nhop") self.controllers[args.sw_name].table_clear("ipv4_lpm") print args.sw_name, "has been shut down" def remove_cpu(self): # log=open("./router.log","w") # log.write(str(self.topo)) print(1) print(self.topo.get_shortest_paths_between_nodes("s5", "h2")) # print(self.topo["sw-cpu"]) # print(self.topo.network_graph["sw-cpu"]) self.topo.network_graph.remove_node("sw-cpu") # self.topo.save("../p4src_interval/topology.db") # self.topo.load("../p4src_interval/topology.db") # del self.topo #self.topo=Topology(db="../p4src_interval/topology.db") print("\n\n\n\n\n") print(2) print(self.topo.get_shortest_paths_between_nodes("h1", "h8")) # print(self.topo["sw-cpu"]) # print(self.topo.network_graph["sw-cpu"]) # log=open("./router1.log","w") # log.write(str(self.topo)) def reroute(self): #log=open("./router.log","w") #log.write(str(self.topo)) self.topo.network_graph.remove_node("sw-cpu") switch_ecmp_groups = { sw_name: {} for sw_name in self.topo.get_p4switches().keys() } for sw_name, controllers in self.controllers.items(): controllers.table_clear("ecmp_group_to_nhop") controllers.table_clear("ipv4_lpm") 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) # log.write("[1] table_add ipv4_lpm set_nhop at {} to host {} using port {}\n".format(sw_name,host,sw_port)) 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) # log.write("[2] table_add ipv4_lpm set_nhop at {} to host {} using port {} to nexthop {}\n".format(sw_name,host,sw_port,next_hop)) 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) # log.write("[3] table_add ipv4_lpm ecmp_group at {} to switch {} to paths{}\n".format(sw_name,sw_dst,paths)) 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) #log.write("[4] table_add ipv4_lpm ecmp_group at {} to switch {} to paths{}\n".format(sw_name,sw_dst,paths)) # log.write("[4] table_add ipv4_lpm ecmp_group at {} to switch {} using port {}\n".format(sw_name,sw_dst,port)) 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) # log.write("[5] table_add ipv4_lpm ecmp_group at {} to switch {} to paths{}\n".format(sw_name,sw_dst,paths)) self.controllers[sw_name].table_add( "ipv4_lpm", "ecmp_group", [str(host_ip)], [ str(new_ecmp_group_id), str(len(dst_macs_ports)) ])
class FlowtableManager(object): def __init__(self): self.topo = Topology(db='topology.db') dic = self.topo.get_p4switches() self.sw_name = [sw for sw in dic.keys()] self.controller = { sw: SimpleSwitchAPI(self.topo.get_thrift_port(sw)) for sw in self.sw_name } self.multicast_table = dict() def add_forward_table(self): host_list = [h for h in self.topo.get_hosts().keys()] for src in self.sw_name: self.controller[src].table_set_default('ingress.ipv4_c.ipv4', 'drop', []) direct_sw_list = self.topo.get_switches_connected_to(src) for sw in direct_sw_list: port = self.topo.node_to_node_port_num(src, sw) self.controller[src].table_add( 'egress.mac_c.adjust_mac', 'set_mac', [str(port)], [ str(self.topo.node_to_node_mac(src, sw)), str(self.topo.node_to_node_mac(sw, src)) ]) direct_host_list = self.topo.get_hosts_connected_to(src) for h in direct_host_list: ip = self.topo.get_host_ip(h) port = self.topo.node_to_node_port_num(src, h) self.controller[src].table_add('ingress.ipv4_c.ipv4', 'forward', [str(ip) + '/32'], [str(port)]) self.controller[src].table_add( 'egress.mac_c.adjust_mac', 'set_mac', [str(port)], [ str(self.topo.node_to_node_mac(src, h)), str(self.topo.node_to_node_mac(h, src)) ]) indirect_host_list = list( set(host_list).difference(direct_host_list)) for h in indirect_host_list: ip = self.topo.get_host_ip(h) path = self.topo.get_shortest_paths_between_nodes(src, h)[0] port = self.topo.node_to_node_port_num(src, path[1]) self.controller[src].table_add('ingress.ipv4_c.ipv4', 'forward', [str(ip) + '/32'], [str(port)]) def add_multicast_table(self): for sw in self.sw_name: self.multicast_table.update({sw: {}}) port = self.topo.get_interfaces_to_port(sw) num = len(port) - 1 if sw + '-cpu-eth0' in port.keys(): num -= 1 self.controller[sw].mc_mgrp_create('1') for i in range(int(comb(num, 2))): self.controller[sw].mc_mgrp_create(str(i + 2)) port_list = [] for i in range(num): port_list.append(str(i + 1)) self.controller[sw].mc_node_create('0', port_list) self.controller[sw].mc_node_associate('1', '0') n = 2 for i in range(num): for j in range(i + 1, num): port_list = [str(i + 1), str(j + 1)] self.controller[sw].mc_node_create(str(n - 1), port_list) self.controller[sw].mc_node_associate(str(n), str(n - 1)) self.multicast_table[sw].update({ (str(i + 1), str(j + 1)): n }) n += 1 def add_forward_entry(self, sw, ip, port): try: self.controller[sw].table_add('ingress.ipv4_c.ipv4', 'forward', [str(ip)], [str(port)]) except: print('add_forward_entry error') def add_l3_entry(self, sw, act=[], key=[]): try: self.controller[sw].table_add('ingress.ipv4_c.l3_match_to_index', 'protect', key, act) except: print('add_l3_entry error')
class NCacheController(object): def __init__(self, sw_name, vtables_num=8): self.topo = Topology(db="../p4/topology.db") self.sw_name = sw_name self.thrift_port = self.topo.get_thrift_port(self.sw_name) self.cpu_port = self.topo.get_cpu_port_index(self.sw_name) self.controller = SimpleSwitchAPI(self.thrift_port) self.custom_calcs = self.controller.get_custom_crc_calcs() self.sketch_register_num = len(self.custom_calcs) self.vtables = [] self.vtables_num = vtables_num # create a pool of ids (as much as the total amount of keys) # this pool will be used to assign index to keys which will be # used to index the cached key counter and the validity register self.ids_pool = range(0, VTABLE_ENTRIES * VTABLE_SLOT_SIZE) # array of bitmap, which marks available slots per cache line # as 0 bits and occupied slots as 1 bits self.mem_pool = [0] * VTABLE_ENTRIES # number of memory slots used (useful for lfu eviction policy) self.used_mem_slots = 0 # dictionary storing the value table index, bitmap and counter/validity # register index in the P4 switch that corresponds to each key self.key_map = {} self.setup() #self.out_of_band_test() def inform_server(self): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.connect(UNIX_CHANNEL) except socket.error as msg: #print('Error: Unable to contact server for cache operation completion') return sock.sendall(CACHE_INSERT_COMPLETE) # reports the value of counters for each cached key # (used only for debugging purposes) def report_counters(self): for key, val in self.key_map.items(): vt_idx, bitmap, key_idx = val res = self.controller.counter_read(CACHED_KEYS_COUNTER, key_idx) if res != 0: print("[COUNTER] key = " + key + " [ " + str(res.packets) + " ]") # periodically reset registers pertaining to query statistics module of the # P4 switch (count-min sketch registers, bloom filters and counters) def periodic_registers_reset(self): t = threading.Timer(STATISTICS_REFRESH_INTERVAL, self.periodic_registers_reset) t.daemon = True t.start() # before reseting registers check if the cache is utilized above a # threshold (e.g 80%) and evict keys using lfu policy if needed self.cache_lfu_eviction(threshold=0.8, sampling=0.2, to_remove=0.5) # reset bloom filter related registers for i in range(BLOOMF_REGISTERS_NUM): self.controller.register_reset(BLOOMF_REG_PREFIX + str(i + 1)) # reset count min sketch related registers for i in range(SKETCH_REGISTERS_NUM): self.controller.register_reset(SKETCH_REG_PREFIX + str(i + 1)) # reset counter register storing the query frequency of each cached item self.controller.counter_reset(CACHED_KEYS_COUNTER) print("[INFO]: Reset query statistics registers.") # the controller periodically checks if the memory used has exceeded a given threshold # (e.g 80 %) and if that is the case then it evicts keys according to an approximated # LFU policy inspired by REDIS (https://redis.io/topics/lru-cache)) def cache_lfu_eviction(self, threshold=0.8, sampling=0.2, to_remove=0.5): # if the threshold has not been surpassed then nothing to do if self.used_mem_slots <= (threshold * len(self.mem_pool) * VTABLE_SLOT_SIZE): return n_samples = int(sampling * len(self.key_map.items())) samples = random.sample(self.key_map.items(), n_samples) # read the counter for each sample and store them in an array evict_list = [] for key, val in samples: x, y, cnt_idx = self.key_map[key] counter = self.controller.counter_read(CACHED_KEYS_COUNTER, cnt_idx).packets evict_list.append((key, counter)) # sort the array and pick the smallest K-th counters and evict their keys # (this could be achieved more optimally by using quickselect) import operator evict_list.sort(key=operator.itemgetter(1)) for i in range(int(to_remove * n_samples)): curr = evict_list[i] self.evict(curr[0]) def setup(self): if self.cpu_port: self.controller.mirroring_add(CONTROLLER_MIRROR_SESSION, self.cpu_port) # create custom hash functions for count min sketch and bloom filters self.set_crc_custom_hashes() self.create_hashes() # set a daemon to periodically reset registers self.periodic_registers_reset() # spawn new thread to serve incoming udp connections # (i.e hot reports from the switch) #udp_t = threading.Thread(target=self.hot_reports_loop) #udp_t.start() def set_crc_custom_hashes(self): i = 0 for custom_crc32, width in sorted(self.custom_calcs.items()): self.controller.set_crc32_parameters(custom_crc32, crc32_polinomials[i], 0xffffffff, 0xffffffff, True, True) i += 1 def create_hashes(self): self.hashes = [] for i in range(self.sketch_register_num): self.hashes.append( Crc(32, crc32_polinomials[i], True, 0xffffffff, True, 0xffffffff)) # set a static allocation scheme for l2 forwarding where the mac address of # each host is associated with the port connecting this host to the switch def set_forwarding_table(self): for host in self.topo.get_hosts_connected_to(self.sw_name): port = self.topo.node_to_node_port_num(self.sw_name, host) host_mac = self.topo.get_host_mac(host) self.controller.table_add("l2_forward", "set_egress_port", [str(host_mac)], [str(port)]) def set_value_tables(self): for i in range(self.vtables_num): self.controller.table_add("vtable_" + str(i), "process_array_" + str(i), ['1'], []) # this function manages the mapping between between slots in register arrays # and the cached items by implementing the First Fit algorithm described in # Memory Management section of 4.4.2 (netcache paper) def first_fit(self, key, value_size): n_slots = (value_size / (VTABLE_SLOT_SIZE + 1)) + 1 if value_size <= 0: return None if key in self.key_map: return None for idx in range(len(self.mem_pool)): old_bitmap = self.mem_pool[idx] n_zeros = 8 - bin(old_bitmap).count("1") if n_zeros >= n_slots: cnt = 0 bitmap = 0 for i in reversed(range(8)): if cnt >= n_slots: break if not self.bit_is_set(old_bitmap, i): bitmap = bitmap | (1 << i) cnt += 1 # mark last n_slots 0 bits as 1 bits because we assigned # them to the new key and they are now allocated self.mem_pool[idx] = old_bitmap | bitmap self.used_mem_slots += bin(bitmap).count("1") return (idx, bitmap) return None # converts a list of 1s and 0s represented as strings and converts it # to a bitmap using bitwise operations (this intermediate representation # of a list of 1s and 0s is used to avoid low level bitwise logic inside # core implementation logic) def convert_to_bitmap(self, strlist, bitmap_len): bitmap = 0 # supports only bitmaps with multiple of 8 bits size if bitmap_len % 8 != 0: return bitmap for i in strlist: bitmap = bitmap << 1 bitmap = bitmap | int(i) return bitmap # this function checks whether the k-th bit of a given number is set def bit_is_set(self, n, k): if n & (1 << k): return True else: return False # given a key and its associated value, we update the lookup table on # the switch and we also update the value registers with the value # given as argument (stored in multiple slots) def insert(self, key, value, cont=True): # find where to put the value for given key mem_info = self.first_fit(key, len(value)) # if key already exists or not space available then stop if mem_info == None: return vt_index, bitmap = mem_info # keep track of number of bytes of the value written so far cnt = 0 # store the value of the key in the vtables of the switch while # incrementally storing a part of the value at each value table # if the correspoding bit of the bitmap is set for i in range(self.vtables_num): if self.bit_is_set(bitmap, self.vtables_num - i - 1): partial_val = value[cnt:cnt + VTABLE_SLOT_SIZE] self.controller.register_write(VTABLE_NAME_PREFIX + str(i), vt_index, self.str_to_int(partial_val)) cnt += VTABLE_SLOT_SIZE # allocate an id from the pool to index the counter and validity register # (we take the last element of list because in python list is implemented # to optimize for inserting and removing elements from the end of the list) key_index = self.ids_pool.pop() # add the new key to the cache lookup table of the p4 switch self.controller.table_add( NETCACHE_LOOKUP_TABLE, "set_lookup_metadata", [str(self.str_to_int(key))], [str(bitmap), str(vt_index), str(key_index)]) # mark cache entry for this key as valid self.controller.register_write("cache_status", key_index, 1) self.key_map[key] = vt_index, bitmap, key_index # inform the server about the successful cache insertion if cont: self.inform_server() print("Inserted key-value pair to cache: (" + key + "," + value + ")") # converts a string to a bytes representation and afterwards returns # its integer representation of width specified by argument int_width # (seems hacky due to restriction to use python2.7) def str_to_int(self, x, int_width=VTABLE_SLOT_SIZE): if len(x) > int_width: print "Error: Overflow while converting string to int" # add padding with 0x00 if input string size less than int_width bytearr = bytearray(int_width - len(x)) bytearr.extend(x.encode('utf-8')) return struct.unpack(">Q", bytearr)[0] # given an arbitrary sized integer, the max width (in bits) of the integer # it returns the string representation of the number (also stripping it of # any '0x00' characters) (network byte order is assumed) def int_to_packed(self, int_val, max_width=128, word_size=32): num_words = max_width / word_size words = self.int_to_words(int_val, num_words, word_size) fmt = '>%dI' % (num_words) return struct.pack(fmt, *words).strip('\x00') # split up an arbitrary sized integer to words (needed to hack # around struct.pack limitation to convert to byte any integer # greater than 8 bytes) def int_to_words(self, int_val, num_words, word_size): max_int = 2**(word_size * num_words) - 1 max_word_size = 2**word_size - 1 words = [] for _ in range(num_words): word = int_val & max_word_size words.append(int(word)) int_val >>= word_size words.reverse() return words # update the value of the given key with the new value given as argument # (by allowing updates also to be done by the controller, the client is # also able to update keys with values bigger than the previous one) # in netcache paper this restriction is not resolved def update(self, key, value): # if key is not in cache then nothing to do if key not in self.key_map: return # update key-value pair by removing old pair and inserting new one self.evict(key) self.insert(key, value) # evict given key from the cache by deleting its associated entries in # action tables of the switch, by deallocating its memory space and by # marking the cache entry as valid once the deletion is completed def evict(self, key): if key not in self.key_map: return # delete entry from the lookup_table entry_handle = self.controller.get_handle_from_match( NETCACHE_LOOKUP_TABLE, [ str(self.str_to_int(key)), ]) if entry_handle is not None: self.controller.table_delete(NETCACHE_LOOKUP_TABLE, entry_handle) # delete mapping of key from controller's dictionary vt_idx, bitmap, key_idx = self.key_map[key] del self.key_map[key] # deallocate space from memory pool self.mem_pool[vt_idx] = self.mem_pool[vt_idx] ^ bitmap self.used_mem_slots = self.used_mem_slots - bin(bitmap).count("1") # free the id used to index the validity/counter register and append # it back to the id pool of the controller self.ids_pool.append(key_idx) # mark cache entry as valid again (should be the last thing to do) self.controller.register_write("cache_status", key_idx, 1) # used for testing purposes and static population of cache def dummy_populate_vtables(self): test_values_l = [ "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "hita", "theta", "yiota", "kappa", "lambda", "meta" ] test_keys_l = [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve" ] cnt = 0 for i in range(11): self.insert(test_keys_l[i], test_values_l[i], False) # handling reports from the switch corresponding to hot keys, updates to # key-value pairs or deletions - this function receives a packet, extracts # its netcache header and manipulates cache based on the operation field # of the netcache header (callback function) def recv_switch_updates(self, pkt): print("Received message from switch") # extract netcache header information if pkt.haslayer(UDP): ncache_header = NetcacheHeader(pkt[UDP].payload) elif pkt.haslayer(TCP): ncache_header = NetcacheHeader(pkt[TCP].payload) key = self.int_to_packed(ncache_header.key, max_width=128) value = self.int_to_packed(ncache_header.value, max_width=1024) op = ncache_header.op if op == NETCACHE_HOT_READ_QUERY: print("Received hot report for key = " + key) # if the netcache header has null value or if the "hot key" # reported doesn't exist then do not update cache if ncache_header.op == NETCACHE_KEY_NOT_FOUND: return self.insert(key, value) elif op == NETCACHE_DELETE_COMPLETE: print("Received query to delete key = " + key) self.evict(key) elif op == NETCACHE_UPDATE_COMPLETE: print("Received query to update key = " + key) self.update(key, value) else: print("Error: unrecognized operation field of netcache header") # sniff infinitely the interface connected to the P4 switch and when a valid netcache # packet is captured, handle the packet via a callback to recv_switch_updates function def hot_reports_loop(self): cpu_port_intf = str(self.topo.get_cpu_port_intf(self.sw_name)) sniff(iface=cpu_port_intf, prn=self.recv_switch_updates, filter="port 50000") def main(self): self.set_forwarding_table() self.set_value_tables() self.dummy_populate_vtables() self.hot_reports_loop()
class Controller(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", []) def install_rules(self): 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): 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)]) def main(self): self.install_rules()
class PacketLossController(object): def __init__(self, num_hashes=3): self.topo = Topology(db="topology.db") self.controllers = {} self.num_hashes = num_hashes # gets a controller API for each switch: {"s1": controller, "s2": controller...} self.connect_to_switches() # creates the 3 hashes that will use the p4 switch self.create_local_hashes() # initializes the switch # resets all registers, configures the 3 x 2 hash functions # reads the registers # populates the tables and mirroring id self.init() self.registers = {} def init(self): self.reset_all_registers() self.set_crc_custom_hashes_all() self.read_registers() self.configure_switches() 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 configure_switches(self): for sw, controller in self.controllers.items(): # ads cpu port controller.mirroring_add(100, 3) # set the basic forwarding rules controller.table_add("forwarding", "set_egress_port", ["1"], ["2"]) controller.table_add("forwarding", "set_egress_port", ["2"], ["1"]) # set the remove header rules when there is a host in a port direct_hosts = self.topo.get_hosts_connected_to(sw) for host in direct_hosts: port = self.topo.node_to_node_port_num(sw,host) controller.table_add("remove_loss_header", "remove_header", [str(port)], []) def set_crc_custom_hashes_all(self): for sw_name in self.controllers: self.set_crc_custom_hashes(sw_name) def set_crc_custom_hashes(self, sw_name): custom_calcs = sorted(self.controllers[sw_name].get_custom_crc_calcs().items()) i = 0 # Set the first 3 hashes for the um for custom_crc32, width in custom_calcs[:self.num_hashes]: self.controllers[sw_name].set_crc32_parameters(custom_crc32, crc32_polinomials[i], 0xffffffff, 0xffffffff, True, True) i += 1 i = 0 # Sets the 3 hashes for the dm, they have to be the same, thus we use the same index for custom_crc32, width in custom_calcs[self.num_hashes:]: self.controllers[sw_name].set_crc32_parameters(custom_crc32, crc32_polinomials[i], 0xffffffff, 0xffffffff, True, True) i += 1 def create_local_hashes(self): self.hashes = [] for i in range(self.num_hashes): self.hashes.append(Crc(32, crc32_polinomials[i], True, 0xffffffff, True, 0xffffffff)) def reset_all_registers(self): for sw, controller in self.controllers.items(): for register in controller.get_register_arrays(): controller.register_reset(register) def reset_registers(self, sw, stream, port, batch_id): start = (batch_id * REGISTER_BATCH_SIZE) + ((port-1) * REGISTER_PORT_SIZE) end = start + REGISTER_PORT_SIZE for register in self.controllers[sw].get_register_arrays(): if stream in register: self.controllers[sw].register_write(register, [start, end], 0) def flow_to_bytestream(self, flow): # flow fields are: srcip , dstip, srcport, dstport, protocol, ip id return socket.inet_aton(flow[0]) + socket.inet_aton(flow[1]) + struct.pack(">HHBH",flow[2], flow[3], flow[4], flow[5]) def read_registers(self): # reads all the registers self.registers = {sw: {} for sw in self.controllers.keys()} for sw, controller in self.controllers.items(): for register in controller.get_register_arrays(): self.registers[sw][register] = (controller.register_read(register)) def extract_register_information(self, sw, stream, port, batch_id): # reads the region of a um or dm register: uses port, batch id. start = (batch_id * REGISTER_BATCH_SIZE) + ((port-1) * REGISTER_PORT_SIZE) end = start + REGISTER_PORT_SIZE res = {} for name, values in self.registers[sw].items(): if stream in name: res[name] = values[start:end] return res def decode_meter_pair(self, um_registers, dm_registers): # xor the registers counters = [x - y for x, y in zip(um_registers['MyIngress.um_counter'], dm_registers['MyIngress.dm_counter'])] ip_src = [x ^ y for x, y in zip(um_registers['MyIngress.um_ip_src'], dm_registers['MyIngress.dm_ip_src'])] ip_dst = [x ^ y for x, y in zip(um_registers['MyIngress.um_ip_dst'], dm_registers['MyIngress.dm_ip_dst'])] ports_proto_id = [x ^ y for x, y in zip(um_registers['MyIngress.um_ports_proto_id'], dm_registers['MyIngress.dm_ports_proto_id'])] dropped_packets = set() while 1 in counters: i = counters.index(1) tmp_src = ip_src[i] tmp_dst = ip_dst[i] src = socket.inet_ntoa(struct.pack("!I", tmp_src)) dst = socket.inet_ntoa(struct.pack("!I", tmp_dst)) misc = ports_proto_id[i] id = misc & 0xffff proto = misc >> 16 & 0xff dst_port = misc >> 24 & 0xffff src_port = misc >> 40 & 0xffff flow = (src, dst, src_port, dst_port, proto, id) # get the three indexes flow_stream = self.flow_to_bytestream(flow) index0 = self.hashes[0].bit_by_bit_fast(flow_stream) % REGISTER_PORT_SIZE index1 = self.hashes[1].bit_by_bit_fast(flow_stream) % REGISTER_PORT_SIZE index2 = self.hashes[2].bit_by_bit_fast(flow_stream) % REGISTER_PORT_SIZE # clean this entries everywhere an continue counters[index0] -= 1 counters[index1] -= 1 counters[index2] -= 1 ip_src[index0] ^= tmp_src ip_src[index1] ^= tmp_src ip_src[index2] ^= tmp_src ip_dst[index0] ^= tmp_dst ip_dst[index1] ^= tmp_dst ip_dst[index2] ^= tmp_dst ports_proto_id[index0] ^= misc ports_proto_id[index1] ^= misc ports_proto_id[index2] ^= misc # if there is a bad sync we skip this round # do not ask this in the readme # mainly the problem is the amount of buffer the switch allows if any(x < 0 for x in counters): return dropped_packets dropped_packets.add(flow) return dropped_packets def verify_link(self, sw1, sw2, batch_id): sw1_to_sw2_interface = self.topo.node_to_node_port_num(sw1, sw2) sw2_to_sw1_interface = self.topo.node_to_node_port_num(sw2, sw1) sw1_um = self.extract_register_information(sw1, 'um', sw1_to_sw2_interface, batch_id) sw2_dm = self.extract_register_information(sw2, 'dm', sw2_to_sw1_interface, batch_id) dropped_packets = self.decode_meter_pair(sw1_um, sw2_dm) # clean registers self.reset_registers(sw1, 'um', sw1_to_sw2_interface, batch_id) self.reset_registers(sw2, 'dm', sw2_to_sw1_interface, batch_id) # report if dropped_packets: print "Packets dropped: {} at link {}->{}:".format(len(dropped_packets), sw1, sw2) print "Details:" for packet in dropped_packets: print packet def check_sw_links(self, sw, batch_id): # just in case for the delay # increase decrease depending on the batch timeing time.sleep(0.25) # read all registers since its a small topo self.read_registers() # Process the right links and clean registers neighboring_p4_switches = [x for x in self.topo.get_neighbors(sw) if x in self.topo.get_p4switches()] for neighboring_switch in neighboring_p4_switches: self.verify_link(sw, neighboring_switch, batch_id) # When a batch_id changes the controller gets triggered def recv_msg_cpu(self, pkt): interface = pkt.sniffed_on print interface switch_name = interface.split("-")[0] packet = Ether(str(pkt)) if packet.type == 0x1234: loss_header = LossHeader(packet.payload) batch_id = loss_header.batch_id >> 7 print switch_name, batch_id self.check_sw_links(switch_name, batch_id) def run_cpu_port_loop(self): cpu_interfaces = [str(self.topo.get_cpu_port_intf(sw_name).replace("eth0", "eth1")) for sw_name in self.controllers] sniff(iface=cpu_interfaces, prn=self.recv_msg_cpu)
class RSVPController(object): def __init__(self): """Initializes the topology and data structures """ if not os.path.exists("topology.db"): print("Could not find topology object!!!\n") raise (Exception) self.topo = Topology(db="topology.db") self.controllers = {} self.init() # sorted by timeouts self.current_reservations = {} # initial link capacity self.links_capacity = self.build_links_capacity() self.update_lock = threading.Lock() self.timeout_thread = threading.Thread( target=self.reservations_timeout_thread, args=(1, )) self.timeout_thread.daemon = True self.timeout_thread.start() def init(self): """Connects to switches and resets. """ self.connect_to_switches() self.reset_states() def reset_states(self): """Resets registers, tables, etc. """ [controller.reset_state() for controller in self.controllers.values()] def connect_to_switches(self): """Connects to all the switches in the topology and saves them in self.controllers. """ for p4switch in self.topo.get_p4switches(): thrift_port = self.topo.get_thrift_port(p4switch) self.controllers[p4switch] = SimpleSwitchAPI(thrift_port) def build_links_capacity(self): """Builds link capacities dictionary Returns: dict: {edge: bw} """ links_capacity = {} # Iterates all the edges in the topology formed by switches for src, dst in self.topo.network_graph.keep_only_p4switches().edges: bw = self.topo.network_graph.edges[(src, dst)]['bw'] # add both directions links_capacity[(src, dst)] = bw links_capacity[(dst, src)] = bw return links_capacity def reservations_timeout_thread(self, refresh_rate=1): """Every refresh_rate checks all the reservations. If any times out tries to delete it. Args: refresh_rate (int, optional): Refresh rate. Defaults to 1. """ while True: # sleeps time.sleep(refresh_rate) # locks the self.current_reservations data structure. This is done # because the CLI can also access the reservations. with self.update_lock: to_remove = [] # iterates all the reservations and updates its timeouts # if timeout is reached we delete it for reservation, data in self.current_reservations.items(): data['timeout'] -= refresh_rate # has expired? if data['timeout'] <= 0: to_remove.append(reservation) # removes all the reservations that expired for reservation in to_remove: self.del_reservation(*reservation) def set_mpls_tbl_labels(self): """We set all the table defaults to reach all the hosts/networks in the network """ # for all switches for sw_name, controller in self.controllers.items(): # get all direct hosts and add direct entry 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) host_mac = self.topo.get_host_mac(host) # adds direct forwarding rule controller.table_add( "FEC_tbl", "ipv4_forward", ["0.0.0.0/0", str(host_ip)], [str(host_mac), str(sw_port)]) for switch in self.topo.get_switches_connected_to(sw_name): sw_port = self.topo.node_to_node_port_num(sw_name, switch) # reverse port mac other_switch_mac = self.topo.node_to_node_mac(switch, sw_name) # we add a normal rule and a penultimate one controller.table_add("mpls_tbl", "mpls_forward", [str(sw_port), '0'], [str(other_switch_mac), str(sw_port)]) controller.table_add("mpls_tbl", "penultimate", [str(sw_port), '1'], [str(other_switch_mac), str(sw_port)]) def build_mpls_path(self, switches_path): """Using a path of switches builds the mpls path. In our simplification labels are port indexes. Args: switches_path (list): path of switches to allocate Returns: list: label path """ # label path label_path = [] # iterate over all pair of switches in the path for current_node, next_node in zip(switches_path, switches_path[1:]): # we get sw1->sw2 port number from topo object label = self.topo.node_to_node_port_num(current_node, next_node) label_path.append(label) return label_path def get_sorted_paths(self, src, dst): """Gets all paths between src, dst sorted by length. This function uses the internal networkx API. Args: src (str): src name dst (str): dst name Returns: list: paths between src and dst """ paths = self.topo.get_all_paths_between_nodes(src, dst) # trim src and dst paths = [x[1:-1] for x in paths] return paths def get_shortest_path(self, src, dst): """Computes shortest path. Simple function used to test the system by always allocating the shortest path. Args: src (str): src name dst (str): dst name Returns: list: shortest path between src,dst """ return self.get_sorted_paths(src, dst)[0] def check_if_reservation_fits(self, path, bw): """Checks if a the candidate reservation fits in the current state of the network. Using the path of switches, checks if all the edges (links) have enough space. Otherwise, returns False. Args: path (list): list of switches bw (float): requested bandwidth in mbps Returns: bool: true if allocation can be performed on path """ # iterates over all pairs of switches (edges) for link in zip(path, path[1:]): # checks if there is enough capacity if (self.links_capacity[link] - bw) < 0: return False return True def add_link_capacity(self, path, bw): """Adds bw capacity to a all the edges along path. This function is used when an allocation is removed. Args: path (list): list of switches bw (float): requested bandwidth in mbps """ # iterates over all pairs of switches (edges) for link in zip(path, path[1:]): # adds capacity self.links_capacity[link] += bw def sub_link_capacity(self, path, bw): """subtracts bw capacity to a all the edges along path. This function is used when an allocation is added. Args: path (list): list of switches bw (float): requested bandwidth in mbps """ # iterates over all pairs of switches (edges) for link in zip(path, path[1:]): # subtracts capacity self.links_capacity[link] -= bw def get_available_path(self, src, dst, bw): """Checks all paths from src to dst and picks the shortest path that can allocate bw. Args: src (str): src name dst (str): dst name bw (float): requested bandwidth in mbps Returns: list/bool: best path/ False if none """ # get all paths sorted from shorter to longer paths = self.get_sorted_paths(src, dst) for path in paths: # checks if the path has capacity if self.check_if_reservation_fits(path, bw): return path return False def get_meter_rates_from_bw(self, bw, burst_size=700000): """Returns the CIR and PIR rates and bursts to configure meters at bw. Args: bw (float): desired bandwdith in mbps burst_size (int, optional): Max capacity of the meter buckets. Defaults to 50000. Returns: list: [(rate1, burst1), (rate2, burst2)] """ rates = [] rates.append((0.125 * bw, burst_size)) rates.append((0.125 * bw, burst_size)) return rates def set_direct_meter_bandwidth(self, sw_name, meter_name, handle, bw): """Sets a meter entry (using a table handle) to color packets using bw mbps Args: sw_name (str): switch name meter_name (str): meter name handle (int): entry handle bw (float): desired bandwidth to rate limit """ rates = self.get_meter_rates_from_bw(bw) self.controllers[sw_name].meter_set_rates(meter_name, handle, rates) def _add_reservation(self, src, dst, duration, bandwidth, priority, path, update): """Adds or updates a single reservation Args: src (str): src name dst (str): dst name duration (float): reservation timeout bandwidth (float): requested bandwidth in mbps priority (int): reservation priority path (list): switch path were to allocate the reservation update (bool): update flag """ # We build the label path. For that we use self.build_mpls_path and # reverse the returned labels, since our rsvp.p4 will push them in # reverse order. label_path = [str(x) for x in self.build_mpls_path(path)[::-1]] # Get required info to add a table rule # get ingress switch as the first node in the path src_gw = path[0] # compute the action name using the length of the labels path action = "mpls_ingress_{}_hop".format(len(label_path)) # src lpm address src_ip = str(self.topo.get_host_ip(src) + "/32") # dst exact address dst_ip = str(self.topo.get_host_ip(dst)) # match list match = [src_ip, dst_ip] # if we have a label path if len(label_path) != 0: # If the entry is new we simply add it if not update: entry_handle = self.controllers[src_gw].table_add( "FEC_tbl", action, match, label_path) self.set_direct_meter_bandwidth(src_gw, "rsvp_meter", entry_handle, bandwidth) # if the entry is being updated we modify if using its handle else: entry = self.current_reservations.get((src, dst), None) entry_handle = self.controllers[src_gw].table_modify( "FEC_tbl", action, entry['handle'], label_path) self.set_direct_meter_bandwidth(src_gw, "rsvp_meter", entry_handle, bandwidth) # udpates controllers link and reservation structures if rules were added succesfully if entry_handle: self.sub_link_capacity(path, bandwidth) self.current_reservations[(src, dst)] = { "timeout": (duration), "bw": (bandwidth), "priority": (priority), 'handle': entry_handle, 'path': path } print("Successful reservation({}->{}): path: {}".format( src, dst, "->".join(path))) else: print("\033[91mFailed reservation({}->{}): path: {}\033[0m". format(src, dst, "->".join(path))) else: print("Warning: Hosts are connected to the same switch!") def add_reservation(self, src, dst, duration, bandwidth, priority): """Adds a new reservation taking into account the priority. This addition can potentially move or delete other allocations. Args: src (str): src name dst (str): dst name duration (float): reservation timeout bandwidth (float): requested bandwidth in mbps priority (int): reservation priority """ # locks the self.current_reservations data structure. This is done # because there is a thread that could access it concurrently. with self.update_lock: # if reservation exists, we allocate it again, by just updating the entry # for that we set the FLAG UPDATE_ENTRY and restore its link capacity # such the new re-allocation with a possible new bw/prioirty can be done # taking new capacities into account. UPDATE_ENTRY = False if self.current_reservations.get((src, dst), None): data = self.current_reservations[(src, dst)] path = data['path'] bw = data['bw'] # updates link capacities self.add_link_capacity(path, bw) UPDATE_ENTRY = True # finds the best (if exists) path to allocate the requestes reservation path = self.get_available_path(src, dst, bandwidth) if path: # add or update the reservation self._add_reservation(src, dst, duration, bandwidth, priority, path, UPDATE_ENTRY) # Cant be allocated! However, it might be possible to re-allocate things else: # check if the flow could be placed removing lower priorities previous_links_capacities = self.links_capacity.copy() for reservation, data in self.current_reservations.items(): # make sure we do not remove ourselves # again in case this is a modification if reservation == (src, dst): continue if data['priority'] < priority: self.add_link_capacity(data['path'], data['bw']) # check if it fits in a newtwork without lower priority flows path = self.get_available_path(src, dst, bandwidth) # we rebalance lower priority reservations if possible if path: # adds main new allocation self._add_reservation(src, dst, duration, bandwidth, priority, path, UPDATE_ENTRY) # re-allocate everything if possible for reservation, data in sorted( self.current_reservations.items(), key=lambda x: x[1]['priority'], reverse=True): if data['priority'] < priority: src, dst = reservation[0], reservation[1] path = self.get_available_path( src, dst, data['bw']) if path: # add or update the reservation self._add_reservation(src, dst, data['timeout'], data['bw'], data['priority'], path, True) else: # delete it data = self.current_reservations[(src, dst)] path = data['path'] bw = data['bw'] self.sub_link_capacity(path, bw) print( "\033[91mDeleting allocation {}->{} due to a higher priority allocation!\033[0m" .format(src, dst)) self.del_reservation(src, dst) else: # restore capacities self.links_capacity = previous_links_capacities # if we failed and it was an entry to be updated we remove it if UPDATE_ENTRY: data = self.current_reservations[(src, dst)] path = data['path'] bw = data['bw'] self.sub_link_capacity(path, bw) print("Deleting new allocation. Does not fit anymore!") self.del_reservation(src, dst) print( "\033[91mRESERVATION FAILURE: no bandwidth available!\033[0m" ) def del_reservation(self, src, dst): """Deletes a reservation between src and dst, if exists. To delete the reservation the self.current_reservations data structure is used to retrieve all the needed information. After deleting the reservation from the ingress switch, path capacities are updated. Args: src (str): src name dst (str): dst name """ # checks if there is an allocation between src->dst entry = self.current_reservations.get((src, dst), None) if entry: # gets handle to delete entry entry_handle = entry['handle'] # gets src ingress switch sw_gw = self.topo.get_host_gateway_name(src) # removes table entry using the handle self.controllers[sw_gw].table_delete("FEC_tbl", entry_handle, True) # updates links capacity self.add_link_capacity(entry['path'], entry['bw']) # removes the reservation from the controllers memory del (self.current_reservations[(src, dst)]) print( "\nRSVP Deleted/Expired Reservation({}->{}): path: {}".format( src, dst, "->".join(entry['path']))) else: print("No entry for {} -> {}".format(src, dst)) def del_all_reservations(self): """Deletes all the current reservations """ # locks the self.current_reservations data structure. This is done # because there is a thread that could access it concurrently. with self.update_lock: # makes a copy of all the reservation pairs reservation_keys = self.current_reservations.keys() for src, dst in reservation_keys: self.del_reservation(src, dst)
class RoutingController(object): def __init__(self): self.topo = Topology(db="./topology.db") #set the topology self.controllers = {} #the switches self.custom_calcs={} self.register_num={} self.registers={} self.init() def init(self): self.connect_to_switches() self.reset_states() self.set_table_defaults() self.set_custom_calcs() self.reset_all_registers() self.set_crc_custom_hashes() def connect_to_switches(self): for p4switch in self.topo.get_p4switches():# topology line 632 thrift_port = self.topo.get_thrift_port(p4switch) self.controllers[p4switch] = SimpleSwitchAPI(thrift_port) def reset_states(self): [controllers.reset_state() for controllers in self.controllers.values()] def set_table_defaults(self): for controllers in self.controllers.values(): controllers.table_set_default("ipv4_lpm", "drop", []) controllers.table_set_default("ecmp_group_to_nhop", "drop", []) def set_custom_calcs(self): for p4switch in self.topo.get_p4switches(): self.custom_calcs[p4switch]=self.controllers[p4switch].get_custom_crc_calcs() self.register_num[p4switch] =len(self.custom_calcs[p4switch]) def reset_all_registers(self): for sw, controller in self.controllers.items(): for register in controller.get_register_arrays(): controller.register_reset(register) def set_crc_custom_hashes(self): for sw_name in self.controllers.keys(): i = 0 for custom_crc32, width in sorted(self.custom_calcs[sw_name].items()): self.controllers[sw_name].set_crc32_parameters(custom_crc32, crc32_polinomials[i], 0xffffffff, 0xffffffff, True, True) i+=1 def route(self): switch_ecmp_groups = {sw_name:{} for sw_name in self.topo.get_p4switches().keys()} # self.topo.network_graph.remove_node("sw-cpu") for sw_name, controllers 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.route() # for switch_id, controller in enumerate(self.controllers.values()): # controller.register_write("switch_id", 0, switch_id) # controller.register_write("swap_control", 0, 0) # controller.register_write("sketch_fg", 0, 0) # controller.register_write("previous_ingress_timestamp", 0, 0) for switch_id, switch_name in enumerate(self.controllers.keys()): print "{} {}".format(switch_id, switch_name)