def vlan_load_reg(): if vlan_removed: of_actions.append(nx.nx_reg_load(dst=vlan_reg, value=0, nbits=16)) else: """The load/unload operations are complicated to simplify the intermediate masked write operations. This is really helpful with multiple stages: it's easier to do a single masked write per stage; there are more stages which do the former than load/unload. Basically what is happening is the following mapping of different parts of the VLAN_TCI field into the register (typically NXM_NX_REG3) as follows: REG3: CFI PCP2 PCP1 PCP0 ID11 ID10 ... ID1 ID0 VLAN_TCI: PCP2 PCP1 PCP0 CFI ID11 ID10 ... ID1 ID0 This enables masked writes considering the VLAN as one *contiguous* 15 bit field, instead of breaking the masked write into two writes, one for the ID part and one for the PCP part (yuck!) """ of_actions.append(nx.nx_reg_move(dst=vlan_reg, src=nx.NXM_OF_VLAN_TCI, dst_ofs=0, src_ofs=0, nbits=12)) of_actions.append(nx.nx_reg_move(dst=vlan_reg, src=nx.NXM_OF_VLAN_TCI, dst_ofs=12, src_ofs=13, nbits=3)) of_actions.append(nx.nx_reg_move(dst=vlan_reg, src=nx.NXM_OF_VLAN_TCI, dst_ofs=15, src_ofs=12, nbits=1))
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 vlan_write_back(): """ The write back operation is slightly complicated for reasons described under `vlan_load_reg()`. """ if debug: print "pox_client: build_nx_actions: Writing back VLAN. The actions are:" print actions if table_id > 0: of_actions.append(nx.nx_reg_move(src=vlan_reg, dst=nx.NXM_OF_VLAN_TCI, src_ofs=0, dst_ofs=0, nbits=12)) of_actions.append(nx.nx_reg_move(src=vlan_reg, dst=nx.NXM_OF_VLAN_TCI, src_ofs=12, dst_ofs=13, nbits=3)) of_actions.append(nx.nx_reg_move(src=vlan_reg, dst=nx.NXM_OF_VLAN_TCI, src_ofs=15, dst_ofs=12, nbits=1))
def build_nx_actions(self,inport,action_list,table_id,pipeline): ### BUILD NX ACTIONS of_actions = [] ctlr_outport = False # there is a controller outport action phys_outports = list() # list of physical outports to forward out of possibly_resubmit_next_table = False # should packet be passed on to next table? atleast_one_action = False for actions in action_list: atleast_one_action = True if 'srcmac' in actions: of_actions.append(of.ofp_action_dl_addr.set_src(actions['srcmac'])) if 'dstmac' in actions: of_actions.append(of.ofp_action_dl_addr.set_dst(actions['dstmac'])) if 'srcip' in actions: of_actions.append(of.ofp_action_nw_addr.set_src(actions['srcip'])) if 'dstip' in actions: of_actions.append(of.ofp_action_nw_addr.set_dst(actions['dstip'])) if 'srcport' in actions: of_actions.append(of.ofp_action_tp_port.set_src(actions['srcport'])) if 'dstport' in actions: of_actions.append(of.ofp_action_tp_port.set_dst(actions['dstport'])) if 'vlan_id' in actions: if actions['vlan_id'] is None: of_actions.append(of.ofp_action_strip_vlan()) else: of_actions.append(of.ofp_action_vlan_vid(vlan_vid=actions['vlan_id'])) if 'vlan_pcp' in actions: if actions['vlan_pcp'] is None: if not actions['vlan_id'] is None: raise RuntimeError("vlan_id and vlan_pcp must be set together!") pass else: of_actions.append(of.ofp_action_vlan_pcp(vlan_pcp=actions['vlan_pcp'])) assert 'port' in actions outport = actions['port'] if outport == of.OFPP_CONTROLLER: ctlr_outport = True else: """ There is either a physical output action (i.e., on a non-controller port), or a "send to next table" action.""" possibly_resubmit_next_table = True if outport != CUSTOM_NEXT_TABLE_PORT: phys_outports.append(outport) """ Otherwise there are no physical outports; just a possibility of resubmitting to the next table. Pass. """ """In general, actual packet forwarding may have to wait until the final table in the pipeline. This means we must determine if there is a "next" table that processes the packet from here, or if this is the last one. But first, the easy part. There are exactly three cases where a forwarding table *will* in fact "immediately forward" a packet according to the current rule (and all previous table stages that processed the packet), without waiting for any other further processing: (1) if the packet is dropped by the current rule, (2) if the packet is forwarded to the controller port, or (3) if this is the last stage of the pipeline. In the case of (1) and (2), packet forwarding may happen immediately and only depend on the current rule. But in (3), the forwarding decision must take the current rule as well as previous port changes into account, as follows: (a) if the current rule specifies an output port, forward the packet out of that port. (b) if the current rule does not specify an outport, then forward the packet out of the port using the value stored in the dedicated per-packet port register. If neither of (1)-(3) above is true, then we take the following approach: (a) if there is an outport set by this rule, write that value into the dedicated per-packet register that contains the current port the packet is in. (b) if there is no outport set by this rule, and if this is table id 0, move the value of the inport into the dedicated per-packet port register. This denotes that the packet is currently still on its inport. (c) resubmit the packet to the "next" table (according to the pipeline). """ exists_next_table = table_id in pipeline.edges # Decide first on "immediate forwarding" conditions: immediately_fwd = True if not atleast_one_action: # (1) drop of_actions = [] elif ctlr_outport: # (2) controller of_actions = [] of_actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) elif possibly_resubmit_next_table and (not exists_next_table): # (3) last stage of pipeline if len(phys_outports) > 0: # fwd out of latest assigned ports for p in phys_outports: of_actions.append(of.ofp_action_output(port=p)) else: # fwd out of stored port value of_actions.append(nx.nx_output_reg(reg=nx.NXM_NX_REG2, nbits=16)) elif (not exists_next_table) and (not possibly_resubmit_next_table): raise RuntimeError("Unexpected condition in multi-stage processing") else: # must resubmit packet to subsequent tables for processing immediately_fwd = False if immediately_fwd: return of_actions # Act on packet with knowledge that subsequent tables must process it assert (possibly_resubmit_next_table and exists_next_table and (not immediately_fwd)) next_table = pipeline.edges[table_id] if len(phys_outports) > 0: # move port register to latest assigned port values for p in phys_outports: of_actions.append(nx.nx_reg_load(dst=nx.NXM_NX_REG2, value=p, nbits=16)) of_actions.append(nx.nx_action_resubmit.resubmit_table( table=next_table)) elif table_id == 0: # move the inport value to reg2. of_actions.append(nx.nx_reg_move(src=nx.NXM_OF_IN_PORT, dst=nx.NXM_NX_REG2, nbits=16)) of_actions.append(nx.nx_action_resubmit.resubmit_table( table=next_table)) else: of_actions.append(nx.nx_action_resubmit.resubmit_table( table=next_table)) return of_actions
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)
def build_nx_actions(self,inport,action_list,table_id,pipeline,debug=False): ### BUILD NX ACTIONS of_actions = [] ctlr_outport = False # there is a controller outport action phys_outports = list() # list of physical outports to forward out of possibly_resubmit_next_table = False # should packet be passed on to next table? atleast_one_action = False # vlan handling flags vlan_removed = False vlan_written = {} if debug: print "pox_client: build_nx_actions: Received actions:" print action_list for actions in action_list: atleast_one_action = True if 'srcmac' in actions: of_actions.append(of.ofp_action_dl_addr.set_src(actions['srcmac'])) if 'dstmac' in actions: of_actions.append(of.ofp_action_dl_addr.set_dst(actions['dstmac'])) if 'srcip' in actions: of_actions.append(of.ofp_action_nw_addr.set_src(actions['srcip'])) if 'dstip' in actions: of_actions.append(of.ofp_action_nw_addr.set_dst(actions['dstip'])) if 'srcport' in actions: of_actions.append(of.ofp_action_tp_port.set_src(actions['srcport'])) if 'dstport' in actions: of_actions.append(of.ofp_action_tp_port.set_dst(actions['dstport'])) if 'vlan_id' in actions: if actions['vlan_id'] is None: vlan_removed = True else: assert 'vlan_pcp' in actions assert 'vlan_offset' in actions assert 'vlan_nbits' in actions vlan_written = {k: actions['vlan_' + k] for k in ['pcp', 'offset', 'nbits', 'id']} if 'vlan_pcp' in actions: assert 'vlan_id' in actions, "vlan_id and vlan_pcp must be set together" assert 'port' in actions outport = actions['port'] if outport == of.OFPP_CONTROLLER: ctlr_outport = True else: """ There is either a physical output action (i.e., on a non-controller port), or a "send to next table" action.""" possibly_resubmit_next_table = True if outport != CUSTOM_NEXT_TABLE_PORT: phys_outports.append(outport) """ Otherwise there are no physical outports; just a possibility of resubmitting to the next table. Pass. """ """Construct routines to move packet VLAN into the register, do a masked write into the register, and to write back the register into the packet. """ vlan_reg = nx.NXM_NX_REG3 def vlan_load_reg(): if vlan_removed: of_actions.append(nx.nx_reg_load(dst=vlan_reg, value=0, nbits=16)) else: """The load/unload operations are complicated to simplify the intermediate masked write operations. This is really helpful with multiple stages: it's easier to do a single masked write per stage; there are more stages which do the former than load/unload. Basically what is happening is the following mapping of different parts of the VLAN_TCI field into the register (typically NXM_NX_REG3) as follows: REG3: CFI PCP2 PCP1 PCP0 ID11 ID10 ... ID1 ID0 VLAN_TCI: PCP2 PCP1 PCP0 CFI ID11 ID10 ... ID1 ID0 This enables masked writes considering the VLAN as one *contiguous* 15 bit field, instead of breaking the masked write into two writes, one for the ID part and one for the PCP part (yuck!) """ of_actions.append(nx.nx_reg_move(dst=vlan_reg, src=nx.NXM_OF_VLAN_TCI, dst_ofs=0, src_ofs=0, nbits=12)) of_actions.append(nx.nx_reg_move(dst=vlan_reg, src=nx.NXM_OF_VLAN_TCI, dst_ofs=12, src_ofs=13, nbits=3)) of_actions.append(nx.nx_reg_move(dst=vlan_reg, src=nx.NXM_OF_VLAN_TCI, dst_ofs=15, src_ofs=12, nbits=1)) def vlan_masked_write(): if debug: print "pox_client: build_nx_actions: in rewrite function:" print vlan_removed, vlan_written if vlan_removed: if debug: print "pox_client: build_nx_actions: VLAN was removed" of_actions.append(nx.nx_reg_load(dst=vlan_reg, value=0, nbits=16)) elif vlan_written: vlan_16bit = ((int(vlan_written['pcp']) << 12) | (int(vlan_written['id']))) load_value = ((vlan_16bit >> vlan_written['offset']) & ((1 << vlan_written['nbits'])-1)) of_actions.append(nx.nx_reg_load(dst=vlan_reg, value=load_value, offset=vlan_written['offset'], nbits=vlan_written['nbits'])) of_actions.append(nx.nx_reg_load(dst=vlan_reg, value=1, offset=15, nbits=1)) def vlan_write_back(): """ The write back operation is slightly complicated for reasons described under `vlan_load_reg()`. """ if debug: print "pox_client: build_nx_actions: Writing back VLAN. The actions are:" print actions if table_id > 0: of_actions.append(nx.nx_reg_move(src=vlan_reg, dst=nx.NXM_OF_VLAN_TCI, src_ofs=0, dst_ofs=0, nbits=12)) of_actions.append(nx.nx_reg_move(src=vlan_reg, dst=nx.NXM_OF_VLAN_TCI, src_ofs=12, dst_ofs=13, nbits=3)) of_actions.append(nx.nx_reg_move(src=vlan_reg, dst=nx.NXM_OF_VLAN_TCI, src_ofs=15, dst_ofs=12, nbits=1)) """In general, actual packet forwarding may have to wait until the final table in the pipeline. This means we must determine if there is a "next" table that processes the packet from here, or if this is the last one. But first, the easy part. There are exactly three cases where a forwarding table *will* in fact "immediately forward" a packet according to the current rule (and all previous table stages that processed the packet), without waiting for any other further processing: (1) if the packet is dropped by the current rule, (2) if the packet is forwarded to the controller port, or (3) if this is the last stage of the pipeline. In the case of (1) and (2), packet forwarding may happen immediately and only depend on the current rule. But in (3), the forwarding decision must take the current rule as well as previous port changes into account, as follows: (a) if the current rule specifies an output port, forward the packet out of that port. (b) if the current rule does not specify an outport, then forward the packet out of the port using the value stored in the dedicated per-packet port register. If neither of (1)-(3) above is true, then we take the following approach: (a) if there is an outport set by this rule, write that value into the dedicated per-packet register that contains the current port the packet is in. (b) if there is no outport set by this rule, and if this is table id 0, move the value of the inport into the dedicated per-packet port register. This denotes that the packet is currently still on its inport. (c) resubmit the packet to the "next" table (according to the pipeline). """ exists_next_table = table_id in pipeline.edges # Decide first on "immediate forwarding" conditions: immediately_fwd = True if not atleast_one_action: # (1) drop of_actions = [] elif ctlr_outport: # (2) controller of_actions = [] vlan_write_back() of_actions.append(of.ofp_action_output(port=of.OFPP_CONTROLLER)) elif possibly_resubmit_next_table and (not exists_next_table): # (3) last stage of pipeline vlan_masked_write() vlan_write_back() if len(phys_outports) > 0: # fwd out of latest assigned ports for p in phys_outports: of_actions.append(of.ofp_action_output(port=p)) else: # fwd out of stored port value of_actions.append(nx.nx_output_reg(reg=nx.NXM_NX_REG2, nbits=16)) elif (not exists_next_table) and (not possibly_resubmit_next_table): raise RuntimeError("Unexpected condition in multi-stage processing") else: # must resubmit packet to subsequent tables for processing immediately_fwd = False if immediately_fwd: return of_actions if debug: print "pox_client: build_nx_actions: Not the last stage." # Act on packet with knowledge that subsequent tables must process it assert (possibly_resubmit_next_table and exists_next_table and (not immediately_fwd)) # 1. Handle VLAN writing for resubmitted packets if table_id == 0: vlan_load_reg() vlan_masked_write() # 2. Handle packet forwarding for resubmitted packets next_table = pipeline.edges[table_id] if len(phys_outports) > 0: # move port register to latest assigned port values for p in phys_outports: of_actions.append(nx.nx_reg_load(dst=nx.NXM_NX_REG2, value=p, nbits=16)) of_actions.append(nx.nx_action_resubmit.resubmit_table( table=next_table)) elif table_id == 0: # move the inport value to reg2. of_actions.append(nx.nx_reg_move(src=nx.NXM_OF_IN_PORT, dst=nx.NXM_NX_REG2, nbits=16)) of_actions.append(nx.nx_action_resubmit.resubmit_table( table=next_table)) else: of_actions.append(nx.nx_action_resubmit.resubmit_table( table=next_table)) return of_actions