def __init__(self, dpid, name=None, ports=4, miss_send_len=128, n_buffers=100, n_tables=1, capabilities=None): """Initialize switch""" ##Datapath id of switch self.dpid = dpid ## Human-readable name of the switch self.name = name if self.name is None: self.name = str(dpid) self.log = logging.getLogger(self.name) ##Number of buffers self.n_buffers = n_buffers ##Number of tables self.n_tables= n_tables # Note that there is one switch table in the OpenFlow 1.0 world self.table = SwitchFlowTable() # buffer for packets during packet_in self.packet_buffer = [] if(ports == None or isinstance(ports, int)): ports=_default_port_list(num_ports=ports, prefix=dpid) self.xid_count = xid_generator(1) ## Hash of 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) ## (OpenFlow Handler map) self.ofp_handlers = { # Reactive handlers ofp_type_rev_map['OFPT_HELLO'] : self._receive_hello, ofp_type_rev_map['OFPT_ECHO_REQUEST'] : self._receive_echo, ofp_type_rev_map['OFPT_FEATURES_REQUEST'] : self._receive_features_request, ofp_type_rev_map['OFPT_FLOW_MOD'] : self._receive_flow_mod, ofp_type_rev_map['OFPT_PACKET_OUT'] : self._receive_packet_out, ofp_type_rev_map['OFPT_BARRIER_REQUEST'] : self._receive_barrier_request, ofp_type_rev_map['OFPT_GET_CONFIG_REQUEST'] : self._receive_get_config_request, ofp_type_rev_map['OFPT_SET_CONFIG'] : self._receive_set_config, ofp_type_rev_map['OFPT_STATS_REQUEST'] : self._receive_stats_request, ofp_type_rev_map['OFPT_VENDOR'] : self._receive_vendor, ofp_type_rev_map['OFPT_PORT_MOD'] : self._receive_port_mod, # Proactive responses ofp_type_rev_map['OFPT_ECHO_REPLY'] : self._receive_echo_reply # TODO: many more packet types to process } self._connection = None ##Capabilities if (isinstance(capabilities, SwitchCapabilities)): self.capabilities = capabilities else: self.capabilities = SwitchCapabilities(miss_send_len)
class SwitchImpl(EventMixin): _eventMixin_events = set([DpPacketOut]) # ports is a list of ofp_phy_ports def __init__(self, dpid, name=None, ports=4, miss_send_len=128, n_buffers=100, n_tables=1, capabilities=None): """Initialize switch""" ##Datapath id of switch self.dpid = dpid ## Human-readable name of the switch self.name = name if self.name is None: self.name = str(dpid) self.log = logging.getLogger(self.name) ##Number of buffers self.n_buffers = n_buffers ##Number of tables self.n_tables= n_tables # Note that there is one switch table in the OpenFlow 1.0 world self.table = SwitchFlowTable() # buffer for packets during packet_in self.packet_buffer = [] if(ports == None or isinstance(ports, int)): ports=_default_port_list(num_ports=ports, prefix=dpid) self.xid_count = xid_generator(1) ## Hash of 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) ## (OpenFlow Handler map) self.ofp_handlers = { # Reactive handlers ofp_type_rev_map['OFPT_HELLO'] : self._receive_hello, ofp_type_rev_map['OFPT_ECHO_REQUEST'] : self._receive_echo, ofp_type_rev_map['OFPT_FEATURES_REQUEST'] : self._receive_features_request, ofp_type_rev_map['OFPT_FLOW_MOD'] : self._receive_flow_mod, ofp_type_rev_map['OFPT_PACKET_OUT'] : self._receive_packet_out, ofp_type_rev_map['OFPT_BARRIER_REQUEST'] : self._receive_barrier_request, ofp_type_rev_map['OFPT_GET_CONFIG_REQUEST'] : self._receive_get_config_request, ofp_type_rev_map['OFPT_SET_CONFIG'] : self._receive_set_config, ofp_type_rev_map['OFPT_STATS_REQUEST'] : self._receive_stats_request, ofp_type_rev_map['OFPT_VENDOR'] : self._receive_vendor, ofp_type_rev_map['OFPT_PORT_MOD'] : self._receive_port_mod, # Proactive responses ofp_type_rev_map['OFPT_ECHO_REPLY'] : self._receive_echo_reply # TODO: many more packet types to process } self._connection = None ##Capabilities if (isinstance(capabilities, SwitchCapabilities)): self.capabilities = capabilities else: self.capabilities = SwitchCapabilities(miss_send_len) def set_io_worker(self, io_worker): self._connection = ControllerConnection(io_worker, self.ofp_handlers) return self._connection def set_connection(self, connection): connection.ofp_handlers = self.ofp_handlers self._connection = connection def send(self, message): """ Send a message to this switches communication partner. If the switch is not connected, the message is silently dropped. """ if self._connection: self._connection.send(message) else: self.log.debug("Asked to send message %s, but not connected" % message) # ==================================== # # Reactive OFP processing # # ==================================== # def _receive_hello(self, ofp): self.log.debug("Receive hello %s" % self.name) # How does the OpenFlow protocol prevent an infinite loop of Hello messages? self.send_hello() def _receive_echo(self, ofp): """Reply to echo request """ self.log.debug("Reply echo of xid: %s %s" % (str(ofp), self.name)) msg = ofp_echo_reply(xid=ofp.xid) self.send(msg) def _receive_features_request(self, ofp): """Reply to feature request """ self.log.debug("Reply features request of xid %s %s" % (str(ofp), self.name)) msg = ofp_features_reply(datapath_id = self.dpid, xid = ofp.xid, n_buffers = self.n_buffers, n_tables = self.n_tables, capabilities = self.capabilities.get_capabilities(), actions = self.capabilities.get_actions(), ports = self.ports.values()) self.send(msg) def _receive_flow_mod(self, ofp): """Handle flow mod: just print it here """ self.log.debug("Flow mod %s: %s" % (self.name, ofp.show())) self.table.process_flow_mod(ofp) if(ofp.buffer_id > 0): self._process_actions_for_packet_from_buffer(ofp.actions, ofp.buffer_id) def _receive_packet_out(self, packet_out): """ Send the packet out the given port """ self.log.debug("Packet out: %s" % packet_out.show()) if(packet_out.data): self._process_actions_for_packet(packet_out.actions, packet_out.data, packet_out.in_port) elif(packet_out.buffer_id > 0): self._process_actions_for_packet_from_buffer(packet_out.actions, packet_out.buffer_id) else: self.log.warn("packet_out: No data and no buffer_id -- don't know what to send") def _receive_echo_reply(self, ofp): self.log.debug("Echo reply: %s %s" % (str(ofp), self.name)) def _receive_barrier_request(self, ofp): self.log.debug("Barrier request %s %s" % (self.name, str(ofp))) msg = ofp_barrier_reply(xid = ofp.xid) self.send(msg) def _receive_get_config_request(self, ofp): self.log.debug("Get config request %s %s " % (self.name, str(ofp))) msg = ofp_get_config_reply(xid = ofp.xid) self.send(msg) def _receive_stats_request(self, ofp): self.log.debug("Get stats request %s %s " % (self.name, str(ofp))) def desc_stats(ofp): return ofp_desc_stats(mfr_desc="BadAssEmulatedPoxSwitch(TM)", hw_desc="your finest emulated asics", sw_desc="pox. reliable, fast, stable. Choose 0 (or more?)", serial_num=str(self.dpid), dp_desc="high performance emuswitch. Several packets per second have been observed (but not by reliable witnesses)") def flow_stats(ofp): req = ofp_flow_stats_request().unpack(ofp.body) assert(self.table_id == TABLE_ALL) return self.table.flow_stats(req.match, req.out_port) def aggregate_stats(ofp): req = ofp_aggregate_stats_request().unpack(ofp.body) assert(self.table_id == TABLE_ALL) return self.table.aggregate_stats(req.match, out_port) def table_stats(ofp): return self.table.table_stats() def port_stats(ofp): req = ofp_port_stats_request().unpack(ofp.body) if req.port_no == OFPP_NONE: res = ofp_port_stats(port_no=OFPP_NONE) for stats in self.port_stats.values(): res += stats return res else: return self.port_stats[req.port_no] def queue_stats(ofp): raise AttributeError("not implemented") stats_handlers = { OFPST_DESC: desc_stats, OFPST_FLOW: flow_stats, OFPST_AGGREGATE: aggregate_stats, OFPST_TABLE: table_stats, OFPST_PORT: port_stats, OFPST_QUEUE: queue_stats } if ofp.type in stats_handlers: handler = stats_handlers[ofp.type] else: raise AttributeError("Unsupported stats request type %d" % ofp.type) reply = ofp_stats_reply(xid=ofp.xid, body=handler(ofp)) self.log.debug("Sending stats reply %s %s" % (self.name, str(reply))) self.send(reply) def _receive_set_config(self, config): self.log.debug("Set config %s %s" % (self.name, str(config))) def _receive_vendor(self, vendor): self.log.debug("Vendor %s %s" % (self.name, str(vendor))) # We don't support vendor extensions, so send an OFP_ERROR, per page 42 of spec err = ofp_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_VENDOR) self.send(err) def _receive_port_mod(self, ofp): self.log.debug("Get port modification request %s %s " % (self.name, str(ofp))) port_no = ofp.port_no if port_no not in self.ports: err = ofp_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_PORT) self.send(err) return port = self.ports[port_no] if port.hw_addr != ofp.hw_addr: err = ofp_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_HW_ADDR) self.send(err) return supported = OFPPC_PORT_DOWN assert (ofp.mask | supported) == supported if ofp.mask & OFPPC_PORT_DOWN: port.config = (port.config & ~OFPPC_PORT_DOWN) | (ofp.config & 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 OpenVSwitch. # FIXME: for now, we assume that there is always physical link present # and that the link state depends only on the configuration. port.state = port.state & ~OFPPS_LINK_DOWN if port.config & OFPPC_PORT_DOWN: port.state = port.state | OFPPS_LINK_DOWN self.send_port_status(port, OFPPR_MODIFY) # ==================================== # # Proactive OFP processing # # ==================================== # def send_hello(self): """Send hello """ self.log.debug("Send hello %s " % self.name) msg = ofp_hello() self.send(msg) def send_packet_in(self, in_port, buffer_id=None, packet="", xid=None, reason=None): """Send PacketIn Assume no match as reason, buffer_id = 0xFFFFFFFF, and empty packet by default """ assert_type("packet", packet, ethernet) self.log.debug("Send PacketIn %s " % self.name) if (reason == None): reason = ofp_packet_in_reason_rev_map['OFPR_NO_MATCH'] if (buffer_id == None): buffer_id = int("0xFFFFFFFF",16) if xid == None: xid = self.xid_count() msg = ofp_packet_in(xid=xid, in_port = in_port, buffer_id = buffer_id, reason = reason, data = packet.pack()) self.send(msg) def send_echo(self, xid=0): """Send echo request """ self.log.debug("Send echo %s" % self.name) msg = ofp_echo_request() self.send(msg) def send_port_status(self, port, reason): ''' port is an ofp_phy_port reason is one of 'OFPPR_ADD', 'OFPPR_DELETE', 'OFPPR_MODIFY' ''' 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) # ==================================== # # Dataplane processing # # ==================================== # def process_packet(self, packet, in_port): """ process a dataplane packet the way a real OpenFlow switch would. packet: an instance of ethernet in_port: the integer port number """ assert_type("packet", packet, ethernet, none_ok=False) assert_type("in_port", in_port, int, none_ok=False) entry = self.table.entry_for_packet(packet, in_port) if(entry != None): entry.touch_packet(len(packet)) self._process_actions_for_packet(entry.actions, packet, in_port) else: # no matching entry buffer_id = self._buffer_packet(packet, in_port) self.send_packet_in(in_port, buffer_id, packet, self.xid_count(), reason=OFPR_NO_MATCH) def take_port_down(self, port): ''' Take the given port down, and send a port_status message to the controller ''' port_no = port.port_no if port_no not in self.ports: raise RuntimeError("port_no %d not in %s's ports" % (port_no, str(self))) # Hmmm, is deleting the port the correct behavior? del self.ports[port_no] self.send_port_status(port, OFPPR_DELETE) def bring_port_up(self, port): ''' Bring the given port up, and send a port_status message to the controller ''' port_no = port.port_no if port_no in self.ports: raise RuntimeError("port_no %d already in %s's ports" % (port_no, str(self))) self.ports[port_no] = port self.send_port_status(port, OFPPR_ADD) # ==================================== # # Helper Methods # # ==================================== # def _output_packet(self, packet, out_port, in_port): """ send a packet out some port. packet: instance of ethernet out_port, in_port: the integer port number """ assert_type("packet", packet, ethernet, none_ok=False) def real_send(port_no): if type(port_no) == ofp_phy_port: port_no = port_no.port_no if port_no not in self.ports: raise RuntimeError("Invalid physical output port: %x" % port_no) if self.ports[port_no].state & OFPPS_LINK_DOWN: self.log.debug("Sending packet on a port which is down!") else: self.raiseEvent(DpPacketOut(self, packet, self.ports[port_no])) if out_port < OFPP_MAX: real_send(out_port) elif out_port == OFPP_IN_PORT: real_send(in_port) elif out_port == OFPP_FLOOD or out_port == OFPP_ALL: # no support for spanning tree yet -> flood=all for (no,port) in self.ports.iteritems(): if no != in_port: real_send(port) elif out_port == OFPP_CONTROLLER: buffer_id = self._buffer_packet(packet, in_port) self.send_packet_in(in_port, buffer_id, packet, self.xid_count(), reason=OFPR_ACTION) else: raise("Unsupported virtual output port: %x" % out_port) def _buffer_packet(self, packet, in_port=None): """ Find a free buffer slot to buffer the packet in. """ for (i, value) in enumerate(self.packet_buffer): if(value==None): self.packet_buffer[i] = (packet, in_port) return i + 1 self.packet_buffer.append( (packet, in_port) ) return len(self.packet_buffer) def _process_actions_for_packet_from_buffer(self, actions, buffer_id): """ output and release a packet from the buffer """ buffer_id = buffer_id - 1 if(buffer_id > len(self.packet_buffer) or self.packet_buffer[buffer_id] == None): self.log.warn("Invalid output buffer id: %x" % buffer_id) return (packet, in_port) = self.packet_buffer[buffer_id] self._process_actions_for_packet(actions, packet, in_port) self.packet_buffer[buffer_id] = None def _process_actions_for_packet(self, actions, packet, in_port): """ process the output actions for a packet """ assert_type("packet", packet, [ethernet, str], none_ok=False) if not isinstance(packet, ethernet): packet = ethernet.unpack(packet) def output_packet(action, packet): self._output_packet(packet, action.port, in_port) return packet def set_vlan_id(action, packet): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet.next) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.id = action.vlan_id return packet def set_vlan_pcp(action, packet): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.pcp = action.vlan_pcp return packet def strip_vlan(action, packet): if isinstance(packet.next, vlan): packet.type = packet.next.eth_type packet.next = packet.next.next return packet def set_dl_src(action, packet): packet.src = action.dl_addr return packet def set_dl_dst(action, packet): packet.dst = action.dl_addr return packet def set_nw_src(action, packet): if(isinstance(packet.next, ipv4)): packet.next.nw_src = action.nw_addr return packet def set_nw_dst(action, packet): if(isinstance(packet.next, ipv4)): packet.next.nw_dst = action.nw_addr return packet def set_nw_tos(action, packet): if(isinstance(packet.next, ipv4)): packet.next.tos = action.nw_tos return packet def set_tp_src(action, packet): if(isinstance(packet.next, udp) or isinstance(packet.next, tcp)): packet.next.srcport = action.tp_port return packet def set_tp_dst(action, packet): if(isinstance(packet.next, udp) or isinstance(packet.next, tcp)): packet.next.dstport = action.tp_port return packet def enqueue(action, packet): self.log.warn("output_enqueue not supported yet. Performing regular output") return output_packet(action.tp_port, packet) # def push_mpls_tag(action, packet): # 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 pop_mpls_tag(action, packet): # 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 set_mpls_label(action, packet): # 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 set_mpls_tc(action, packet): # 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 set_mpls_ttl(action, packet): # 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 dec_mpls_ttl(action, packet): # if not isinstance(packet.next, mpls): # return packet # packet.next.ttl = packet.next.ttl - 1 # return packet handler_map = { OFPAT_OUTPUT: output_packet, OFPAT_SET_VLAN_VID: set_vlan_id, OFPAT_SET_VLAN_PCP: set_vlan_pcp, OFPAT_STRIP_VLAN: strip_vlan, OFPAT_SET_DL_SRC: set_dl_src, OFPAT_SET_DL_DST: set_dl_dst, OFPAT_SET_NW_SRC: set_nw_src, OFPAT_SET_NW_DST: set_nw_dst, OFPAT_SET_NW_TOS: set_nw_tos, OFPAT_SET_TP_SRC: set_tp_src, OFPAT_SET_TP_DST: set_tp_dst, OFPAT_ENQUEUE: enqueue, # OFPAT_PUSH_MPLS: push_mpls_tag, # OFPAT_POP_MPLS: pop_mpls_tag, # OFPAT_SET_MPLS_LABEL: set_mpls_label, # OFPAT_SET_MPLS_TC: set_mpls_tc, # OFPAT_SET_MPLS_TTL: set_mpls_ttl, # OFPAT_DEC_MPLS_TTL: dec_mpls_ttl, } for action in actions: # if action.type is ofp_action_resubmit: # self.process_packet(packet, in_port) # return if(action.type not in handler_map): raise NotImplementedError("Unknown action type: %x " % type) packet = handler_map[action.type](action, packet) def __repr__(self): return "SwitchImpl(dpid=%d, num_ports=%d)" % (self.dpid, len(self.ports))
class SoftwareSwitchBase (object): def __init__ (self, dpid, name=None, ports=4, miss_send_len=128, max_buffers=100, features=None): """ Initialize switch - ports is a list of ofp_phy_ports """ 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.miss_send_len = miss_send_len self._has_sent_hello = False self.table = SwitchFlowTable() 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.flow_stats = True self.features.table_stats = True self.features.port_stats = True #self.features.stp = True #self.features.ip_reasm = True #self.features.queue_stats = True #self.features.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 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): """ Send a message to this switch's communication partner """ if self._connection: self._connection.send(message) else: self.log.debug("Asked to send message %s, but not connected", message) def _rx_hello (self, ofp, connection): self.send_hello() def _rx_echo_request (self, ofp, connection): """ Handles echo requests """ msg = ofp_echo_reply(xid=ofp.xid) self.send(msg) def _rx_features_request (self, ofp, connection): """ Handles feature requests """ 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) 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) self.send(msg) def _rx_stats_request (self, ofp, connection): def desc_stats (ofp): 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 flow_stats (ofp): req = ofp_flow_stats_request().unpack(ofp.body) assert self.table_id in (TABLE_ALL, 0) return self.table.flow_stats(req.match, req.out_port) def aggregate_stats (ofp): req = ofp_aggregate_stats_request().unpack(ofp.body) assert self.table_id in (TABLE_ALL, 0) return self.table.aggregate_stats(req.match, out_port) def table_stats (ofp): # 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 = 0x7fFFffFF r.active_count = len(self.table) r.lookup_count = self._lookup_count r.matched_count = self._matched_count return r def port_stats (ofp): req = ofp_port_stats_request().unpack(ofp.body) if req.port_no == OFPP_NONE: res = ofp_port_stats(port_no=OFPP_NONE) for stats in self.port_stats.values(): res += stats return res else: return self.port_stats[req.port_no] def queue_stats (ofp): raise AttributeError("not implemented") stats_handlers = { OFPST_DESC: desc_stats, OFPST_FLOW: flow_stats, OFPST_AGGREGATE: aggregate_stats, OFPST_TABLE: table_stats, OFPST_PORT: port_stats, OFPST_QUEUE: queue_stats } if ofp.type in stats_handlers: handler = stats_handlers[ofp.type] else: raise AttributeError("Unsupported stats request type %d" % (ofp.type,)) reply = ofp_stats_reply(xid=ofp.xid, body=handler(ofp)) self.log.debug("Sending stats reply %s", str(reply)) self.send(reply) def _rx_set_config (self, config, connection): pass def _rx_port_mod (self, port_mod, connection): port_no = port_mod.port_no if port_no not in self.ports: err = ofp_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_PORT) err.xid = port_mod.xid err.data = port_mod.pack() self.send(err) return port = self.ports[port_no] if port.hw_addr != port_mod.hw_addr: err = ofp_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_HW_ADDR) err.xid = port_mod.xid err.data = port_mod.pack() self.send(err) return mask = port_mod.mask if mask & OFPPC_NO_FLOOD: mask ^= OFPPC_NO_FLOOD if port.set_config(port_mod.config, OFPPC_NO_FLOOD): if port.config & OFPPC_NO_FLOOD: self.log.debug("Disabling flooding on port %s", port) else: self.log.debug("Enabling flooding on port %s", port) if mask & OFPPC_PORT_DOWN: mask ^= OFPPC_PORT_DOWN if port.set_config(port_mod.config, OFPPC_PORT_DOWN): if port.config & OFPPC_PORT_DOWN: self.log.debug("Set port %s down", port) else: self.log.debug("Set port %s up", port) # 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) if mask != 0: self.log.warn("Unsupported PORT_MOD flags: %08x", mask) def _rx_vendor (self, vendor, connection): # We don't support vendor extensions, so send an OFP_ERROR, per # page 42 of spec err = ofp_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_VENDOR) err.xid = vendor.xid err.data = vendor.pack() self.send(err) 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 rx_packet (self, packet, in_port): """ process a dataplane packet packet: an instance of ethernet in_port: the integer port number """ 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 if port.config & OFPPC_NO_RECV: return 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) self.send_packet_in(in_port, buffer_id, packet, 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 _output_packet_physical (self, packet, port_no): """ send a packet out a single physical port This is called by the more general _output_packet(). """ 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._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: # There better be a table entry there, else we get infinite recurision # between switch<->controller # Note that this isn't infinite recursion, since the table entry's # out_port will not be OFPP_TABLE self.rx_packet(packet, in_port) else: raise("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): self.log.warn("Invalid output buffer id: %d", buffer_id) return if self._packet_buffer[buffer_id] is None: self.log.warn("Buffer %d has already been flushed", buffer_id) 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,)) err = ofp_error(type=OFPET_BAD_ACTION, code=OFPBAC_BAD_TYPE) if ofp: err.xid = ofp.xid err.data = ofp.pack() else: err.xid = 0 self.send(err) return packet = h(action, packet, in_port) 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_id (self, action, packet, in_port): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet.next) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.id = action.vlan_id return packet def _action_set_vlan_pcp (self, action, packet, in_port): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.pcp = action.vlan_pcp return packet def _action_strip_vlan (self, action, packet, in_port): if isinstance(packet.next, vlan): packet.type = packet.next.eth_type packet.next = packet.next.next 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): if isinstance(packet.next, ipv4): packet.next.nw_src = action.nw_addr return packet def _action_set_nw_dst (self, action, packet, in_port): if isinstance(packet.next, ipv4): packet.next.nw_dst = action.nw_addr return packet def _action_set_nw_tos (self, action, packet, in_port): if isinstance(packet.next, ipv4): packet.next.tos = action.nw_tos return packet def _action_set_tp_src (self, action, packet, in_port): if isinstance(packet.next, udp) or isinstance(packet.next, tcp): packet.next.srcport = action.tp_port return packet def _action_set_tp_dst (self, action, packet, in_port): if isinstance(packet.next, udp) or isinstance(packet.next, tcp): packet.next.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 __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, features=None): """ Initialize switch - ports is a list of ofp_phy_ports """ 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.miss_send_len = miss_send_len self._has_sent_hello = False self.table = SwitchFlowTable() 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.flow_stats = True self.features.table_stats = True self.features.port_stats = True #self.features.stp = True #self.features.ip_reasm = True #self.features.queue_stats = True #self.features.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
class SoftwareSwitchBase (object): def __init__ (self, dpid, name=None, ports=4, miss_send_len=128, max_buffers=100, features=None): """ Initialize switch - ports is a list of ofp_phy_ports """ 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.miss_send_len = miss_send_len self._has_sent_hello = False self.table = SwitchFlowTable() 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.flow_stats = True self.features.table_stats = True self.features.port_stats = True #self.features.stp = True #self.features.ip_reasm = True #self.features.queue_stats = True #self.features.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 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. """ connection.set_message_handler(self.rx_message) self._connection = connection def send (self, message): """ Send a message to this switch's communication partner """ if self._connection: self._connection.send(message) else: self.log.debug("Asked to send message %s, but not connected", message) def _rx_hello (self, ofp, connection): self.send_hello() def _rx_echo_request (self, ofp, connection): """ Handles echo requests """ msg = ofp_echo_reply(xid=ofp.xid) self.send(msg) def _rx_features_request (self, ofp, connection): """ Handles feature requests """ 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) 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) self.send(msg) def _rx_stats_request (self, ofp, connection): def desc_stats (ofp): 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__) def flow_stats (ofp): req = ofp_flow_stats_request().unpack(ofp.body) assert self.table_id in (TABLE_ALL, 0) return self.table.flow_stats(req.match, req.out_port) def aggregate_stats (ofp): req = ofp_aggregate_stats_request().unpack(ofp.body) assert self.table_id in (TABLE_ALL, 0) return self.table.aggregate_stats(req.match, out_port) def table_stats (ofp): # 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 = 0x7fFFffFF r.active_count = len(self.table) r.lookup_count = self._lookup_count r.matched_count = self._matched_count return r def port_stats (ofp): req = ofp_port_stats_request().unpack(ofp.body) if req.port_no == OFPP_NONE: res = ofp_port_stats(port_no=OFPP_NONE) for stats in self.port_stats.values(): res += stats return res else: return self.port_stats[req.port_no] def queue_stats (ofp): raise AttributeError("not implemented") stats_handlers = { OFPST_DESC: desc_stats, OFPST_FLOW: flow_stats, OFPST_AGGREGATE: aggregate_stats, OFPST_TABLE: table_stats, OFPST_PORT: port_stats, OFPST_QUEUE: queue_stats } if ofp.type in stats_handlers: handler = stats_handlers[ofp.type] else: raise AttributeError("Unsupported stats request type %d" % (ofp.type,)) reply = ofp_stats_reply(xid=ofp.xid, body=handler(ofp)) self.log.debug("Sending stats reply %s", str(reply)) self.send(reply) def _rx_set_config (self, config, connection): pass def _rx_port_mod (self, port_mod, connection): port_no = port_mod.port_no if port_no not in self.ports: err = ofp_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_PORT) err.xid = port_mod.xid err.data = port_mod.pack() self.send(err) return port = self.ports[port_no] if port.hw_addr != port_mod.hw_addr: err = ofp_error(type=OFPET_PORT_MOD_FAILED, code=OFPPMFC_BAD_HW_ADDR) err.xid = port_mod.xid err.data = port_mod.pack() self.send(err) return mask = port_mod.mask if mask & OFPPC_NO_FLOOD: mask ^= OFPPC_NO_FLOOD if port.set_config(port_mod.config, OFPPC_NO_FLOOD): if port.config & OFPPC_NO_FLOOD: self.log.debug("Disabling flooding on port %s", port) else: self.log.debug("Enabling flooding on port %s", port) if mask & OFPPC_PORT_DOWN: mask ^= OFPPC_PORT_DOWN if port.set_config(port_mod.config, OFPPC_PORT_DOWN): if port.config & OFPPC_PORT_DOWN: self.log.debug("Set port %s down", port) else: self.log.debug("Set port %s up", port) # 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) if mask != 0: self.log.warn("Unsupported PORT_MOD flags: %08x", mask) def _rx_vendor (self, vendor, connection): # We don't support vendor extensions, so send an OFP_ERROR, per # page 42 of spec err = ofp_error(type=OFPET_BAD_REQUEST, code=OFPBRC_BAD_VENDOR) err.xid = vendor.xid err.data = vendor.pack() self.send(err) def send_hello (self, force = False): """ Send hello (once) """ 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 rx_packet (self, packet, in_port): """ process a dataplane packet packet: an instance of ethernet in_port: the integer port number """ 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 if port.config & OFPPC_NO_RECV: return 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) self.send_packet_in(in_port, buffer_id, packet, 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 _output_packet_physical (self, packet, port_no): """ send a packet out a single physical port This is called by the more general _output_packet(). """ 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._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: # There better be a table entry there, else we get infinite recurision # between switch<->controller # Note that this isn't infinite recursion, since the table entry's # out_port will not be OFPP_TABLE self.rx_packet(packet, in_port) else: raise("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): self.log.warn("Invalid output buffer id: %d", buffer_id) return if self._packet_buffer[buffer_id] is None: self.log.warn("Buffer %d has already been flushed", buffer_id) 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,)) err = ofp_error(type=OFPET_BAD_ACTION, code=OFPBAC_BAD_TYPE) if ofp: err.xid = ofp.xid err.data = ofp.pack() else: err.xid = 0 self.send(err) return packet = h(action, packet, in_port) 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_id (self, action, packet, in_port): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet.next) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.id = action.vlan_id return packet def _action_set_vlan_pcp (self, action, packet, in_port): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.pcp = action.vlan_pcp return packet def _action_strip_vlan (self, action, packet, in_port): if isinstance(packet.next, vlan): packet.type = packet.next.eth_type packet.next = packet.next.next 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): if isinstance(packet.next, ipv4): packet.next.nw_src = action.nw_addr return packet def _action_set_nw_dst (self, action, packet, in_port): if isinstance(packet.next, ipv4): packet.next.nw_dst = action.nw_addr return packet def _action_set_nw_tos (self, action, packet, in_port): if isinstance(packet.next, ipv4): packet.next.tos = action.nw_tos return packet def _action_set_tp_src (self, action, packet, in_port): if isinstance(packet.next, udp) or isinstance(packet.next, tcp): packet.next.srcport = action.tp_port return packet def _action_set_tp_dst (self, action, packet, in_port): if isinstance(packet.next, udp) or isinstance(packet.next, tcp): packet.next.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 __repr__ (self): return "%s(dpid=%s, num_ports=%d)" % (type(self).__name__, dpid_to_str(self.dpid), len(self.ports))
class SwitchImpl(EventMixin): _eventMixin_events = set([DpPacketOut]) # ports is a list of ofp_phy_ports def __init__(self, dpid, name=None, ports=4, miss_send_len=128, n_buffers=100, n_tables=1, capabilities=None): """Initialize switch""" ##Datapath id of switch self.dpid = dpid ## Human-readable name of the switch self.name = name if self.name is None: self.name = str(dpid) self.log = logging.getLogger(self.name) ##Number of buffers self.n_buffers = n_buffers ##Number of tables self.n_tables= n_tables # Note that there is one switch table in the OpenFlow 1.0 world self.table = SwitchFlowTable() # buffer for packets during packet_in self.packet_buffer = [] if(ports == None or isinstance(ports, int)): ports=_default_port_list(num_ports=ports, prefix=dpid) self.xid_count = xid_generator(1) ## Hash of port_no -> openflow.pylibopenflow_01.ofp_phy_ports self.ports = {} for port in ports: self.ports[port.port_no] = port ## (OpenFlow Handler map) self.ofp_handlers = { # Reactive handlers ofp_type_rev_map['OFPT_HELLO'] : self._receive_hello, ofp_type_rev_map['OFPT_ECHO_REQUEST'] : self._receive_echo, ofp_type_rev_map['OFPT_FEATURES_REQUEST'] : self._receive_features_request, ofp_type_rev_map['OFPT_FLOW_MOD'] : self._receive_flow_mod, ofp_type_rev_map['OFPT_PACKET_OUT'] : self._receive_packet_out, ofp_type_rev_map['OFPT_BARRIER_REQUEST'] : self._receive_barrier_request, ofp_type_rev_map['OFPT_SET_CONFIG'] : self._receive_set_config, # Proactive responses ofp_type_rev_map['OFPT_ECHO_REPLY'] : self._receive_echo_reply # TODO: many more packet types to process } self._connection = None ##Capabilities if (isinstance(capabilities, SwitchCapabilities)): self.capabilities = capabilities else: self.capabilities = SwitchCapabilities(miss_send_len) def set_io_worker(self, io_worker): self._connection = ControllerConnection(io_worker, self.ofp_handlers) return self._connection def set_connection(self, connection): connection.ofp_handlers = self.ofp_handlers self._connection = connection def send(self, message): """ Send a message to this switches communication partner. If the switch is not connected, the message is silently dropped. """ if self._connection: self._connection.send(message) else: self.log.debug("Asked to send message %s, but not connected" % message) # ==================================== # # Reactive OFP processing # # ==================================== # def _receive_hello(self, ofp): self.log.debug("Receive hello %s" % self.name) # How does the OpenFlow protocol prevent an infinite loop of Hello messages? self.send_hello() def _receive_echo(self, ofp): """Reply to echo request """ self.log.debug("Reply echo of xid: %s %s" % (str(ofp), self.name)) msg = ofp_echo_reply(xid=ofp.xid) self.send(msg) def _receive_features_request(self, ofp): """Reply to feature request """ self.log.debug("Reply features request of xid %s %s" % (str(ofp), self.name)) msg = ofp_features_reply(datapath_id = self.dpid, xid = ofp.xid, n_buffers = self.n_buffers, n_tables = self.n_tables, capabilities = self.capabilities.get_capabilities(), actions = self.capabilities.get_actions(), ports = self.ports.values()) self.send(msg) def _receive_flow_mod(self, ofp): """Handle flow mod: just print it here """ self.log.debug("Flow mod %s: %s" % (self.name, ofp.show())) self.table.process_flow_mod(ofp) if(ofp.buffer_id >=0 ): self._process_actions_for_packet_from_buffer(ofp.actions, ofp.buffer_id) def _receive_packet_out(self, packet_out): """ Send the packet out the given port """ self.log.debug("Packet out: %s" % packet_out.show()) if(packet_out.data): self._process_actions_for_packet(packet_out.actions, packet_out.data, packet_out.in_port) elif(packet_out.buffer_id > 0): self._process_actions_for_packet_from_buffer(packet_out.actions, packet_out.buffer_id) else: self.log.warn("packet_out: No data and no buffer_id -- don't know what to send") def _receive_echo_reply(self, ofp): self.log.debug("Echo reply: %s %s" % (str(ofp), self.name)) def _receive_barrier_request(self, ofp): self.log.debug("Barrier request %s %s" % (self.name, str(ofp))) msg = ofp_barrier_reply(xid = ofp.xid) self.send(msg) def _receive_set_config(self, config): self.log.debug("Set config %s %s" % (self.name, str(config))) # ==================================== # # Proactive OFP processing # # ==================================== # def send_hello(self): """Send hello """ self.log.debug("Send hello %s " % self.name) msg = ofp_hello() self.send(msg) def send_packet_in(self, in_port, buffer_id=None, packet="", xid=None, reason=None): """Send PacketIn Assume no match as reason, buffer_id = 0xFFFFFFFF, and empty packet by default """ assert_type("packet", packet, ethernet) self.log.debug("Send PacketIn %s " % self.name) if (reason == None): reason = ofp_packet_in_reason_rev_map['OFPR_NO_MATCH'] if (buffer_id == None): buffer_id = int("0xFFFFFFFF",16) if xid == None: xid = self.xid_count.next() msg = ofp_packet_in(xid=xid, in_port = in_port, buffer_id = buffer_id, reason = reason, data = packet.pack()) self.send(msg) def send_echo(self, xid=0): """Send echo request """ self.log.debug("Send echo %s" % self.name) msg = ofp_echo_request() self.send(msg) def send_port_status(self, port, reason): ''' port is an ofp_phy_port reason is one of 'OFPPR_ADD', 'OFPPR_DELETE', 'OFPPR_MODIFY' ''' 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) # ==================================== # # Dataplane processing # # ==================================== # def process_packet(self, packet, in_port): """ process a dataplane packet the way a real OpenFlow switch would. packet: an instance of ethernet in_port: the integer port number """ assert_type("packet", packet, ethernet, none_ok=False) entry = self.table.entry_for_packet(packet, in_port) if(entry != None): entry.touch_packet(len(packet)) self._process_actions_for_packet(entry.actions, packet, in_port) else: # no matching entry buffer_id = self._buffer_packet(packet, in_port) self.send_packet_in(in_port, buffer_id, packet, self.xid_count.next(), reason=OFPR_NO_MATCH) def take_port_down(self, port): ''' Take the given port down, and send a port_status message to the controller ''' port_no = port.port_no if port_no not in self.ports: raise RuntimeError("port_no %d not in %s's ports" % (port_no, str(self))) # Hmmm, is deleting the port the correct behavior? del self.ports[port_no] self.send_port_status(port, OFPPR_DELETE) def bring_port_up(self, port): ''' Bring the given port up, and send a port_status message to the controller ''' port_no = port.port_no if port_no in self.ports: raise RuntimeError("port_no %d already in %s's ports" % (port_no, str(self))) self.ports[port_no] = port self.send_port_status(port, OFPPR_ADD) # ==================================== # # Helper Methods # # ==================================== # def _output_packet(self, packet, out_port, in_port): """ send a packet out some port. packet: instance of ethernet out_port, in_port: the integer port number """ assert_type("packet", packet, ethernet, none_ok=False) def real_send(port_no): if type(port_no) == ofp_phy_port: port_no = port_no.port_no if port_no not in self.ports: raise RuntimeError("Invalid physical output port: %x" % port_no) self.raiseEvent(DpPacketOut(self, packet, self.ports[port_no])) if out_port < OFPP_MAX: real_send(out_port) elif out_port == OFPP_IN_PORT: real_send(in_port) elif out_port == OFPP_FLOOD or out_port == OFPP_ALL: # no support for spanning tree yet -> flood=all for (no,port) in self.ports.iteritems(): if no != in_port: real_send(port) elif out_port == OFPP_CONTROLLER: buffer_id = self._buffer_packet(packet, in_port) self.send_packet_in(in_port, buffer_id, packet, self.xid_count.next(), reason=OFPR_ACTION) else: raise("Unsupported virtual output port: %x" % out_port) def _buffer_packet(self, packet, in_port=None): """ Find a free buffer slot to buffer the packet in. """ for (i, value) in enumerate(self.packet_buffer): if(value==None): self.packet_buffer[i] = (packet, in_port) return i + 1 self.packet_buffer.append( (packet, in_port) ) return len(self.packet_buffer) def _process_actions_for_packet_from_buffer(self, actions, buffer_id): """ output and release a packet from the buffer """ buffer_id = buffer_id - 1 if(buffer_id > len(self.packet_buffer) or self.packet_buffer[buffer_id] == None): self.log.warn("Invalid output buffer id: %x" % buffer_id) return (packet, in_port) = self.packet_buffer[buffer_id] self._process_actions_for_packet(actions, packet, in_port) self.packet_buffer[buffer_id] = None def _process_actions_for_packet(self, actions, packet, in_port): """ process the output actions for a packet """ assert_type("packet", packet, [ethernet, str], none_ok=False) if not isinstance(packet, ethernet): packet = ethernet.unpack(packet) def output_packet(action, packet): self._output_packet(packet, action.port, in_port) return packet def set_vlan_id(action, packet): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet.next) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.id = action.vlan_id return packet def set_vlan_pcp(action, packet): if not isinstance(packet.next, vlan): packet.next = vlan(prev = packet) packet.next.eth_type = packet.type packet.type = ethernet.VLAN_TYPE packet.pcp = action.vlan_pcp return packet def strip_vlan(action, packet): if isinstance(packet.next, vlan): packet.type = packet.next.eth_type packet.next = packet.next.next return packet def set_dl_src(action, packet): packet.src = action.dl_addr return packet def set_dl_dst(action, packet): packet.dst = action.dl_addr return packet def set_nw_src(action, packet): if(isinstance(packet.next, ipv4)): packet.next.nw_src = action.nw_addr return packet def set_nw_dst(action, packet): if(isinstance(packet.next, ipv4)): packet.next.nw_dst = action.nw_addr return packet def set_nw_tos(action, packet): if(isinstance(packet.next, ipv4)): packet.next.tos = action.nw_tos return packet def set_tp_src(action, packet): if(isinstance(packet.next, udp) or isinstance(packet.next, tcp)): packet.next.srcport = action.tp_port return packet def set_tp_dst(action, packet): if(isinstance(packet.next, udp) or isinstance(packet.next, tcp)): packet.next.dstport = action.tp_port return packet def enqueue(action, packet): self.log.warn("output_enqueue not supported yet. Performing regular output") return output_packet(action.tp_port, packet) def push_mpls_tag(action, packet): 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 pop_mpls_tag(action, packet): 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 set_mpls_label(action, packet): 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 set_mpls_tc(action, packet): 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 set_mpls_ttl(action, packet): 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 dec_mpls_ttl(action, packet): if not isinstance(packet.next, mpls): return packet packet.next.ttl = packet.next.ttl - 1 return packet handler_map = { OFPAT_OUTPUT: output_packet, OFPAT_SET_VLAN_VID: set_vlan_id, OFPAT_SET_VLAN_PCP: set_vlan_pcp, OFPAT_STRIP_VLAN: strip_vlan, OFPAT_SET_DL_SRC: set_dl_src, OFPAT_SET_DL_DST: set_dl_dst, OFPAT_SET_NW_SRC: set_nw_src, OFPAT_SET_NW_DST: set_nw_dst, OFPAT_SET_NW_TOS: set_nw_tos, OFPAT_SET_TP_SRC: set_tp_src, OFPAT_SET_TP_DST: set_tp_dst, OFPAT_ENQUEUE: enqueue, OFPAT_PUSH_MPLS: push_mpls_tag, OFPAT_POP_MPLS: pop_mpls_tag, OFPAT_SET_MPLS_LABEL: set_mpls_label, OFPAT_SET_MPLS_TC: set_mpls_tc, OFPAT_SET_MPLS_TTL: set_mpls_ttl, OFPAT_DEC_MPLS_TTL: dec_mpls_ttl, } for action in actions: if action.type is ofp_action_resubmit: self.process_packet(packet, in_port) return if(action.type not in handler_map): raise NotImplementedError("Unknown action type: %x " % type) packet = handler_map[action.type](action, packet) def __repr__(self): return "SwitchImpl(dpid=%d, num_ports=%d)" % (self.dpid, len(self.ports))
def __init__(self, sw, topo): OpenFlowSwitch.__init__(self, sw) self.topo = topo self.table = SwitchFlowTable() log.debug("ScnOpenFlowSwitch init : %s" % str(self))
class ScnOpenFlowSwitch(OpenFlowSwitch): """describe OpenFlowSwitch on ScnOpenFlow space. """ def __init__(self, sw, topo): OpenFlowSwitch.__init__(self, sw) self.topo = topo self.table = SwitchFlowTable() log.debug("ScnOpenFlowSwitch init : %s" % str(self)) def _getIniOfpSpeed(self, target_port): """get ofp max spped setting. """ ofs_ip = self.ipaddr for section in core.parser.getSwitchsSections(): ip = core.parser.getValue(section, IP) if ip != ofs_ip: continue ports = core.parser.getPortsSections(section) for port in ports: #log.debug("port = %s" % port) name = core.parser.getValue(port, INTFNAME) if target_port.name != name: continue speed = core.parser.getValue(port, SPEED) if not speed: return None factor = 1. speed = speed.lower() if speed[-1] == "k": factor = 10.**3 speed = speed[:-1] elif speed[-1] == "m": factor = 10.**6 speed = speed[:-1] elif speed[-1] == "g": factor = 10.**9 speed = speed[:-1] return float(speed) * factor def setConnection(self, connection, ofp = None): """set connection listener. ofp - a FeaturesReply message """ if self._connection: self._connection.removeListeners(self._listeners) self._listeners = [] self._connection = connection if self._reconnectTimeout is not None: self._reconnectTimeout.cancel() self._reconnectTimeout = None if connection is None: self._reconnectTimeout = Timer(RECONNECT_TIMEOUT, self._timer_ReconnectTimeout) if ofp is not None: # update capabilities self.capabilities = ofp.capabilities # update all ports untouched = set(self.ports.keys()) for port in ofp.ports: if port.port_no in self.ports: self.ports[port.port_no]._update(port) # _update method is protected. untouched.remove(port.port_no) else: speed = self._getIniOfpSpeed(port) log.debug("New ScnOpenFlowPort: [%s] OFP = %s" % (str(port), str(ofp))) self.ports[port.port_no] = ScnOpenFlowPort(port, self, speed = speed) for port in untouched: log.debug("remove ports: [%s]" % str(port)) self.ports[port].exists = False del self.ports[port] if connection is not None: self._listeners = self.listenTo(connection, prefix = "con") self.raiseEvent(SwitchConnectionUp(switch = self, connection = connection)) else: self.raiseEvent(SwitchConnectionDown(switch = self)) def _handle_con_PortStatus(self, event): port = event.ofp.desc if event.ofp.reason == of.ofp_port_reason_rev_map["OFPPR_DELETE"]: if port.port_no in self.ports: self.ports[port.port_no].exists = False del self.ports[port.port_no] elif event.ofp.reason == of.ofp_port_reason_rev_map["OFPPR_MODIFY"]: self.ports[port.port_no]._update(port) # _update method is protected. else: assert event.ofp.reason == of.ofp_port_reason_rev_map["OFPPR_ADD"] assert port.port_no not in self.ports speed = self._getIniOfpSpeed(port) log.debug("New ScnOpenFlowPort: [%s] OFP = %s" % (str(port), str(event.ofp))) self.ports[port.port_no] = ScnOpenFlowPort(port, self, speed = speed) self.raiseEvent(event) event.halt = False @property def ipaddr(self): """IP Address attribute.(getter) """ ip = IPAddr(self._connection.sock.getpeername()[0]) log.debug("[IP] get IP: %s" % str(ip)) return ip def getPorts(self): """get all ScnOpenFlowPort. """ return self.ports.values() def getOFPort(self, number): """get a ScnOpenFlowPort specified by number. """ for port in self.ports.values(): if port.number == number: return port return None def getHost(self, locator): """get a ScnOpenFlowHost specified by locator. use ScnOpenFlowPort.getHost """ log.debug("getHost self=[%s], o=[%s]" % (str(self), str(locator))) assert isinstance(locator, EthAddr) or isinstance(locator, IPAddr) for ofp in self.ports.values(): host = ofp.getHost(locator) if host: return host def __receive_flow_mod__(self, ofp): """Handle flow mod: just print it here """ log.debug("Flow mod %s: %s" % (self.name, ofp.show())) self.table.process_flow_mod(ofp) def installFlow(self, msg): """@override because base class has bug. no import TableEntry... and extend input struct to flow_mod object. """ tabEntry = TableEntry.from_flow_mod(msg) log.info("apply entry - %s" % str(tabEntry)) self.flow_table.install(tabEntry) def removeFlow(self, msg): """remove flow entry. msg[flow_mod] -- entry msg object. """ tabEntry = TableEntry.from_flow_mod(msg) log.info("remove entry - %s" % str(tabEntry)) self.flow_table.remove_with_wildcards(tabEntry)