def __init__(self, dpid=None): FlowTable.__init__(self) self._table = set() self.dpid = dpid self.fields = {} for f in self.dict_field_names: self.fields[f] = {} for f in self.trie_field_names: self.fields[f] = Trie()
def __init__ (self, switch=None, **kw): EventMixin.__init__(self) self.flow_table = FlowTable() self.switch = switch # a list of pending flow table entries : tuples (ADD|REMOVE, entry) self._pending = [] # a map of pending barriers barrier_xid-> ([entry1,entry2]) self._pending_barrier_to_ops = {} # a map of pending barriers per request entry -> (barrier_xid, time) self._pending_op_to_barrier = {} self.listenTo(switch)
def GenerateACL(): acl_rules = open(r'..\Data\acl3_4k_rules.txt') table = FlowTable() i = 3874 while True: r = acl_rules.readline().split() if not r: break table.add_entry( TableEntry(priority=i, cookie=0x1, match=ofp_match(dl_src=EthAddr("00:00:00:00:00:01"), nw_src=r[0], nw_dst=r[1]), actions=[ofp_action_output(port=5)])) #table.add_entry(TableEntry(priority=i, cookie=0x1, match=ofp_match(dl_src=EthAddr("00:00:00:00:00:01"),nw_src="53.45.14.183/32",nw_dst="18.184.25.126/32"), actions=[ofp_action_output(port=5)])) i = i - 1 return table
def table(): t = FlowTable() t.add_entry( TableEntry(priority=6, cookie=0x1, match=ofp_match(dl_src=EthAddr("00:00:00:00:00:01"), nw_src="1.2.3.4"), actions=[ofp_action_output(port=5)])) t.add_entry( TableEntry(priority=5, cookie=0x2, match=ofp_match(dl_src=EthAddr("00:00:00:00:00:02"), nw_src="1.2.3.0/24"), actions=[ofp_action_output(port=6)])) t.add_entry( TableEntry(priority=1, cookie=0x3, match=ofp_match(), actions=[])) return t
def decode_flow_table(self, json): ft = FlowTable() for e in json["entries"]: ft.add_entry(self.decode_entry(e)) return ft
class OFSyncFlowTable (EventMixin): _eventMixin_events = set([FlowTableModification]) """ A flow table that keeps in sync with a switch """ ADD = of.OFPFC_ADD REMOVE = of.OFPFC_DELETE REMOVE_STRICT = of.OFPFC_DELETE_STRICT TIME_OUT = 2 def __init__ (self, switch=None, **kw): EventMixin.__init__(self) self.flow_table = FlowTable() self.switch = switch # a list of pending flow table entries : tuples (ADD|REMOVE, entry) self._pending = [] # a map of pending barriers barrier_xid-> ([entry1,entry2]) self._pending_barrier_to_ops = {} # a map of pending barriers per request entry -> (barrier_xid, time) self._pending_op_to_barrier = {} self.listenTo(switch) def install (self, entries=[]): """ asynchronously install entries in the flow table will raise a FlowTableModification event when the change has been processed by the switch """ self._mod(entries, OFSyncFlowTable.ADD) def remove_with_wildcards (self, entries=[]): """ asynchronously remove entries in the flow table will raise a FlowTableModification event when the change has been processed by the switch """ self._mod(entries, OFSyncFlowTable.REMOVE) def remove_strict (self, entries=[]): """ asynchronously remove entries in the flow table. will raise a FlowTableModification event when the change has been processed by the switch """ self._mod(entries, OFSyncFlowTable.REMOVE_STRICT) @property def entries (self): return self.flow_table.entries @property def num_pending (self): return len(self._pending) def __len__ (self): return len(self.flow_table) def _mod (self, entries, command): if isinstance(entries, TableEntry): entries = [ entries ] for entry in entries: if(command == OFSyncFlowTable.REMOVE): self._pending = [(cmd,pentry) for cmd,pentry in self._pending if not (cmd == OFSyncFlowTable.ADD and entry.matches_with_wildcards(pentry))] elif(command == OFSyncFlowTable.REMOVE_STRICT): self._pending = [(cmd,pentry) for cmd,pentry in self._pending if not (cmd == OFSyncFlowTable.ADD and entry == pentry)] self._pending.append( (command, entry) ) self._sync_pending() def _sync_pending (self, clear=False): if not self.switch.connected: return False # resync the switch if clear: self._pending_barrier_to_ops = {} self._pending_op_to_barrier = {} self._pending = filter(lambda(op): op[0] == OFSyncFlowTable.ADD, self._pending) self.switch.send(of.ofp_flow_mod(command=of.OFPFC_DELETE, match=of.ofp_match())) self.switch.send(of.ofp_barrier_request()) todo = map(lambda(e): (OFSyncFlowTable.ADD, e), self.flow_table.entries) + self._pending else: todo = [op for op in self._pending if op not in self._pending_op_to_barrier or (self._pending_op_to_barrier[op][1] + OFSyncFlowTable.TIME_OUT) < time.time() ] for op in todo: fmod_xid = self.switch._xid_generator() flow_mod = op[1].to_flow_mod(xid=fmod_xid, command=op[0], flags=op[1].flags | of.OFPFF_SEND_FLOW_REM) self.switch.send(flow_mod) barrier_xid = self.switch._xid_generator() self.switch.send(of.ofp_barrier_request(xid=barrier_xid)) now = time.time() self._pending_barrier_to_ops[barrier_xid] = todo for op in todo: self._pending_op_to_barrier[op] = (barrier_xid, now) def _handle_SwitchConnectionUp (self, event): # sync all_flows self._sync_pending(clear=True) def _handle_SwitchConnectionDown (self, event): # connection down. too bad for our unconfirmed entries self._pending_barrier_to_ops = {} self._pending_op_to_barrier = {} def _handle_BarrierIn (self, barrier): # yeah. barrier in. time to sync some of these flows if barrier.xid in self._pending_barrier_to_ops: added = [] removed = [] #print "barrier in: pending for barrier: %d: %s" % (barrier.xid, # self._pending_barrier_to_ops[barrier.xid]) for op in self._pending_barrier_to_ops[barrier.xid]: (command, entry) = op if(command == OFSyncFlowTable.ADD): self.flow_table.add_entry(entry) added.append(entry) else: removed.extend(self.flow_table.remove_matching_entries(entry.match, entry.priority, strict=command == OFSyncFlowTable.REMOVE_STRICT)) #print "op: %s, pending: %s" % (op, self._pending) if op in self._pending: self._pending.remove(op) self._pending_op_to_barrier.pop(op, None) del self._pending_barrier_to_ops[barrier.xid] self.raiseEvent(FlowTableModification(added = added, removed=removed)) return EventHalt else: return EventContinue def _handle_FlowRemoved (self, event): """ process a flow removed event -- remove the matching flow from the table. """ flow_removed = event.ofp for entry in self.flow_table.entries: if (flow_removed.match == entry.match and flow_removed.priority == entry.priority): self.flow_table.remove_entry(entry) self.raiseEvent(FlowTableModification(removed=[entry])) return EventHalt return EventContinue
def __init__(self, dpid, name=None, ports=4, miss_send_len=128, max_buffers=100, max_entries=0x7fFFffFF, features=None): """ Initialize switch - ports is a list of ofp_phy_ports or a number of ports - miss_send_len is number of bytes to send to controller on table miss - max_buffers is number of buffered packets to store - max_entries is max flows entries per table """ if name is None: name = dpid_to_str(dpid) self.name = name if isinstance(ports, int): ports = _generate_ports(num_ports=ports, dpid=dpid) self.dpid = dpid self.max_buffers = max_buffers self.max_entries = max_entries self.miss_send_len = miss_send_len self._has_sent_hello = False self.table = FlowTable() self.table.addListeners(self) self._lookup_count = 0 self._matched_count = 0 self.log = logging.getLogger(self.name) self._connection = None # buffer for packets during packet_in self._packet_buffer = [] # Map port_no -> openflow.pylibopenflow_01.ofp_phy_ports self.ports = {} self.port_stats = {} for port in ports: self.ports[port.port_no] = port self.port_stats[port.port_no] = ofp_port_stats( port_no=port.port_no) if features is not None: self.features = features else: # Set up default features self.features = SwitchFeatures() self.features.cap_flow_stats = True self.features.cap_table_stats = True self.features.cap_port_stats = True #self.features.cap_stp = True #self.features.cap_ip_reasm = True #self.features.cap_queue_stats = True #self.features.cap_arp_match_ip = True self.features.act_output = True self.features.act_enqueue = True self.features.act_strip_vlan = True self.features.act_set_vlan_vid = True self.features.act_set_vlan_pcp = True self.features.act_set_dl_dst = True self.features.act_set_dl_src = True self.features.act_set_nw_dst = True self.features.act_set_nw_src = True self.features.act_set_nw_tos = True self.features.act_set_tp_dst = True self.features.act_set_tp_src = True #self.features.act_vendor = True # Set up handlers for incoming OpenFlow messages # That is, self.ofp_handlers[OFPT_FOO] = self._rx_foo self.ofp_handlers = {} for value, name in ofp_type_map.iteritems(): name = name.split("OFPT_", 1)[-1].lower() h = getattr(self, "_rx_" + name, None) if not h: continue assert of._message_type_to_class[value]._from_controller, name self.ofp_handlers[value] = h # Set up handlers for actions # That is, self.action_handlers[OFPAT_FOO] = self._action_foo #TODO: Refactor this with above self.action_handlers = {} for value, name in ofp_action_type_map.iteritems(): name = name.split("OFPAT_", 1)[-1].lower() h = getattr(self, "_action_" + name, None) if not h: continue if getattr(self.features, "act_" + name) is False: continue self.action_handlers[value] = h # Set up handlers for stats handlers # That is, self.stats_handlers[OFPST_FOO] = self._stats_foo #TODO: Refactor this with above self.stats_handlers = {} for value, name in ofp_stats_type_map.iteritems(): name = name.split("OFPST_", 1)[-1].lower() h = getattr(self, "_stats_" + name, None) if not h: continue self.stats_handlers[value] = h # Set up handlers for flow mod handlers # That is, self.flow_mod_handlers[OFPFC_FOO] = self._flow_mod_foo #TODO: Refactor this with above self.flow_mod_handlers = {} for name, value in ofp_flow_mod_command_rev_map.iteritems(): name = name.split("OFPFC_", 1)[-1].lower() h = getattr(self, "_flow_mod_" + name, None) if not h: continue self.flow_mod_handlers[value] = h
class SoftwareSwitchBase(object): def __init__(self, dpid, name=None, ports=4, miss_send_len=128, max_buffers=100, max_entries=0x7fFFffFF, features=None): """ Initialize switch - ports is a list of ofp_phy_ports or a number of ports - miss_send_len is number of bytes to send to controller on table miss - max_buffers is number of buffered packets to store - max_entries is max flows entries per table """ if name is None: name = dpid_to_str(dpid) self.name = name if isinstance(ports, int): ports = _generate_ports(num_ports=ports, dpid=dpid) self.dpid = dpid self.max_buffers = max_buffers self.max_entries = max_entries self.miss_send_len = miss_send_len self._has_sent_hello = False self.table = FlowTable() self.table.addListeners(self) self._lookup_count = 0 self._matched_count = 0 self.log = logging.getLogger(self.name) self._connection = None # buffer for packets during packet_in self._packet_buffer = [] # Map port_no -> openflow.pylibopenflow_01.ofp_phy_ports self.ports = {} self.port_stats = {} for port in ports: self.ports[port.port_no] = port self.port_stats[port.port_no] = ofp_port_stats( port_no=port.port_no) if features is not None: self.features = features else: # Set up default features self.features = SwitchFeatures() self.features.cap_flow_stats = True self.features.cap_table_stats = True self.features.cap_port_stats = True #self.features.cap_stp = True #self.features.cap_ip_reasm = True #self.features.cap_queue_stats = True #self.features.cap_arp_match_ip = True self.features.act_output = True self.features.act_enqueue = True self.features.act_strip_vlan = True self.features.act_set_vlan_vid = True self.features.act_set_vlan_pcp = True self.features.act_set_dl_dst = True self.features.act_set_dl_src = True self.features.act_set_nw_dst = True self.features.act_set_nw_src = True self.features.act_set_nw_tos = True self.features.act_set_tp_dst = True self.features.act_set_tp_src = True #self.features.act_vendor = True # Set up handlers for incoming OpenFlow messages # That is, self.ofp_handlers[OFPT_FOO] = self._rx_foo self.ofp_handlers = {} for value, name in ofp_type_map.iteritems(): name = name.split("OFPT_", 1)[-1].lower() h = getattr(self, "_rx_" + name, None) if not h: continue assert of._message_type_to_class[value]._from_controller, name self.ofp_handlers[value] = h # Set up handlers for actions # That is, self.action_handlers[OFPAT_FOO] = self._action_foo #TODO: Refactor this with above self.action_handlers = {} for value, name in ofp_action_type_map.iteritems(): name = name.split("OFPAT_", 1)[-1].lower() h = getattr(self, "_action_" + name, None) if not h: continue if getattr(self.features, "act_" + name) is False: continue self.action_handlers[value] = h # Set up handlers for stats handlers # That is, self.stats_handlers[OFPST_FOO] = self._stats_foo #TODO: Refactor this with above self.stats_handlers = {} for value, name in ofp_stats_type_map.iteritems(): name = name.split("OFPST_", 1)[-1].lower() h = getattr(self, "_stats_" + name, None) if not h: continue self.stats_handlers[value] = h # Set up handlers for flow mod handlers # That is, self.flow_mod_handlers[OFPFC_FOO] = self._flow_mod_foo #TODO: Refactor this with above self.flow_mod_handlers = {} for name, value in ofp_flow_mod_command_rev_map.iteritems(): name = name.split("OFPFC_", 1)[-1].lower() h = getattr(self, "_flow_mod_" + name, None) if not h: continue self.flow_mod_handlers[value] = h @property def _time(self): """ Get the current time This should be used for, e.g., calculating timeouts. It currently isn't used everywhere it should be. Override this to change time behavior. """ return time.time() def _handle_FlowTableModification(self, event): """ Handle flow table modification events """ # Currently, we only use this for sending flow_removed messages if not event.removed: return if event.reason in (OFPRR_IDLE_TIMEOUT, OFPRR_HARD_TIMEOUT, OFPRR_DELETE): # These reasons may lead to a flow_removed count = 0 for entry in event.removed: if entry.flags & OFPFF_SEND_FLOW_REM: # Flow wants removal notification -- send it fr = entry.to_flow_removed(self._time, reason=event.reason) self.send(fr) count += 1 self.log.debug("%d flows removed (%d removal notifications)", len(event.removed), count) def rx_message(self, connection, msg): """ Handle an incoming OpenFlow message """ ofp_type = msg.header_type h = self.ofp_handlers.get(ofp_type) if h is None: raise RuntimeError("No handler for ofp_type %s(%d)" % (ofp_type_map.get(ofp_type), ofp_type)) self.log.debug("Got %s with XID %s", ofp_type_map.get(ofp_type), msg.xid) h(msg, connection=connection) def set_connection(self, connection): """ Set this switch's connection. """ self._has_sent_hello = False connection.set_message_handler(self.rx_message) self._connection = connection def send(self, message, connection=None): """ Send a message to this switch's communication partner """ if connection is None: connection = self._connection if connection: connection.send(message) else: self.log.debug("Asked to send message %s, but not connected", message) def _rx_hello(self, ofp, connection): #FIXME: This isn't really how hello is supposed to work -- we're supposed # to send it immediately on connection. See _send_hello(). self.send_hello() def _rx_echo_request(self, ofp, connection): """ Handles echo requests """ msg = ofp_echo_reply(xid=ofp.xid, body=ofp.body) self.send(msg) def _rx_features_request(self, ofp, connection): """ Handles feature requests """ self.log.debug("Send features reply") msg = ofp_features_reply(datapath_id=self.dpid, xid=ofp.xid, n_buffers=self.max_buffers, n_tables=1, capabilities=self.features.capability_bits, actions=self.features.action_bits, ports=self.ports.values()) self.send(msg) def _rx_flow_mod(self, ofp, connection): """ Handles flow mods """ self.log.debug("Flow mod details: %s", ofp.show()) #self.table.process_flow_mod(ofp) #self._process_flow_mod(ofp, connection=connection, table=self.table) handler = self.flow_mod_handlers.get(ofp.command) if handler is None: self.log.warn("Command not implemented: %s" % command) self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_BAD_COMMAND, ofp=ofp, connection=connection) return handler(flow_mod=ofp, connection=connection, table=self.table) if ofp.buffer_id is not None: self._process_actions_for_packet_from_buffer( ofp.actions, ofp.buffer_id, ofp) def _rx_packet_out(self, packet_out, connection): """ Handles packet_outs """ self.log.debug("Packet out details: %s", packet_out.show()) if packet_out.data: self._process_actions_for_packet(packet_out.actions, packet_out.data, packet_out.in_port, packet_out) elif packet_out.buffer_id is not None: self._process_actions_for_packet_from_buffer( packet_out.actions, packet_out.buffer_id, packet_out) else: self.log.warn("packet_out: No data and no buffer_id -- " "don't know what to send") def _rx_echo_reply(self, ofp, connection): pass def _rx_barrier_request(self, ofp, connection): msg = ofp_barrier_reply(xid=ofp.xid) self.send(msg) def _rx_get_config_request(self, ofp, connection): msg = ofp_get_config_reply(xid=ofp.xid) #FIXME: Set attributes of reply! self.send(msg) def _rx_stats_request(self, ofp, connection): handler = self.stats_handlers.get(ofp.type) if handler is None: self.log.warning("Stats type %s not implemented", ofp.type) self.send_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_STAT, ofp=ofp, connection=connection) return body = handler(ofp, connection=connection) if body is not None: reply = ofp_stats_reply(xid=ofp.xid, type=ofp.type, body=body) self.log.debug("Sending stats reply %s", reply) self.send(reply) def _rx_set_config(self, config, connection): #FIXME: Implement this! pass def _rx_port_mod(self, port_mod, connection): port_no = port_mod.port_no if port_no not in self.ports: self.send_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_PORT, ofp=port_mod, connection=connection) return port = self.ports[port_no] if port.hw_addr != port_mod.hw_addr: self.send_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_HW_ADDR, ofp=port_mod, connection=connection) return mask = port_mod.mask for bit in range(32): bit = 1 << bit if mask & bit: handled, r = self._set_port_config_bit(port, bit, port_mod.config & bit) if not handled: self.log.warn("Unsupported port config flag: %08x", bit) continue if r is not None: msg = "Port %s: " % (port.port_no, ) if isinstance(r, str): msg += r else: msg += ofp_port_config_map.get( bit, "config bit %x" % (bit, )) msg += " set to " msg += "true" if r else "false" self.log.debug(msg) def _rx_vendor(self, vendor, connection): # We don't support vendor extensions, so send an OFP_ERROR, per # page 42 of spec self.send_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_VENDOR, ofp=vendor, connection=connection) def send_hello(self, force=False): """ Send hello (once) """ #FIXME: This is wrong -- we should just send when connecting. if self._has_sent_hello and not force: return self._has_sent_hello = True self.log.debug("Sent hello") msg = ofp_hello(xid=0) self.send(msg) def send_packet_in(self, in_port, buffer_id=None, packet=b'', reason=None, data_length=None): """ Send PacketIn """ if hasattr(packet, 'pack'): packet = packet.pack() assert assert_type("packet", packet, bytes) self.log.debug("Send PacketIn") if reason is None: reason = OFPR_NO_MATCH if data_length is not None and len(packet) > data_length: if buffer_id is not None: packet = packet[:data_length] msg = ofp_packet_in(xid=0, in_port=in_port, buffer_id=buffer_id, reason=reason, data=packet) self.send(msg) def send_port_status(self, port, reason): """ Send port status port is an ofp_phy_port reason is one of OFPPR_xxx """ assert assert_type("port", port, ofp_phy_port, none_ok=False) assert reason in ofp_port_reason_rev_map.values() msg = ofp_port_status(desc=port, reason=reason) self.send(msg) def send_error(self, type, code, ofp=None, data=None, connection=None): """ Send an error If you pass ofp, it will be used as the source of the error's XID and data. You can override the data by also specifying data. """ err = ofp_error(type=type, code=code) if ofp: err.xid = ofp.xid err.data = ofp.pack() else: err.xid = 0 if data is not None: err.data = data self.send(err, connection=connection) def rx_packet(self, packet, in_port, packet_data=None): """ process a dataplane packet packet: an instance of ethernet in_port: the integer port number packet_data: packed version of packet if available """ assert assert_type("packet", packet, ethernet, none_ok=False) assert assert_type("in_port", in_port, int, none_ok=False) port = self.ports.get(in_port) if port is None: self.log.warn("Got packet on missing port %i", in_port) return is_stp = packet.dst == _STP_MAC if (port.config & OFPPC_NO_RECV) and not is_stp: # Drop all except STP return if (port.config & OFPPC_NO_RECV_STP) and is_stp: # Drop STP return self.port_stats[in_port].rx_packets += 1 if packet_data is not None: self.port_stats[in_port].rx_bytes += len(packet_data) else: self.port_stats[in_port].rx_bytes += len( packet.pack()) # Expensive self._lookup_count += 1 entry = self.table.entry_for_packet(packet, in_port) if entry is not None: self._matched_count += 1 entry.touch_packet(len(packet)) self._process_actions_for_packet(entry.actions, packet, in_port) else: # no matching entry if port.config & OFPPC_NO_PACKET_IN: return buffer_id = self._buffer_packet(packet, in_port) if packet_data is None: packet_data = packet.pack() self.send_packet_in(in_port, buffer_id, packet_data, reason=OFPR_NO_MATCH, data_length=self.miss_send_len) def delete_port(self, port): """ Removes a port Sends a port_status message to the controller Returns the removed phy_port """ try: port_no = port.port_no assert self.ports[port_no] is port except: port_no = port port = self.ports[port_no] if port_no not in self.ports: raise RuntimeError("Can't remove nonexistent port " + str(port_no)) self.send_port_status(port, OFPPR_DELETE) del self.ports[port_no] return port def add_port(self, port): """ Adds a port Sends a port_status message to the controller """ try: port_no = port.port_no except: port_no = port port = _generate_port(port_no, self.dpid) if port_no in self.ports: raise RuntimeError("Port %s already exists" % (port_no, )) self.ports[port_no] = port self.send_port_status(port, OFPPR_ADD) def _set_port_config_bit(self, port, bit, value): """ Set a port config bit This is called in response to port_mods. It is passed the ofp_phy_port, the bit/mask, and the value of the bit (i.e., 0 if the flag is to be unset, or the same value as bit if it is to be set). The return value is a tuple (handled, msg). If bit is handled, then handled will be True, else False. if msg is a string, it will be used as part of a log message. If msg is None, there will be no log message. If msg is anything else "truthy", an "enabled" log message is generated. If msg is anything else "falsy", a "disabled" log message is generated. msg is only used when handled is True. """ if bit == OFPPC_NO_STP: if value == 0: # we also might send OFPBRC_EPERM if trying to disable this bit self.log.warn("Port %s: Can't enable 802.1D STP", port.port_no) return (True, None) if bit not in (OFPPC_PORT_DOWN, OFPPC_NO_STP, OFPPC_NO_RECV, OFPPC_NO_RECV_STP, OFPPC_NO_FLOOD, OFPPC_NO_FWD, OFPPC_NO_PACKET_IN): return (False, None) if port.set_config(value, bit): if bit == OFPPC_PORT_DOWN: # Note (Peter Peresini): Although the spec is not clear about it, # we will assume that config.OFPPC_PORT_DOWN implies # state.OFPPS_LINK_DOWN. This is consistent with Open vSwitch. #TODO: for now, we assume that there is always physical link present # and that the link state depends only on the configuration. old_state = port.state & OFPPS_LINK_DOWN port.state = port.state & ~OFPPS_LINK_DOWN if port.config & OFPPC_PORT_DOWN: port.state = port.state | OFPPS_LINK_DOWN new_state = port.state & OFPPS_LINK_DOWN if old_state != new_state: self.send_port_status(port, OFPPR_MODIFY) # Do default log message. return (True, value) # No change -- no log message. return (True, None) def _output_packet_physical(self, packet, port_no): """ send a packet out a single physical port This is called by the more general _output_packet(). Override this. """ self.log.info("Sending packet %s out port %s", str(packet), port_no) def _output_packet(self, packet, out_port, in_port, max_len=None): """ send a packet out some port This handles virtual ports and does validation. packet: instance of ethernet out_port, in_port: the integer port number max_len: maximum packet payload length to send to controller """ assert assert_type("packet", packet, ethernet, none_ok=False) def real_send(port_no, allow_in_port=False): if type(port_no) == ofp_phy_port: port_no = port_no.port_no if port_no == in_port and not allow_in_port: self.log.warn("Dropping packet sent on port %i: Input port", port_no) return if port_no not in self.ports: self.log.warn("Dropping packet sent on port %i: Invalid port", port_no) return if self.ports[port_no].config & OFPPC_NO_FWD: self.log.warn( "Dropping packet sent on port %i: Forwarding disabled", port_no) return if self.ports[port_no].config & OFPPC_PORT_DOWN: self.log.warn("Dropping packet sent on port %i: Port down", port_no) return if self.ports[port_no].state & OFPPS_LINK_DOWN: self.log.debug("Dropping packet sent on port %i: Link down", port_no) return self.port_stats[port_no].tx_packets += 1 self.port_stats[port_no].tx_bytes += len( packet.pack()) #FIXME: Expensive self._output_packet_physical(packet, port_no) if out_port < OFPP_MAX: real_send(out_port) elif out_port == OFPP_IN_PORT: real_send(in_port, allow_in_port=True) elif out_port == OFPP_FLOOD: for no, port in self.ports.iteritems(): if no == in_port: continue if port.config & OFPPC_NO_FLOOD: continue real_send(port) elif out_port == OFPP_ALL: for no, port in self.ports.iteritems(): if no == in_port: continue real_send(port) elif out_port == OFPP_CONTROLLER: buffer_id = self._buffer_packet(packet, in_port) # Should we honor OFPPC_NO_PACKET_IN here? self.send_packet_in(in_port, buffer_id, packet, reason=OFPR_ACTION, data_length=max_len) elif out_port == OFPP_TABLE: # Do we disable send-to-controller when performing this? # (Currently, there's the possibility that a table miss from this # will result in a send-to-controller which may send back to table...) self.rx_packet(packet, in_port) else: self.log.warn("Unsupported virtual output port: %d", out_port) def _buffer_packet(self, packet, in_port=None): """ Buffer packet and return buffer ID If no buffer is available, return None. """ # Do we have an empty slot? for (i, value) in enumerate(self._packet_buffer): if value is None: # Yes -- use it self._packet_buffer[i] = (packet, in_port) return i + 1 # No -- create a new slow if len(self._packet_buffer) >= self.max_buffers: # No buffers available! return None self._packet_buffer.append((packet, in_port)) return len(self._packet_buffer) def _process_actions_for_packet_from_buffer(self, actions, buffer_id, ofp=None): """ output and release a packet from the buffer ofp is the message which triggered this processing, if any (used for error generation) """ buffer_id = buffer_id - 1 if (buffer_id >= len(self._packet_buffer)) or (buffer_id < 0): self.log.warn("Invalid output buffer id: %d", buffer_id + 1) return if self._packet_buffer[buffer_id] is None: self.log.warn("Buffer %d has already been flushed", buffer_id + 1) return (packet, in_port) = self._packet_buffer[buffer_id] self._process_actions_for_packet(actions, packet, in_port, ofp) self._packet_buffer[buffer_id] = None def _process_actions_for_packet(self, actions, packet, in_port, ofp=None): """ process the output actions for a packet ofp is the message which triggered this processing, if any (used for error generation) """ assert assert_type("packet", packet, (ethernet, bytes), none_ok=False) if not isinstance(packet, ethernet): packet = ethernet.unpack(packet) for action in actions: #if action.type is ofp_action_resubmit: # self.rx_packet(packet, in_port) # return h = self.action_handlers.get(action.type) if h is None: self.log.warn("Unknown action type: %x " % (action.type, )) self.send_error(type=OFPET_BAD_ACTION, code=OFPBAC_BAD_TYPE, ofp=ofp) return packet = h(action, packet, in_port) def _flow_mod_add(self, flow_mod, connection, table): """ Process an OFPFC_ADD flow mod sent to the switch. """ match = flow_mod.match priority = flow_mod.priority new_entry = TableEntry.from_flow_mod(flow_mod) if flow_mod.flags & OFPFF_CHECK_OVERLAP: if table.check_for_overlapping_entry(new_entry): # Another entry overlaps. Do not add. self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_OVERLAP, ofp=flow_mod, connection=connection) return if flow_mod.command == OFPFC_ADD: # Exactly matching entries have to be removed if OFPFC_ADD table.remove_matching_entries(match, priority=priority, strict=True) if len(table) >= self.max_entries: # Flow table is full. Respond with error message. self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_ALL_TABLES_FULL, ofp=flow_mod, connection=connection) return table.add_entry(new_entry) def _flow_mod_modify(self, flow_mod, connection, table, strict=False): """ Process an OFPFC_MODIFY flow mod sent to the switch. """ match = flow_mod.match priority = flow_mod.priority modified = False for entry in table.entries: # update the actions field in the matching flows if entry.is_matched_by(match, priority=priority, strict=strict): entry.actions = flow_mod.actions modified = True if not modified: # if no matching entry is found, modify acts as add self._flow_mod_add(flow_mod, connection, table) def _flow_mod_modify_strict(self, flow_mod, connection, table): """ Process an OFPFC_MODIFY_STRICT flow mod sent to the switch. """ self._flow_mod_modify(flow_mod, connection, table, strict=True) def _flow_mod_delete(self, flow_mod, connection, table, strict=False): """ Process an OFPFC_DELETE flow mod sent to the switch. """ match = flow_mod.match priority = flow_mod.priority out_port = flow_mod.out_port if out_port == OFPP_NONE: out_port = None # Don't filter table.remove_matching_entries(match, priority=priority, strict=strict, out_port=out_port, reason=OFPRR_DELETE) def _flow_mod_delete_strict(self, flow_mod, connection, table): """ Process an OFPFC_DELETE_STRICT flow mod sent to the switch. """ self._flow_mod_delete(flow_mod, connection, table, strict=True) def _action_output(self, action, packet, in_port): self._output_packet(packet, action.port, in_port, action.max_len) return packet def _action_set_vlan_vid(self, action, packet, in_port): if not isinstance(packet.payload, vlan): vl = vlan() vl.eth_type = packet.type vl.payload = packet.payload packet.type = ethernet.VLAN_TYPE packet.payload = vl packet.payload.id = action.vlan_vid return packet def _action_set_vlan_pcp(self, action, packet, in_port): if not isinstance(packet.payload, vlan): vl = vlan() vl.payload = packet.payload vl.eth_type = packet.type packet.payload = vl packet.type = ethernet.VLAN_TYPE packet.payload.pcp = action.vlan_pcp return packet def _action_strip_vlan(self, action, packet, in_port): if isinstance(packet.payload, vlan): packet.type = packet.payload.eth_type packet.payload = packet.payload.payload return packet def _action_set_dl_src(self, action, packet, in_port): packet.src = action.dl_addr return packet def _action_set_dl_dst(self, action, packet, in_port): packet.dst = action.dl_addr return packet def _action_set_nw_src(self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): nw.srcip = action.nw_addr return packet def _action_set_nw_dst(self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): nw.dstip = action.nw_addr return packet def _action_set_nw_tos(self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): nw.tos = action.nw_tos return packet def _action_set_tp_src(self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): tp = nw.payload if isinstance(tp, udp) or isinstance(tp, tcp): tp.srcport = action.tp_port return packet def _action_set_tp_dst(self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): tp = nw.payload if isinstance(tp, udp) or isinstance(tp, tcp): tp.dstport = action.tp_port return packet def _action_enqueue(self, action, packet, in_port): self.log.warn("Enqueue not supported. Performing regular output.") self._output_packet(packet, action.tp_port, in_port) return packet # def _action_push_mpls_tag (self, action, packet, in_port): # bottom_of_stack = isinstance(packet.next, mpls) # packet.next = mpls(prev = packet.pack()) # if bottom_of_stack: # packet.next.s = 1 # packet.type = action.ethertype # return packet # def _action_pop_mpls_tag (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # return packet # if not isinstance(packet.next.next, str): # packet.next.next = packet.next.next.pack() # if action.ethertype in ethernet.type_parsers: # packet.next = ethernet.type_parsers[action.ethertype](packet.next.next) # else: # packet.next = packet.next.next # packet.ethertype = action.ethertype # return packet # def _action_set_mpls_label (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # mock = ofp_action_push_mpls() # packet = push_mpls_tag(mock, packet) # packet.next.label = action.mpls_label # return packet # def _action_set_mpls_tc (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # mock = ofp_action_push_mpls() # packet = push_mpls_tag(mock, packet) # packet.next.tc = action.mpls_tc # return packet # def _action_set_mpls_ttl (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # mock = ofp_action_push_mpls() # packet = push_mpls_tag(mock, packet) # packet.next.ttl = action.mpls_ttl # return packet # def _action_dec_mpls_ttl (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # return packet # packet.next.ttl = packet.next.ttl - 1 # return packet def _stats_desc(self, ofp, connection): try: from pox.core import core return ofp_desc_stats(mfr_desc="POX", hw_desc=core._get_platform_info(), sw_desc=core.version_string, serial_num=str(self.dpid), dp_desc=type(self).__name__) except: return ofp_desc_stats(mfr_desc="POX", hw_desc="Unknown", sw_desc="Unknown", serial_num=str(self.dpid), dp_desc=type(self).__name__) def _stats_flow(self, ofp, connection): if ofp.body.table_id not in (TABLE_ALL, 0): return [] # No flows for other tables out_port = ofp.body.out_port if out_port == OFPP_NONE: out_port = None # Don't filter return self.table.flow_stats(ofp.body.match, out_port) def _stats_aggregate(self, ofp, connection): if ofp.body.table_id not in (TABLE_ALL, 0): return [] # No flows for other tables out_port = ofp.body.out_port if out_port == OFPP_NONE: out_port = None # Don't filter return self.table.aggregate_stats(ofp.body.match, out_port) def _stats_table(self, ofp, connection): # Some of these may come from the actual table(s) in the future... r = ofp_table_stats() r.table_id = 0 r.name = "Default" r.wildcards = OFPFW_ALL r.max_entries = self.max_entries r.active_count = len(self.table) r.lookup_count = self._lookup_count r.matched_count = self._matched_count return r def _stats_port(self, ofp, connection): req = ofp.body if req.port_no == OFPP_NONE: return self.port_stats.values() else: return self.port_stats[req.port_no] def _stats_queue(self, ofp, connection): # We don't support queues whatsoever so either send an empty list or send # an OFP_ERROR if an actual queue is requested. req = ofp.body #if req.port_no != OFPP_ALL: # self.send_error(type=OFPET_QUEUE_OP_FAILED, code=OFPQOFC_BAD_PORT, # ofp=ofp, connection=connection) # Note: We don't care about this case for now, even if port_no is bogus. if req.queue_id == OFPQ_ALL: return [] else: self.send_error(type=OFPET_QUEUE_OP_FAILED, code=OFPQOFC_BAD_QUEUE, ofp=ofp, connection=connection) def __repr__(self): return "%s(dpid=%s, num_ports=%d)" % ( type(self).__name__, dpid_to_str(self.dpid), len(self.ports))
def __init__ (self, dpid, name=None, ports=4, miss_send_len=128, max_buffers=100, max_entries=0x7fFFffFF, features=None): """ Initialize switch - ports is a list of ofp_phy_ports or a number of ports - miss_send_len is number of bytes to send to controller on table miss - max_buffers is number of buffered packets to store - max_entries is max flows entries per table """ if name is None: name = dpid_to_str(dpid) self.name = name self.dpid = dpid if isinstance(ports, int): ports = [self.generate_port(i) for i in range(1, ports+1)] self.max_buffers = max_buffers self.max_entries = max_entries self.miss_send_len = miss_send_len self.config_flags = 0 self._has_sent_hello = False self.table = FlowTable() self.table.addListeners(self) self._lookup_count = 0 self._matched_count = 0 self.log = logging.getLogger(self.name) self._connection = None # buffer for packets during packet_in self._packet_buffer = [] # Map port_no -> openflow.pylibopenflow_01.ofp_phy_ports self.ports = {} self.port_stats = {} for port in ports: self.add_port(port) if features is not None: self.features = features else: # Set up default features self.features = SwitchFeatures() self.features.cap_flow_stats = True self.features.cap_table_stats = True self.features.cap_port_stats = True #self.features.cap_stp = True #self.features.cap_ip_reasm = True #self.features.cap_queue_stats = True #self.features.cap_arp_match_ip = True self.features.act_output = True self.features.act_enqueue = True self.features.act_strip_vlan = True self.features.act_set_vlan_vid = True self.features.act_set_vlan_pcp = True self.features.act_set_dl_dst = True self.features.act_set_dl_src = True self.features.act_set_nw_dst = True self.features.act_set_nw_src = True self.features.act_set_nw_tos = True self.features.act_set_tp_dst = True self.features.act_set_tp_src = True #self.features.act_vendor = True # Set up handlers for incoming OpenFlow messages # That is, self.ofp_handlers[OFPT_FOO] = self._rx_foo self.ofp_handlers = {} for value,name in ofp_type_map.iteritems(): name = name.split("OFPT_",1)[-1].lower() h = getattr(self, "_rx_" + name, None) if not h: continue assert of._message_type_to_class[value]._from_controller, name self.ofp_handlers[value] = h # Set up handlers for actions # That is, self.action_handlers[OFPAT_FOO] = self._action_foo #TODO: Refactor this with above self.action_handlers = {} for value,name in ofp_action_type_map.iteritems(): name = name.split("OFPAT_",1)[-1].lower() h = getattr(self, "_action_" + name, None) if not h: continue if getattr(self.features, "act_" + name) is False: continue self.action_handlers[value] = h # Set up handlers for stats handlers # That is, self.stats_handlers[OFPST_FOO] = self._stats_foo #TODO: Refactor this with above self.stats_handlers = {} for value,name in ofp_stats_type_map.iteritems(): name = name.split("OFPST_",1)[-1].lower() h = getattr(self, "_stats_" + name, None) if not h: continue self.stats_handlers[value] = h # Set up handlers for flow mod handlers # That is, self.flow_mod_handlers[OFPFC_FOO] = self._flow_mod_foo #TODO: Refactor this with above self.flow_mod_handlers = {} for name,value in ofp_flow_mod_command_rev_map.iteritems(): name = name.split("OFPFC_",1)[-1].lower() h = getattr(self, "_flow_mod_" + name, None) if not h: continue self.flow_mod_handlers[value] = h
class SoftwareSwitchBase (object): def __init__ (self, dpid, name=None, ports=4, miss_send_len=128, max_buffers=100, max_entries=0x7fFFffFF, features=None): """ Initialize switch - ports is a list of ofp_phy_ports or a number of ports - miss_send_len is number of bytes to send to controller on table miss - max_buffers is number of buffered packets to store - max_entries is max flows entries per table """ if name is None: name = dpid_to_str(dpid) self.name = name self.dpid = dpid if isinstance(ports, int): ports = [self.generate_port(i) for i in range(1, ports+1)] self.max_buffers = max_buffers self.max_entries = max_entries self.miss_send_len = miss_send_len self.config_flags = 0 self._has_sent_hello = False self.table = FlowTable() self.table.addListeners(self) self._lookup_count = 0 self._matched_count = 0 self.log = logging.getLogger(self.name) self._connection = None # buffer for packets during packet_in self._packet_buffer = [] # Map port_no -> openflow.pylibopenflow_01.ofp_phy_ports self.ports = {} self.port_stats = {} for port in ports: self.add_port(port) if features is not None: self.features = features else: # Set up default features self.features = SwitchFeatures() self.features.cap_flow_stats = True self.features.cap_table_stats = True self.features.cap_port_stats = True #self.features.cap_stp = True #self.features.cap_ip_reasm = True #self.features.cap_queue_stats = True #self.features.cap_arp_match_ip = True self.features.act_output = True self.features.act_enqueue = True self.features.act_strip_vlan = True self.features.act_set_vlan_vid = True self.features.act_set_vlan_pcp = True self.features.act_set_dl_dst = True self.features.act_set_dl_src = True self.features.act_set_nw_dst = True self.features.act_set_nw_src = True self.features.act_set_nw_tos = True self.features.act_set_tp_dst = True self.features.act_set_tp_src = True #self.features.act_vendor = True # Set up handlers for incoming OpenFlow messages # That is, self.ofp_handlers[OFPT_FOO] = self._rx_foo self.ofp_handlers = {} for value,name in ofp_type_map.iteritems(): name = name.split("OFPT_",1)[-1].lower() h = getattr(self, "_rx_" + name, None) if not h: continue assert of._message_type_to_class[value]._from_controller, name self.ofp_handlers[value] = h # Set up handlers for actions # That is, self.action_handlers[OFPAT_FOO] = self._action_foo #TODO: Refactor this with above self.action_handlers = {} for value,name in ofp_action_type_map.iteritems(): name = name.split("OFPAT_",1)[-1].lower() h = getattr(self, "_action_" + name, None) if not h: continue if getattr(self.features, "act_" + name) is False: continue self.action_handlers[value] = h # Set up handlers for stats handlers # That is, self.stats_handlers[OFPST_FOO] = self._stats_foo #TODO: Refactor this with above self.stats_handlers = {} for value,name in ofp_stats_type_map.iteritems(): name = name.split("OFPST_",1)[-1].lower() h = getattr(self, "_stats_" + name, None) if not h: continue self.stats_handlers[value] = h # Set up handlers for flow mod handlers # That is, self.flow_mod_handlers[OFPFC_FOO] = self._flow_mod_foo #TODO: Refactor this with above self.flow_mod_handlers = {} for name,value in ofp_flow_mod_command_rev_map.iteritems(): name = name.split("OFPFC_",1)[-1].lower() h = getattr(self, "_flow_mod_" + name, None) if not h: continue self.flow_mod_handlers[value] = h def _gen_port_name (self, port_no): return "%s.%s"%(dpid_to_str(self.dpid, True).replace('-','')[:12], port_no) def _gen_ethaddr (self, port_no): return EthAddr("02%06x%04x" % (self.dpid % 0x00FFff, port_no % 0xffFF)) def generate_port (self, port_no, name = None, ethaddr = None): dpid = self.dpid p = ofp_phy_port() p.port_no = port_no if ethaddr is None: p.hw_addr = self._gen_ethaddr(p.port_no) else: p.hw_addr = EthAddr(ethaddr) if name is None: p.name = self._gen_port_name(p.port_no) else: p.name = name # Fill in features sort of arbitrarily p.config = OFPPC_NO_STP p.curr = OFPPF_10MB_HD p.advertised = OFPPF_10MB_HD p.supported = OFPPF_10MB_HD p.peer = OFPPF_10MB_HD return p @property def _time (self): """ Get the current time This should be used for, e.g., calculating timeouts. It currently isn't used everywhere it should be. Override this to change time behavior. """ return time.time() def _handle_FlowTableModification (self, event): """ Handle flow table modification events """ # Currently, we only use this for sending flow_removed messages if not event.removed: return if event.reason in (OFPRR_IDLE_TIMEOUT,OFPRR_HARD_TIMEOUT,OFPRR_DELETE): # These reasons may lead to a flow_removed count = 0 for entry in event.removed: if entry.flags & OFPFF_SEND_FLOW_REM and not entry.flags & OFPFF_EMERG: # Flow wants removal notification -- send it fr = entry.to_flow_removed(self._time, reason=event.reason) self.send(fr) count += 1 self.log.debug("%d flows removed (%d removal notifications)", len(event.removed), count) def rx_message (self, connection, msg): """ Handle an incoming OpenFlow message """ ofp_type = msg.header_type h = self.ofp_handlers.get(ofp_type) if h is None: raise RuntimeError("No handler for ofp_type %s(%d)" % (ofp_type_map.get(ofp_type), ofp_type)) self.log.debug("Got %s with XID %s",ofp_type_map.get(ofp_type),msg.xid) h(msg, connection=connection) def set_connection (self, connection): """ Set this switch's connection. """ self._has_sent_hello = False connection.set_message_handler(self.rx_message) self._connection = connection def send (self, message, connection = None): """ Send a message to this switch's communication partner """ if connection is None: connection = self._connection if connection: connection.send(message) else: self.log.debug("Asked to send message %s, but not connected", message) def _rx_hello (self, ofp, connection): #FIXME: This isn't really how hello is supposed to work -- we're supposed # to send it immediately on connection. See _send_hello(). self.send_hello() def _rx_echo_request (self, ofp, connection): """ Handles echo requests """ msg = ofp_echo_reply(xid=ofp.xid, body=ofp.body) self.send(msg) def _rx_features_request (self, ofp, connection): """ Handles feature requests """ self.log.debug("Send features reply") msg = ofp_features_reply(datapath_id = self.dpid, xid = ofp.xid, n_buffers = self.max_buffers, n_tables = 1, capabilities = self.features.capability_bits, actions = self.features.action_bits, ports = self.ports.values()) self.send(msg) def _rx_flow_mod (self, ofp, connection): """ Handles flow mods """ self.log.debug("Flow mod details: %s", ofp.show()) #self.table.process_flow_mod(ofp) #self._process_flow_mod(ofp, connection=connection, table=self.table) handler = self.flow_mod_handlers.get(ofp.command) if handler is None: self.log.warn("Command not implemented: %s" % command) self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_BAD_COMMAND, ofp=ofp, connection=connection) return handler(flow_mod=ofp, connection=connection, table=self.table) if ofp.buffer_id is not None: self._process_actions_for_packet_from_buffer(ofp.actions, ofp.buffer_id, ofp) def _rx_packet_out (self, packet_out, connection): """ Handles packet_outs """ self.log.debug("Packet out details: %s", packet_out.show()) if packet_out.data: self._process_actions_for_packet(packet_out.actions, packet_out.data, packet_out.in_port, packet_out) elif packet_out.buffer_id is not None: self._process_actions_for_packet_from_buffer(packet_out.actions, packet_out.buffer_id, packet_out) else: self.log.warn("packet_out: No data and no buffer_id -- " "don't know what to send") def _rx_echo_reply (self, ofp, connection): pass def _rx_barrier_request (self, ofp, connection): msg = ofp_barrier_reply(xid = ofp.xid) self.send(msg) def _rx_get_config_request (self, ofp, connection): msg = ofp_get_config_reply(xid = ofp.xid) msg.miss_send_len = self.miss_send_len msg.flags = self.config_flags self.log.debug("Sending switch config reply %s", msg) self.send(msg) def _rx_stats_request (self, ofp, connection): handler = self.stats_handlers.get(ofp.type) if handler is None: self.log.warning("Stats type %s not implemented", ofp.type) self.send_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_STAT, ofp=ofp, connection=connection) return body = handler(ofp, connection=connection) if body is not None: reply = ofp_stats_reply(xid=ofp.xid, type=ofp.type, body=body) self.log.debug("Sending stats reply %s", reply) self.send(reply) def _rx_set_config (self, config, connection): self.miss_send_len = config.miss_send_len self.config_flags = config.flags def _rx_port_mod (self, port_mod, connection): port_no = port_mod.port_no if port_no not in self.ports: self.send_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_PORT, ofp=port_mod, connection=connection) return port = self.ports[port_no] if port.hw_addr != port_mod.hw_addr: self.send_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_HW_ADDR, ofp=port_mod, connection=connection) return mask = port_mod.mask for bit in range(32): bit = 1 << bit if mask & bit: handled,r = self._set_port_config_bit(port, bit, port_mod.config & bit) if not handled: self.log.warn("Unsupported port config flag: %08x", bit) continue if r is not None: msg = "Port %s: " % (port.port_no,) if isinstance(r, str): msg += r else: msg += ofp_port_config_map.get(bit, "config bit %x" % (bit,)) msg += " set to " msg += "true" if r else "false" self.log.debug(msg) def _rx_vendor (self, vendor, connection): # We don't support vendor extensions, so send an OFP_ERROR, per # page 42 of spec self.send_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_VENDOR, ofp=vendor, connection=connection) def _rx_queue_get_config_request (self, ofp, connection): """ Handles an OFPT_QUEUE_GET_CONFIG_REQUEST message. """ reply = ofp_queue_get_config_reply(xid=ofp.xid, port=ofp.port, queues=[]) self.log.debug("Sending queue get config reply %s", reply) self.send(reply) def send_hello (self, force = False): """ Send hello (once) """ #FIXME: This is wrong -- we should just send when connecting. if self._has_sent_hello and not force: return self._has_sent_hello = True self.log.debug("Sent hello") msg = ofp_hello(xid=0) self.send(msg) def send_packet_in (self, in_port, buffer_id=None, packet=b'', reason=None, data_length=None): """ Send PacketIn """ if hasattr(packet, 'pack'): packet = packet.pack() assert assert_type("packet", packet, bytes) self.log.debug("Send PacketIn") if reason is None: reason = OFPR_NO_MATCH if data_length is not None and len(packet) > data_length: if buffer_id is not None: packet = packet[:data_length] msg = ofp_packet_in(xid = 0, in_port = in_port, buffer_id = buffer_id, reason = reason, data = packet) self.send(msg) def send_port_status (self, port, reason): """ Send port status port is an ofp_phy_port reason is one of OFPPR_xxx """ assert assert_type("port", port, ofp_phy_port, none_ok=False) assert reason in ofp_port_reason_rev_map.values() msg = ofp_port_status(desc=port, reason=reason) self.send(msg) def send_error (self, type, code, ofp=None, data=None, connection=None): """ Send an error If you pass ofp, it will be used as the source of the error's XID and data. You can override the data by also specifying data. """ err = ofp_error(type=type, code=code) if ofp: err.xid = ofp.xid err.data = ofp.pack() else: err.xid = 0 if data is not None: err.data = data self.send(err, connection = connection) def rx_packet (self, packet, in_port, packet_data = None): """ process a dataplane packet packet: an instance of ethernet in_port: the integer port number packet_data: packed version of packet if available """ assert assert_type("packet", packet, ethernet, none_ok=False) assert assert_type("in_port", in_port, int, none_ok=False) port = self.ports.get(in_port) if port is None: self.log.warn("Got packet on missing port %i", in_port) return is_stp = packet.dst == _STP_MAC if (port.config & OFPPC_NO_RECV) and not is_stp: # Drop all except STP return if (port.config & OFPPC_NO_RECV_STP) and is_stp: # Drop STP return if self.config_flags & OFPC_FRAG_MASK: ipp = packet.find(ipv4) if ipp: if (ipp.flags & ipv4.MF_FLAG) or ipp.frag != 0: frag_mode = self.config_flags & OFPC_FRAG_MASK if frag_mode == OFPC_FRAG_DROP: # Drop fragment return elif frag_mode == OFPC_FRAG_REASM: if self.features.cap_ip_reasm: #TODO: Implement fragment reassembly self.log.info("Can't reassemble fragment: not implemented") else: self.log.warn("Illegal fragment processing mode: %i", frag_mode) self.port_stats[in_port].rx_packets += 1 if packet_data is not None: self.port_stats[in_port].rx_bytes += len(packet_data) else: self.port_stats[in_port].rx_bytes += len(packet.pack()) # Expensive self._lookup_count += 1 entry = self.table.entry_for_packet(packet, in_port) if entry is not None: self._matched_count += 1 entry.touch_packet(len(packet)) self._process_actions_for_packet(entry.actions, packet, in_port) else: # no matching entry if port.config & OFPPC_NO_PACKET_IN: return buffer_id = self._buffer_packet(packet, in_port) if packet_data is None: packet_data = packet.pack() self.send_packet_in(in_port, buffer_id, packet_data, reason=OFPR_NO_MATCH, data_length=self.miss_send_len) def delete_port (self, port): """ Removes a port Sends a port_status message to the controller Returns the removed phy_port """ try: port_no = port.port_no assert self.ports[port_no] is port except: port_no = port port = self.ports[port_no] if port_no not in self.ports: raise RuntimeError("Can't remove nonexistent port " + str(port_no)) self.send_port_status(port, OFPPR_DELETE) del self.ports[port_no] return port def add_port (self, port): """ Adds a port Sends a port_status message to the controller """ try: port_no = port.port_no except: port_no = port port = self.generate_port(port_no, self.dpid) if port_no in self.ports: raise RuntimeError("Port %s already exists" % (port_no,)) self.ports[port_no] = port self.port_stats[port.port_no] = ofp_port_stats(port_no=port.port_no) self.send_port_status(port, OFPPR_ADD) def _set_port_config_bit (self, port, bit, value): """ Set a port config bit This is called in response to port_mods. It is passed the ofp_phy_port, the bit/mask, and the value of the bit (i.e., 0 if the flag is to be unset, or the same value as bit if it is to be set). The return value is a tuple (handled, msg). If bit is handled, then handled will be True, else False. if msg is a string, it will be used as part of a log message. If msg is None, there will be no log message. If msg is anything else "truthy", an "enabled" log message is generated. If msg is anything else "falsy", a "disabled" log message is generated. msg is only used when handled is True. """ if bit == OFPPC_NO_STP: if value == 0: # we also might send OFPBRC_EPERM if trying to disable this bit self.log.warn("Port %s: Can't enable 802.1D STP", port.port_no) return (True, None) if bit not in (OFPPC_PORT_DOWN, OFPPC_NO_STP, OFPPC_NO_RECV, OFPPC_NO_RECV_STP, OFPPC_NO_FLOOD, OFPPC_NO_FWD, OFPPC_NO_PACKET_IN): return (False, None) if port.set_config(value, bit): if bit == OFPPC_PORT_DOWN: # Note (Peter Peresini): Although the spec is not clear about it, # we will assume that config.OFPPC_PORT_DOWN implies # state.OFPPS_LINK_DOWN. This is consistent with Open vSwitch. #TODO: for now, we assume that there is always physical link present # and that the link state depends only on the configuration. old_state = port.state & OFPPS_LINK_DOWN port.state = port.state & ~OFPPS_LINK_DOWN if port.config & OFPPC_PORT_DOWN: port.state = port.state | OFPPS_LINK_DOWN new_state = port.state & OFPPS_LINK_DOWN if old_state != new_state: self.send_port_status(port, OFPPR_MODIFY) # Do default log message. return (True, value) # No change -- no log message. return (True, None) def _output_packet_physical (self, packet, port_no): """ send a packet out a single physical port This is called by the more general _output_packet(). Override this. """ self.log.info("Sending packet %s out port %s", str(packet), port_no) def _output_packet (self, packet, out_port, in_port, max_len=None): """ send a packet out some port This handles virtual ports and does validation. packet: instance of ethernet out_port, in_port: the integer port number max_len: maximum packet payload length to send to controller """ assert assert_type("packet", packet, ethernet, none_ok=False) def real_send (port_no, allow_in_port=False): if type(port_no) == ofp_phy_port: port_no = port_no.port_no if port_no == in_port and not allow_in_port: self.log.warn("Dropping packet sent on port %i: Input port", port_no) return if port_no not in self.ports: self.log.warn("Dropping packet sent on port %i: Invalid port", port_no) return if self.ports[port_no].config & OFPPC_NO_FWD: self.log.warn("Dropping packet sent on port %i: Forwarding disabled", port_no) return if self.ports[port_no].config & OFPPC_PORT_DOWN: self.log.warn("Dropping packet sent on port %i: Port down", port_no) return if self.ports[port_no].state & OFPPS_LINK_DOWN: self.log.debug("Dropping packet sent on port %i: Link down", port_no) return self.port_stats[port_no].tx_packets += 1 self.port_stats[port_no].tx_bytes += len(packet.pack()) #FIXME: Expensive self._output_packet_physical(packet, port_no) if out_port < OFPP_MAX: real_send(out_port) elif out_port == OFPP_IN_PORT: real_send(in_port, allow_in_port=True) elif out_port == OFPP_FLOOD: for no,port in self.ports.iteritems(): if no == in_port: continue if port.config & OFPPC_NO_FLOOD: continue real_send(port) elif out_port == OFPP_ALL: for no,port in self.ports.iteritems(): if no == in_port: continue real_send(port) elif out_port == OFPP_CONTROLLER: buffer_id = self._buffer_packet(packet, in_port) # Should we honor OFPPC_NO_PACKET_IN here? self.send_packet_in(in_port, buffer_id, packet, reason=OFPR_ACTION, data_length=max_len) elif out_port == OFPP_TABLE: # Do we disable send-to-controller when performing this? # (Currently, there's the possibility that a table miss from this # will result in a send-to-controller which may send back to table...) self.rx_packet(packet, in_port) else: self.log.warn("Unsupported virtual output port: %d", out_port) def _buffer_packet (self, packet, in_port=None): """ Buffer packet and return buffer ID If no buffer is available, return None. """ # Do we have an empty slot? for (i, value) in enumerate(self._packet_buffer): if value is None: # Yes -- use it self._packet_buffer[i] = (packet, in_port) return i + 1 # No -- create a new slow if len(self._packet_buffer) >= self.max_buffers: # No buffers available! return None self._packet_buffer.append( (packet, in_port) ) return len(self._packet_buffer) def _process_actions_for_packet_from_buffer (self, actions, buffer_id, ofp=None): """ output and release a packet from the buffer ofp is the message which triggered this processing, if any (used for error generation) """ buffer_id = buffer_id - 1 if (buffer_id >= len(self._packet_buffer)) or (buffer_id < 0): self.log.warn("Invalid output buffer id: %d", buffer_id + 1) return if self._packet_buffer[buffer_id] is None: self.log.warn("Buffer %d has already been flushed", buffer_id + 1) return (packet, in_port) = self._packet_buffer[buffer_id] self._process_actions_for_packet(actions, packet, in_port, ofp) self._packet_buffer[buffer_id] = None def _process_actions_for_packet (self, actions, packet, in_port, ofp=None): """ process the output actions for a packet ofp is the message which triggered this processing, if any (used for error generation) """ assert assert_type("packet", packet, (ethernet, bytes), none_ok=False) if not isinstance(packet, ethernet): packet = ethernet.unpack(packet) for action in actions: #if action.type is ofp_action_resubmit: # self.rx_packet(packet, in_port) # return h = self.action_handlers.get(action.type) if h is None: self.log.warn("Unknown action type: %x " % (action.type,)) self.send_error(type=OFPET_BAD_ACTION, code=OFPBAC_BAD_TYPE, ofp=ofp) return packet = h(action, packet, in_port) def _flow_mod_add (self, flow_mod, connection, table): """ Process an OFPFC_ADD flow mod sent to the switch. """ match = flow_mod.match priority = flow_mod.priority if flow_mod.flags & OFPFF_EMERG: if flow_mod.idle_timeout != 0 or flow_mod.hard_timeout != 0: # Emergency flow mod has non-zero timeouts. Do not add. self.log.warn("Rejecting emergency flow with nonzero timeout") self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_BAD_EMERG_TIMEOUT, ofp=flow_mod, connection=connection) return if flow_mod.flags & OFPFF_SEND_FLOW_REM: # Emergency flows can't send removal messages, we we might want to # reject this early. Sadly, there's no error code for this, so we just # abuse EPERM. If we eventually support Nicira extended error codes, # we should use one here. self.log.warn("Rejecting emergency flow with flow removal flag") self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_EPERM, ofp=flow_mod, connection=connection) return #NOTE: An error is sent anyways because the current implementation does # not support emergency entries. self.log.warn("Rejecting emergency flow (not supported)") self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_ALL_TABLES_FULL, ofp=flow_mod, connection=connection) return new_entry = TableEntry.from_flow_mod(flow_mod) if flow_mod.flags & OFPFF_CHECK_OVERLAP: if table.check_for_overlapping_entry(new_entry): # Another entry overlaps. Do not add. self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_OVERLAP, ofp=flow_mod, connection=connection) return if flow_mod.command == OFPFC_ADD: # Exactly matching entries have to be removed if OFPFC_ADD table.remove_matching_entries(match, priority=priority, strict=True) if len(table) >= self.max_entries: # Flow table is full. Respond with error message. self.send_error(type=OFPET_FLOW_MOD_FAILED, code=OFPFMFC_ALL_TABLES_FULL, ofp=flow_mod, connection=connection) return table.add_entry(new_entry) def _flow_mod_modify (self, flow_mod, connection, table, strict=False): """ Process an OFPFC_MODIFY flow mod sent to the switch. """ match = flow_mod.match priority = flow_mod.priority modified = False for entry in table.entries: # update the actions field in the matching flows if entry.is_matched_by(match, priority=priority, strict=strict): entry.actions = flow_mod.actions modified = True if not modified: # if no matching entry is found, modify acts as add self._flow_mod_add(flow_mod, connection, table) def _flow_mod_modify_strict (self, flow_mod, connection, table): """ Process an OFPFC_MODIFY_STRICT flow mod sent to the switch. """ self._flow_mod_modify(flow_mod, connection, table, strict=True) def _flow_mod_delete (self, flow_mod, connection, table, strict=False): """ Process an OFPFC_DELETE flow mod sent to the switch. """ match = flow_mod.match priority = flow_mod.priority out_port = flow_mod.out_port if out_port == OFPP_NONE: out_port = None # Don't filter table.remove_matching_entries(match, priority=priority, strict=strict, out_port=out_port, reason=OFPRR_DELETE) def _flow_mod_delete_strict (self, flow_mod, connection, table): """ Process an OFPFC_DELETE_STRICT flow mod sent to the switch. """ self._flow_mod_delete(flow_mod, connection, table, strict=True) def _action_output (self, action, packet, in_port): self._output_packet(packet, action.port, in_port, action.max_len) return packet def _action_set_vlan_vid (self, action, packet, in_port): if not isinstance(packet.payload, vlan): vl = vlan() vl.eth_type = packet.type vl.payload = packet.payload packet.type = ethernet.VLAN_TYPE packet.payload = vl packet.payload.id = action.vlan_vid return packet def _action_set_vlan_pcp (self, action, packet, in_port): if not isinstance(packet.payload, vlan): vl = vlan() vl.payload = packet.payload vl.eth_type = packet.type packet.payload = vl packet.type = ethernet.VLAN_TYPE packet.payload.pcp = action.vlan_pcp return packet def _action_strip_vlan (self, action, packet, in_port): if isinstance(packet.payload, vlan): packet.type = packet.payload.eth_type packet.payload = packet.payload.payload return packet def _action_set_dl_src (self, action, packet, in_port): packet.src = action.dl_addr return packet def _action_set_dl_dst (self, action, packet, in_port): packet.dst = action.dl_addr return packet def _action_set_nw_src (self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): nw.srcip = action.nw_addr return packet def _action_set_nw_dst (self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): nw.dstip = action.nw_addr return packet def _action_set_nw_tos (self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): nw.tos = action.nw_tos return packet def _action_set_tp_src (self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): tp = nw.payload if isinstance(tp, udp) or isinstance(tp, tcp): tp.srcport = action.tp_port return packet def _action_set_tp_dst (self, action, packet, in_port): nw = packet.payload if isinstance(nw, vlan): nw = nw.payload if isinstance(nw, ipv4): tp = nw.payload if isinstance(tp, udp) or isinstance(tp, tcp): tp.dstport = action.tp_port return packet def _action_enqueue (self, action, packet, in_port): self.log.warn("Enqueue not supported. Performing regular output.") self._output_packet(packet, action.tp_port, in_port) return packet # def _action_push_mpls_tag (self, action, packet, in_port): # bottom_of_stack = isinstance(packet.next, mpls) # packet.next = mpls(prev = packet.pack()) # if bottom_of_stack: # packet.next.s = 1 # packet.type = action.ethertype # return packet # def _action_pop_mpls_tag (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # return packet # if not isinstance(packet.next.next, str): # packet.next.next = packet.next.next.pack() # if action.ethertype in ethernet.type_parsers: # packet.next = ethernet.type_parsers[action.ethertype](packet.next.next) # else: # packet.next = packet.next.next # packet.ethertype = action.ethertype # return packet # def _action_set_mpls_label (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # mock = ofp_action_push_mpls() # packet = push_mpls_tag(mock, packet) # packet.next.label = action.mpls_label # return packet # def _action_set_mpls_tc (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # mock = ofp_action_push_mpls() # packet = push_mpls_tag(mock, packet) # packet.next.tc = action.mpls_tc # return packet # def _action_set_mpls_ttl (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # mock = ofp_action_push_mpls() # packet = push_mpls_tag(mock, packet) # packet.next.ttl = action.mpls_ttl # return packet # def _action_dec_mpls_ttl (self, action, packet, in_port): # if not isinstance(packet.next, mpls): # return packet # packet.next.ttl = packet.next.ttl - 1 # return packet def _stats_desc (self, ofp, connection): try: from pox.core import core return ofp_desc_stats(mfr_desc="POX", hw_desc=core._get_platform_info(), sw_desc=core.version_string, serial_num=str(self.dpid), dp_desc=type(self).__name__) except: return ofp_desc_stats(mfr_desc="POX", hw_desc="Unknown", sw_desc="Unknown", serial_num=str(self.dpid), dp_desc=type(self).__name__) def _stats_flow (self, ofp, connection): if ofp.body.table_id not in (TABLE_ALL, 0): return [] # No flows for other tables out_port = ofp.body.out_port if out_port == OFPP_NONE: out_port = None # Don't filter return self.table.flow_stats(ofp.body.match, out_port) def _stats_aggregate (self, ofp, connection): if ofp.body.table_id not in (TABLE_ALL, 0): return [] # No flows for other tables out_port = ofp.body.out_port if out_port == OFPP_NONE: out_port = None # Don't filter return self.table.aggregate_stats(ofp.body.match, out_port) def _stats_table (self, ofp, connection): # Some of these may come from the actual table(s) in the future... r = ofp_table_stats() r.table_id = 0 r.name = "Default" r.wildcards = OFPFW_ALL r.max_entries = self.max_entries r.active_count = len(self.table) r.lookup_count = self._lookup_count r.matched_count = self._matched_count return r def _stats_port (self, ofp, connection): req = ofp.body if req.port_no == OFPP_NONE: return self.port_stats.values() else: return self.port_stats[req.port_no] def _stats_queue (self, ofp, connection): # We don't support queues whatsoever so either send an empty list or send # an OFP_ERROR if an actual queue is requested. req = ofp.body #if req.port_no != OFPP_ALL: # self.send_error(type=OFPET_QUEUE_OP_FAILED, code=OFPQOFC_BAD_PORT, # ofp=ofp, connection=connection) # Note: We don't care about this case for now, even if port_no is bogus. if req.queue_id == OFPQ_ALL: return [] else: self.send_error(type=OFPET_QUEUE_OP_FAILED, code=OFPQOFC_BAD_QUEUE, ofp=ofp, connection=connection) def __repr__ (self): return "%s(dpid=%s, num_ports=%d)" % (type(self).__name__, dpid_to_str(self.dpid), len(self.ports))
def table(): t = FlowTable() t.add_entry(TableEntry(priority=6, cookie=0x1, match=ofp_match(dl_src=EthAddr("00:00:00:00:00:01"),nw_src="1.2.3.4"), actions=[ofp_action_output(port=5)])) t.add_entry(TableEntry(priority=5, cookie=0x2, match=ofp_match(dl_src=EthAddr("00:00:00:00:00:02"), nw_src="1.2.3.0/24"), actions=[ofp_action_output(port=6)])) t.add_entry(TableEntry(priority=1, cookie=0x3, match=ofp_match(), actions=[])) return t