def shortest_paths_to_switches (self): # tell all switches what their next hop is to all other switches (for shortest paths) all_paths = nx.shortest_path(self.graph) for src, paths in all_paths.items(): if 'dpid' not in self.graph.node[src]: continue src_dpid = self.graph.node[src]['dpid'] if core.openflow.getConnection(src_dpid): core.openflow.sendToDPID(src_dpid, nx_flow_mod_table_id()) # Enables multiple tables data = [] for dst, path in paths.items(): if dst == src: continue if 'dpid' not in self.graph.node[dst]: continue dst_dpid = self.graph.node[dst]['dpid'] #self.log.info(str(src_dpid) + ' --> ' + str(dst_dpid) + ' : ' + str(path)) next_hop = path[1] shortest_path_port = self.graph[src][next_hop]['ports'][src] fm = ofp_flow_mod_table_id( table_id = 0, command = of01.OFPFC_MODIFY, match = of01.ofp_match(dl_vlan=dst_dpid), actions = [ofp_action_output(port=shortest_path_port)]) data.append(fm.pack()) core.openflow.sendToDPID(src_dpid, b''.join(data))
def broadcast_paths (self): # tell all switches what ports to send out broadcast packets self.MST = nx.minimum_spanning_tree(self.graph) switches = {} for edge in self.MST.edges(data=True): #self.log.info(edge) src = edge[0] dst = edge[1] if src in edge[2]['ports']: src_outport = edge[2]['ports'][src] if src in switches: switches[src].append(src_outport) else: switches[src] = [src_outport] if dst in edge[2]['ports']: dst_outport = edge[2]['ports'][dst] if dst in switches: switches[dst].append(dst_outport) else: switches[dst] = [dst_outport] for switch, ports in switches.items(): if 'dpid' not in self.graph.node[switch]: continue switch_dpid = self.graph.node[switch]['dpid'] core.openflow.sendToDPID(switch_dpid, nx_flow_mod_table_id()) out_actions = [ofp_action_output(port=p) for p in ports] fm = ofp_flow_mod_table_id( table_id = 0, command = of01.OFPFC_MODIFY, match = of01.ofp_match(dl_dst=ETHER_BROADCAST), actions = out_actions) core.openflow.sendToDPID(switch_dpid, fm.pack())
def _add_arp_entry(self, ip_or_arp, eth=None): """ Creates an entry in the switch ARP table You can either pass an ARP packet or an IP and Ethernet address """ if not self._conn: return if eth is None: assert isinstance(ip_or_arp, pkt.arp) ip = ip_or_arp.protosrc eth = ip_or_arp.hwsrc else: ip = ip_or_arp self.log.debug("Populating ARP table with %s -> %s", ip, eth) fm = ovs.ofp_flow_mod_table_id() fm.xid = 0 fm.table_id = ARP_TABLE fm.idle_timeout = ARP_IDLE_TIMEOUT fm.hard_timeout = ARP_HARD_TIMEOUT fm.match.dl_type = pkt.ethernet.IP_TYPE fm.match.nw_dst = ip fm.actions.append(of.ofp_action_dl_addr.set_dst(eth)) fm.actions.append( ovs.nx_reg_move(src=DST_IP_REGISTER, dst=ovs.NXM_OF_IP_DST)) fm.actions.append(ovs.nx_output_reg(reg=OUT_PORT_REGISTER)) self._conn.send(fm)
def _clear_table(self, tid): if not self._conn: return self._invalidate() fm = ovs.ofp_flow_mod_table_id() fm.command = of.OFPFC_DELETE fm.table_id = tid self._conn.send(fm)
def _init_arp_table(self): # ARP_TABLE default entry fm = ovs.ofp_flow_mod_table_id() fm.table_id = ARP_TABLE fm.priority = 0 fm.cookie = ARP_TABLE_COOKIE fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) self._conn.send(fm)
def set_hosts (self, host_data): """ Receive list of hosts This gets called with a list of dictionaries that each contain information about a host. Each time this is called, you get a complete list of all current hosts. Each entry looks something like this: {"ether" : "01:02:03:04:05:06", "ip" : "1.2.3.4", "attached_switch" : dpid, "attached_port" : portno}, In a datacenter, you might get this kind of information from a Cloud Management System. In our case, garnet's sync_hosts() sends us the list of Host entities in the "emulated" network garnet is managing. We receive it via the POX Messenger component and the messenger bot above. """ self.last_host_data = host_data for host in host_data: self.log.info("Got host: %s", " ".join("%s=%s" % kv for kv in sorted(host.items()))) host_e = str(host['ether']) switch_dpid = host['attached_switch'] switch_port = host['attached_port'] switch_name = self.graph.names[switch_dpid] self.hosts[host_e] = switch_dpid self.edge[switch_dpid] = switch_port if host_e in self.graph: self.graph.remove_node(host_e) # alter table info on attached switch # add host to networkX graph attached_switch = self.graph.names[switch_dpid] self.graph.add_edge(host_e, attached_switch) self.graph.add_edge(attached_switch, host_e) port_dict = {'ports': {attached_switch: switch_port}} self.graph.edge[host_e][attached_switch] = port_dict self.graph.edge[attached_switch][host_e] = port_dict core.openflow.sendToDPID(switch_dpid, nx_flow_mod_table_id()) # Enables multiple tables data = [] # construct command to remove VLAN and output to host fm = ofp_flow_mod_table_id( table_id = 0, match = of01.ofp_match(dl_dst=EthAddr(host_e)), command = of01.OFPFC_MODIFY, actions = [ofp_action_strip_vlan(), ofp_action_output(port=switch_port)]) data.append(fm.pack()) for dst_host, dst_switch_dpid in self.hosts.items(): if dst_host == host_e: continue if not self._connection_is_permitted(host_e, dst_host): # If we're not allowed to send to this host (or this host is not allowed to receive), tell our switch # to drop all traffic going to this host. self.log.info("MatchedDenyACE: src=%s dst=%s" % host_e, dst_host) fm = ofp_flow_mod_table_id( table_id = 0, command = of01.OFPFC_MODIFY, match = of01.ofp_match(dl_src=EthAddr(host_e), dl_dst=EthAddr(dst_host)), actions = None) data.append(fm.pack()) continue dst_switch_name = self.graph.names[dst_switch_dpid] #self.log.info(switch_name + ' ' + dst_switch_name) if switch_name == dst_switch_name: continue try: next_hop = nx.shortest_path(self.graph, source=switch_name, target=dst_switch_name)[1] except: continue shortest_path_port = self.graph[switch_name][next_hop]['ports'][switch_name] #self.log.info(str(host_e) + ' ' + str(dst_host)) #self.log.info(str(dst_switch_dpid) + ' ' + str(shortest_path_port)) # inform attached switch where other hosts are fm = ofp_flow_mod_table_id( table_id = 0, command = of01.OFPFC_MODIFY, match = of01.ofp_match(dl_src=EthAddr(host_e), dl_dst=EthAddr(dst_host)), actions = [ofp_action_set_vlan_vid(vlan_vid=dst_switch_dpid), ofp_action_output(port=shortest_path_port)]) data.append(fm.pack()) core.openflow.sendToDPID(dst_switch_dpid, nx_flow_mod_table_id()) # Enables multiple tables try: next_hop = nx.shortest_path(self.graph, source=dst_switch_name, target=switch_name)[1] except: continue shortest_path_port = self.graph[dst_switch_name][next_hop]['ports'][dst_switch_name] # inform other attached switches where this host is fm = ofp_flow_mod_table_id( table_id = 0, command = of01.OFPFC_MODIFY, match = of01.ofp_match(dl_src=EthAddr(dst_host), dl_dst=EthAddr(host_e)), actions = [ofp_action_set_vlan_vid(vlan_vid=switch_dpid), ofp_action_output(port=shortest_path_port)]) core.openflow.sendToDPID(dst_switch_dpid, fm.pack()) core.openflow.sendToDPID(switch_dpid, b''.join(data)) self.broadcast_paths()
def sync_table(self): if not self._conn: return self._cur = {RIP_NET_TABLE: {}, RIP_PORT_TABLE: {}} cur = self._cur for e in self.table.values(): if e.metric >= INFINITY: continue fm = ovs.ofp_flow_mod_table_id() fm.xid = 0 fm.table_id = RIP_NET_TABLE fm.priority = e.size + 1 # +1 because 0 reserved for fallback fm.match.dl_type = pkt.ethernet.IP_TYPE fm.match.nw_dst = (e.ip, e.size) if e.dev is not None: # This is for a directly attached network. It'll be looked up in # the port table. fm.actions.append( ovs.nx_action_resubmit.resubmit_table(RIP_PORT_TABLE)) else: # This is for a remote network. # Load the gateway into the dst IP; it will be looked up in the port # table to find the right port. The real dst IP will get reloaded # from a register before egress. fm.actions.append(of.ofp_action_nw_addr.set_dst(e.next_hop)) fm.actions.append( ovs.nx_action_resubmit.resubmit_table(RIP_PORT_TABLE)) cur[RIP_NET_TABLE][(e.ip, e.size)] = fm for e in self.table.values(): if e.metric >= INFINITY: continue fm = ovs.ofp_flow_mod_table_id() fm.xid = 0 fm.table_id = RIP_PORT_TABLE fm.priority = e.size + 1 # +1 because 0 reserved for fallback fm.match.dl_type = pkt.ethernet.IP_TYPE fm.match.nw_dst = (e.ip, e.size) if e.dev is not None: # This is for a directly attached network. Look up the port. # Also, fix the dst IP address. port = self._conn.ports.get(e.dev) if port is None: continue fm.actions.append( ovs.nx_reg_load(dst=OUT_PORT_REGISTER, value=e.dev)) fm.actions.append(of.ofp_action_dl_addr.set_src(port.hw_addr)) fm.actions.append( ovs.nx_action_resubmit.resubmit_table(ARP_TABLE)) else: # If we get to this table and we don't have a direct entry that # matches, we have no working route! # Should we install something so that we generate an ICMP unreachable # or something? pass cur[RIP_PORT_TABLE][(e.ip, e.size)] = fm if self._conn: data1 = b''.join(x.pack() for x in self._cur[RIP_PORT_TABLE].itervalues()) data2 = b''.join(x.pack() for x in self._cur[RIP_NET_TABLE].itervalues()) data = data1 + data2 if data == self._prev: return # Nothing changed self._clear_table(RIP_NET_TABLE) self._clear_table(RIP_PORT_TABLE) self._init_rip_net_table() self._init_rip_port_table() self.log.debug("Syncing %s port and %s net table entries", len(cur[RIP_PORT_TABLE]), len(cur[RIP_NET_TABLE])) self._conn.send(data) self._prev = data
def _init_rip_port_table(self): # RIP_PORT_TABLE default entry (drop) fm = ovs.ofp_flow_mod_table_id() fm.table_id = RIP_PORT_TABLE fm.priority = 0 self._conn.send(fm)
def _init_ingress_table(self): self._clear_table(INGRESS_TABLE) # INGRESS_TABLE: Send RIP to controller fm = ovs.ofp_flow_mod_table_id() fm.table_id = INGRESS_TABLE fm.cookie = RIP_PACKET_COOKIE fm.match.dl_type = pkt.ethernet.IP_TYPE fm.match.dl_dst = RIP.RIP2_ADDRESS.multicast_ethernet_address fm.match.nw_dst = RIP.RIP2_ADDRESS fm.match.nw_proto = pkt.ipv4.UDP_PROTOCOL fm.match.tp_dst = RIP.RIP_PORT fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) self._conn.send(fm) #TODO: Add RIP entry for unicast advertisements? Or be liberal here # and validate on the controller side? # INGRESS_TABLE: Send ARP requests for router to controller fm = ovs.ofp_flow_mod_table_id() fm.table_id = INGRESS_TABLE fm.cookie = ARP_REQUEST_COOKIE fm.match.dl_type = pkt.ethernet.ARP_TYPE fm.match.nw_proto = pkt.arp.REQUEST fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) for portno, portobj in self._ports.iteritems(): if portno not in self._conn.ports: continue fm.match.in_port = portno for ip in portobj.ips: fm.match.nw_dst = ip self._conn.send(fm) # INGRESS_TABLE: Send ARP replies send to router to controller fm = ovs.ofp_flow_mod_table_id() fm.table_id = INGRESS_TABLE fm.cookie = ARP_REPLY_COOKIE fm.match.dl_type = pkt.ethernet.ARP_TYPE fm.match.nw_proto = pkt.arp.REPLY fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) for portno, portobj in self._ports.iteritems(): if portno not in self._conn.ports: continue fm.match.in_port = portno fm.match.dl_dst = self._conn.ports[portno].hw_addr self._conn.send(fm) # INGRESS_TABLE: Send ICMP to controller fm = ovs.ofp_flow_mod_table_id() fm.table_id = INGRESS_TABLE fm.cookie = PING_COOKIE fm.match.dl_type = pkt.ethernet.IP_TYPE fm.match.nw_proto = pkt.ipv4.ICMP_PROTOCOL fm.match.tp_src = pkt.ICMP.TYPE_ECHO_REQUEST # Type fm.match.tp_dst = 0 # Code fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) for portno, portobj in self._ports.iteritems(): if portno not in self._conn.ports: continue fm.match.in_port = portno fm.match.dl_dst = self._conn.ports[portno].hw_addr for ip in self.all_ips: fm.match.nw_dst = ip self._conn.send(fm) if core.hasComponent("DHCPD"): # INGRESS_TABLE: Send DHCP to controller fm = ovs.ofp_flow_mod_table_id() fm.table_id = INGRESS_TABLE fm.cookie = DHCP_COOKIE fm.match.dl_type = pkt.ethernet.IP_TYPE fm.match.nw_proto = pkt.ipv4.UDP_PROTOCOL fm.match.tp_src = pkt.dhcp.CLIENT_PORT fm.match.tp_dst = pkt.dhcp.SERVER_PORT fm.actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) for portno, dhcpd in core.DHCPD.get_ports_for_dpid(self.dpid): if portno not in self._conn.ports: continue if dhcpd._install_flow: self.log.warn( "Turning off DHCP server table entry installation.") self.log.warn( "You probably want to configure it with no_flow.") dhcpd._install_flow = False fm.match.in_port = portno fm.match.dl_dst = pkt.ETHERNET.ETHER_BROADCAST fm.match.nw_dst = pkt.IPV4.IP_BROADCAST self._conn.send(fm) fm.match.dl_dst = self._conn.ports[portno].hw_addr fm.match.nw_dst = dhcpd.ip_addr self._conn.send(fm) # INGRESS_TABLE: IP packets (lower priority) fm = ovs.ofp_flow_mod_table_id() fm.table_id = INGRESS_TABLE fm.priority -= 1 fm.match.dl_type = pkt.ethernet.IP_TYPE fm.actions.append( ovs.nx_reg_move(dst=DST_IP_REGISTER, src=ovs.NXM_OF_IP_DST)) fm.actions.append(ovs.nx_action_dec_ttl()) fm.actions.append(ovs.nx_action_resubmit.resubmit_table(RIP_NET_TABLE)) self._conn.send(fm)