def test_basic(self): god = OpenFlowBuffer() message = ofp_flow_mod(match=ofp_match(in_port=1, nw_src="1.1.1.1"), action=ofp_action_output(port=1)) mock_conn = MockConnection() god.insert_pending_receipt(1,"c1",message,mock_conn) pending_receipt = PendingReceive(1,"c1",OFFingerprint.from_pkt(message)) self.assertTrue(god.message_receipt_waiting(pending_receipt)) god.schedule(pending_receipt) self.assertTrue(mock_conn.passed_message) self.assertFalse(god.message_receipt_waiting(pending_receipt))
def test_basic(self): buf = OpenFlowBuffer() message = ofp_flow_mod(match=ofp_match(in_port=1, nw_src="1.1.1.1"), action=ofp_action_output(port=1)) mock_conn = MockConnection() buf.insert_pending_receipt(1, "c1", message, mock_conn) pending_receipt = PendingReceive(1, "c1", OFFingerprint.from_pkt(message)) self.assertTrue(buf.message_receipt_waiting(pending_receipt)) buf.schedule(pending_receipt) self.assertTrue(mock_conn.passed_message) self.assertFalse(buf.message_receipt_waiting(pending_receipt))
class FuzzSoftwareSwitch (NXSoftwareSwitch): """ A mock switch implementation for testing purposes. Can simulate dropping dead. """ _eventMixin_events = set([DpPacketOut]) def __init__(self, dpid, name=None, ports=4, miss_send_len=128, n_buffers=100, n_tables=1, capabilities=None, can_connect_to_endhosts=True): NXSoftwareSwitch.__init__(self, dpid, name, ports, miss_send_len, n_buffers, n_tables, capabilities) # Whether this is a core or edge switch self.can_connect_to_endhosts = can_connect_to_endhosts self.create_connection = None self.failed = False self.log = logging.getLogger("FuzzSoftwareSwitch(%d)" % dpid) if logging.getLogger().getEffectiveLevel() <= logging.DEBUG: def _print_entry_remove(table_mod): if table_mod.removed != []: self.log.debug("Table entry removed %s" % str(table_mod.removed)) self.table.addListener(FlowTableModification, _print_entry_remove) def error_handler(e): self.log.exception(e) raise e self.cid2connection = {} self.error_handler = error_handler self.controller_info = [] # Used in randomize_flow_mod mode to prioritize the order in which flow_mods are processed. self.cmd_queue = None # Tell our buffer to insert directly to our flow table whenever commands are let through by control_flow. self.openflow_buffer = OpenFlowBuffer() def add_controller_info(self, info): self.controller_info.append(info) def _handle_ConnectionUp(self, event): self._setConnection(event.connection, event.ofp) def connect(self, create_connection, down_controller_ids=None): ''' - create_connection is a factory method for creating Connection objects which are connected to controllers. Takes a ControllerConfig object and a reference to a switch (self) as a parameter ''' # Keep around the connection factory for fail/recovery later if down_controller_ids is None: down_controller_ids = set() self.create_connection = create_connection connected_to_at_least_one = False for info in self.controller_info: # Don't connect to down controllers if info.cid not in down_controller_ids: conn = create_connection(info, self) self.set_connection(conn) # cause errors to be raised conn.error_handler = self.error_handler self.cid2connection[info.cid] = conn connected_to_at_least_one = True return connected_to_at_least_one def send(self, *args, **kwargs): if self.failed: self.log.warn("Currently down. Dropping send()") else: super(FuzzSoftwareSwitch, self).send(*args, **kwargs) def get_connection(self, cid): if cid not in self.cid2connection.keys(): raise ValueError("No such connection %s" % str(cid)) return self.cid2connection[cid] def is_connected_to(self, cid): if cid in self.cid2connection.keys(): conn = self.get_connection(cid) return not conn.closed return False def fail(self): # TODO(cs): depending on the type of failure, a real switch failure # might not lead to an immediate disconnect if self.failed: self.log.warn("Switch already failed") return self.failed = True for connection in self.connections: connection.close() self.connections = [] def recover(self, down_controller_ids=None): if not self.failed: self.log.warn("Switch already up") return if self.create_connection is None: self.log.warn("Never connected in the first place") connected_to_at_least_one = self.connect(self.create_connection, down_controller_ids=down_controller_ids) if connected_to_at_least_one: self.failed = False return connected_to_at_least_one def serialize(self): # Skip over non-serializable data, e.g. sockets # TODO(cs): is self.log going to be a problem? serializable = FuzzSoftwareSwitch(self.dpid, self.parent_controller_name) # Can't serialize files serializable.log = None # TODO(cs): need a cleaner way to add in the NOM port representation if self.software_switch: serializable.ofp_phy_ports = self.software_switch.ports.values() return pickle.dumps(serializable, protocol=0) def has_pending_commands(self): return not self.cmd_queue.empty() def process_delayed_command(self): """ Throws Queue.Empty if the queue is empty. """ buffered_cmd = self.cmd_queue.get_nowait()[1] return (self.openflow_buffer.schedule(buffered_cmd), buffered_cmd) def use_delayed_commands(self): ''' Tell the switch to buffer flow mods ''' self.on_message_received = self.on_message_received_delayed def randomize_flow_mods(self, seed=None): ''' Initialize the RNG and command queue and mandate switch to randomize order in which flow_mods are processed ''' self.random = random.Random() if seed is not None: self.random.seed(seed) self.cmd_queue = Queue.PriorityQueue() def on_message_received_delayed(self, connection, msg): ''' Replacement for NXSoftwareSwitch.on_message_received when delaying command processing ''' if isinstance(msg, ofp_flow_mod): # Buffer flow mods forwarder = TableInserter(super(FuzzSoftwareSwitch, self).on_message_received, connection) receive = self.openflow_buffer.insert_pending_receipt(self.dpid, connection.cid, msg, forwarder) if self.cmd_queue: rnd_weight = self.random.random() # TODO(jl): use exponential moving average (define in params) rather than uniform distirbution # to prioritize oldest flow_mods self.cmd_queue.put((rnd_weight, receive)) else: # Immediately process all other messages super(FuzzSoftwareSwitch, self).on_message_received(connection, msg)
class FuzzSoftwareSwitch (NXSoftwareSwitch): """ A mock switch implementation for testing purposes. Can simulate dropping dead. FuzzSoftwareSwitch supports three features: - flow_mod delays, where flow_mods are not processed immediately - flow_mod processing randomization, where the order in which flow_mods are applied to the routing table perturb - flow_mod dropping, where flow_mods are dropped according to a given filter. This is implemented by the caller of get_next_command() applying a filter to the returned command and selectively allowing them through via process_next_command() NOTE: flow_mod processing randomization and flow_mod dropping both require flow_mod delays to be activated, but are not dependent on each other. """ _eventMixin_events = set([DpPacketOut]) def __init__(self, dpid, name=None, ports=4, miss_send_len=128, n_buffers=100, n_tables=1, capabilities=None, can_connect_to_endhosts=True): NXSoftwareSwitch.__init__(self, dpid, name, ports, miss_send_len, n_buffers, n_tables, capabilities) # Whether this is a core or edge switch self.can_connect_to_endhosts = can_connect_to_endhosts self.create_connection = None self.failed = False self.log = logging.getLogger("FuzzSoftwareSwitch(%d)" % dpid) if logging.getLogger().getEffectiveLevel() <= logging.DEBUG: def _print_entry_remove(table_mod): if table_mod.removed != []: self.log.debug("Table entry removed %s" % str(table_mod.removed)) self.table.addListener(FlowTableModification, _print_entry_remove) def error_handler(e): self.log.exception(e) raise e self.cid2connection = {} self.error_handler = error_handler self.controller_info = [] # Tell our buffer to insert directly to our flow table whenever commands are let through by control_flow. self.delay_flow_mods = False self.openflow_buffer = OpenFlowBuffer() self.barrier_deque = None # Boolean representing whether to use randomize_flow_mod mode to prioritize the order in which flow_mods are processed. self.randomize_flow_mod_order = False # Uninitialized RNG (initialize through randomize_flow_mods()) self.random = None def add_controller_info(self, info): self.controller_info.append(info) def _handle_ConnectionUp(self, event): self._setConnection(event.connection, event.ofp) def connect(self, create_connection, down_controller_ids=None, max_backoff_seconds=1024): ''' - create_connection is a factory method for creating Connection objects which are connected to controllers. Takes a ControllerConfig object and a reference to a switch (self) as a parameter ''' # Keep around the connection factory for fail/recovery later if down_controller_ids is None: down_controller_ids = set() self.create_connection = create_connection connected_to_at_least_one = False for info in self.controller_info: # Don't connect to down controllers if info.cid not in down_controller_ids: conn = create_connection(info, self, max_backoff_seconds=max_backoff_seconds) self.set_connection(conn) # cause errors to be raised conn.error_handler = self.error_handler self.cid2connection[info.cid] = conn connected_to_at_least_one = True return connected_to_at_least_one def send(self, *args, **kwargs): if self.failed: self.log.warn("Currently down. Dropping send()") else: super(FuzzSoftwareSwitch, self).send(*args, **kwargs) def get_connection(self, cid): if cid not in self.cid2connection.keys(): raise ValueError("No such connection %s" % str(cid)) return self.cid2connection[cid] def is_connected_to(self, cid): if cid in self.cid2connection.keys(): conn = self.get_connection(cid) return not conn.closed return False def fail(self): # TODO(cs): depending on the type of failure, a real switch failure # might not lead to an immediate disconnect if self.failed: self.log.warn("Switch already failed") return self.failed = True for connection in self.connections: connection.close() self.connections = [] def recover(self, down_controller_ids=None): if not self.failed: self.log.warn("Switch already up") return if self.create_connection is None: self.log.warn("Never connected in the first place") # We should only ever reconnect to live controllers, so set # max_backoff_seconds to a low number. connected_to_at_least_one = self.connect(self.create_connection, down_controller_ids=down_controller_ids, max_backoff_seconds=5) if connected_to_at_least_one: self.failed = False return connected_to_at_least_one def serialize(self): # Skip over non-serializable data, e.g. sockets # TODO(cs): is self.log going to be a problem? serializable = FuzzSoftwareSwitch(self.dpid, self.parent_controller_name) # Can't serialize files serializable.log = None # TODO(cs): need a cleaner way to add in the NOM port representation if self.software_switch: serializable.ofp_phy_ports = self.software_switch.ports.values() return pickle.dumps(serializable, protocol=0) def use_delayed_commands(self): ''' Tell the switch to buffer flow mods ''' self.delay_flow_mods = True self.on_message_received = self.on_message_received_delayed # barrier_deque has the structure: [(None, queue_1), (barrier_request_1, queue_2), ...] where... # - its elements have the form (barrier_in_request, next_queue_to_use) # - commands are always processed from the queue of the first element (i.e. barrier_deque[0][1]) # - when a new barrier_in request is received, a new tuple is appended to barrier_deque, containing: # (<the just-received request>, <queue for subsequent non-barrier_in commands until all previous commands have been processed>) # - the very first barrier_in is None because, there is no request to respond when we first start buffering commands self.barrier_deque = [(None, Queue.PriorityQueue())] def randomize_flow_mods(self, seed=None): ''' Initialize the RNG and tell switch to randomize order in which flow_mods are processed ''' self.randomize_flow_mod_order = True self.random = random.Random() if seed is not None: self.random.seed(seed) @property def current_cmd_queue(self): ''' Alias for the current epoch's pending flow_mods. ''' assert(len(self.barrier_deque) > 0) return self.barrier_deque[0][1] def _buffer_flow_mod(self, connection, msg, weight, buffr): ''' Called by on_message_received_delayed. Inserts a PendingReceive into self.openflow_buffer and sticks the message and receipt into the provided priority queue buffer for later retrieval. ''' forwarder = TableInserter.instance_for_connection(connection=connection, insert_method=super(FuzzSoftwareSwitch, self).on_message_received) receive = self.openflow_buffer.insert_pending_receipt(self.dpid, connection.cid, msg, forwarder) buffr.put((weight, msg, receive)) def on_message_received_delayed(self, connection, msg): ''' Precondition: use_delayed_commands() has been called. Replacement for NXSoftwareSwitch.on_message_received when delaying command processing ''' # TODO(jl): use exponential moving average (define in params) rather than uniform distribution # to prioritize oldest flow_mods assert(self.delay_flow_mods) def choose_weight(): ''' Return an appropriate weight to associate with a received command with when buffering. ''' if self.randomize_flow_mod_order: # TODO(jl): use exponential moving average (define in params) rather than uniform distribution # to prioritize oldest flow_mods return self.random.random() else: # behave like a normal FIFO queue return time.time() def handle_with_active_barrier_in(connection, msg): ''' Handling of flow_mods and barriers while operating under a barrier_in request''' if isinstance(msg, ofp_barrier_request): # create a new priority queue for all subsequent flow_mods self.barrier_deque.append((msg, Queue.PriorityQueue())) elif isinstance(msg, ofp_flow_mod): # stick the flow_mod on the queue of commands since the last barrier request weight = choose_weight() # N.B. len(self.barrier_deque) > 1, because an active barrier_in implies we appended a queue to barrier_deque, which # already always has at least one element: the default queue self._buffer_flow_mod(connection, msg, weight, buffr=self.barrier_deque[-1][1]) else: raise TypeError("Unsupported type for command buffering") def handle_without_active_barrier_in(connection, msg): if isinstance(msg, ofp_barrier_request): if self.current_cmd_queue.empty(): # if no commands waiting, reply to barrier immediately self.log.debug("Barrier request %s %s", self.name, str(msg)) barrier_reply = ofp_barrier_reply(xid = msg.xid) self.send(barrier_reply) else: self.barrier_deque.append((msg, Queue.PriorityQueue())) elif isinstance(msg, ofp_flow_mod): # proceed normally (no active or pending barriers) weight = choose_weight() self._buffer_flow_mod(connection, msg, weight, buffr=self.current_cmd_queue) else: raise TypeError("Unsupported type for command buffering") if isinstance(msg, ofp_flow_mod) or isinstance(msg, ofp_barrier_request): # Check if switch is currently operating under a barrier request # Note that we start out with len(self.barrier_deque) == 1, add an element for each barrier_in request, and never # delete when len(self.barrier_deque) == 1 if len(self.barrier_deque) > 1: handle_with_active_barrier_in(connection, msg) else: handle_without_active_barrier_in(connection, msg) else: # Immediately process all other messages super(FuzzSoftwareSwitch, self).on_message_received(connection, msg) def has_pending_commands(self): return not self.current_cmd_queue.empty() def get_next_command(self): """ Precondition: use_delayed_commands() has been invoked. Invoked periodically from fuzzer. Retrieves the next buffered command and its PendingReceive receipt. Throws Queue.Empty if the queue is empty. """ assert(self.delay_flow_mods) # tuples in barrier are of the form (weight, buffered command, pending receipt) (buffered_cmd, buffered_cmd_receipt) = self.current_cmd_queue.get_nowait()[1:] while self.current_cmd_queue.empty() and len(self.barrier_deque) > 1: # It's time to move to the next epoch and reply to the most recent barrier_request. # barrier_deque has the structure: [(None, queue_1), (barrier_request_1, queue_2), ...] # so when we empty queue_x, we just finished processing and thus must reply to barrier_request_x, which is coupled in # the next element in barrier_deque: (barrier_request_x, queue_x+1) self.barrier_deque.pop(0) finished_barrier_request = self.barrier_deque[0][0] if finished_barrier_request: self.log.debug("Barrier request %s %s", self.name, str(finished_barrier_request)) barrier_reply = ofp_barrier_reply(xid = finished_barrier_request.xid) self.send(barrier_reply) return (buffered_cmd, buffered_cmd_receipt) def process_delayed_command(self, buffered_cmd_receipt): """ Precondition: use_delayed_commands() has been invoked and buffered_cmd_receipt is the PendingReceive for a previously received and buffered command (i.e. was returned by get_next_command()) Returns the original buffered command """ assert(self.delay_flow_mods) return self.openflow_buffer.schedule(buffered_cmd_receipt) def show_flow_table(self): dl_types = { 0x0800: "IP", 0x0806: "ARP", 0x8100: "VLAN", 0x88cc: "LLDP", 0x888e: "PAE" } nw_protos = { 1 : "ICMP", 6 : "TCP", 17 : "UDP" } ports = { v: k.replace("OFPP_","") for (k,v) in of.ofp_port_rev_map.iteritems() } def dl_type(e): d = e.match.dl_type if d is None: return d else: return dl_types[d] if d in dl_types else "%x" %d def nw_proto(e): p = e.match.nw_proto return nw_protos[p] if p in nw_protos else p def action(a): if isinstance(a, ofp_action_output): return ports[a.port] if a.port in ports else "output(%d)" % a.port else: return str(a) def actions(e): if len(e.actions) == 0: return "(drop)" else: return ", ".join(action(a) for a in e.actions) t = Tabular( ("Prio", lambda e: e.priority), ("in_port", lambda e: e.match.in_port), ("dl_type", dl_type), ("dl_src", lambda e: e.match.dl_src), ("dl_dst", lambda e: e.match.dl_dst), ("nw_proto", nw_proto), ("nw_src", lambda e: e.match.nw_src), ("nw_dst", lambda e: e.match.nw_dst), ("tp_src", lambda e: e.match.tp_src), ("tp_dst", lambda e: e.match.tp_dst), ("actions", actions), ) t.show(self.table.entries)
class FuzzSoftwareSwitch (NXSoftwareSwitch): """ A mock switch implementation for testing purposes. Can simulate dropping dead. FuzzSoftwareSwitch supports three features: - flow_mod delays, where flow_mods are not processed immediately - flow_mod processing randomization, where the order in which flow_mods are applied to the routing table perturb - flow_mod dropping, where flow_mods are dropped according to a given filter. This is implemented by the caller of get_next_command() applying a filter to the returned command and selectively allowing them through via process_next_command() NOTE: flow_mod processing randomization and flow_mod dropping both require flow_mod delays to be activated, but are not dependent on each other. """ _eventMixin_events = set([DpPacketOut]) def __init__(self, dpid, name=None, ports=4, miss_send_len=128, n_buffers=100, n_tables=1, capabilities=None, can_connect_to_endhosts=True): NXSoftwareSwitch.__init__(self, dpid, name, ports, miss_send_len, n_buffers, n_tables, capabilities) # Whether this is a core or edge switch self.can_connect_to_endhosts = can_connect_to_endhosts self.create_connection = None self.failed = False self.log = logging.getLogger("FuzzSoftwareSwitch(%d)" % dpid) if logging.getLogger().getEffectiveLevel() <= logging.DEBUG: def _print_entry_remove(table_mod): if table_mod.removed != []: self.log.debug("Table entry removed %s" % str(table_mod.removed)) self.table.addListener(FlowTableModification, _print_entry_remove) def error_handler(e): self.log.exception(e) raise e self.cid2connection = {} self.error_handler = error_handler self.controller_info = [] # Tell our buffer to insert directly to our flow table whenever commands are let through by control_flow. self.delay_flow_mods = False self.openflow_buffer = OpenFlowBuffer() self.barrier_deque = None # Boolean representing whether to use randomize_flow_mod mode to prioritize the order in which flow_mods are processed. self.randomize_flow_mod_order = False # Uninitialized RNG (initialize through randomize_flow_mods()) self.random = None self.port_violations = [] def _output_packet(self, packet, out_port, in_port): try: return super(FuzzSoftwareSwitch, self)._output_packet(packet, out_port, in_port) except ValueError as e: self.log.warn("invalid arguments %s" % str(e)) if type(out_port) == ofp_phy_port: out_port = out_port.port_no self.port_violations.append((self.dpid, out_port)) def add_controller_info(self, info): self.controller_info.append(info) def _handle_ConnectionUp(self, event): self._setConnection(event.connection, event.ofp) def connect(self, create_connection, down_controller_ids=None, max_backoff_seconds=1024, controller_infos=None): ''' - create_connection is a factory method for creating Connection objects which are connected to controllers. Takes a ControllerConfig object and a reference to a switch (self) as a parameter ''' if controller_infos is None: controller_infos = self.controller_info # Keep around the connection factory for fail/recovery later if down_controller_ids is None: down_controller_ids = set() self.create_connection = create_connection connected_to_at_least_one = False for info in controller_infos: # Don't connect to down controllers if info.cid not in down_controller_ids: conn = create_connection(info, self, max_backoff_seconds=max_backoff_seconds) self.set_connection(conn) # cause errors to be raised conn.error_handler = self.error_handler self.cid2connection[info.cid] = conn connected_to_at_least_one = True return connected_to_at_least_one def send(self, *args, **kwargs): if self.failed: self.log.warn("Currently down. Dropping send()") else: super(FuzzSoftwareSwitch, self).send(*args, **kwargs) def get_connection(self, cid): if cid not in self.cid2connection.keys(): raise ValueError("No such connection %s" % str(cid)) return self.cid2connection[cid] def is_connected_to(self, cid): if cid in self.cid2connection.keys(): conn = self.get_connection(cid) return not conn.closed return False def fail(self): # TODO(cs): depending on the type of failure, a real switch failure # might not lead to an immediate disconnect if self.failed: self.log.warn("Switch already failed") return self.failed = True for connection in self.connections: connection.close() self.connections = [] def recover(self, down_controller_ids=None): if not self.failed: self.log.warn("Switch already up") return if self.create_connection is None: self.log.warn("Never connected in the first place") # We should only ever reconnect to live controllers, so set # max_backoff_seconds to a low number. connected_to_at_least_one = self.connect(self.create_connection, down_controller_ids=down_controller_ids, max_backoff_seconds=2) if connected_to_at_least_one: self.failed = False return connected_to_at_least_one def serialize(self): # Skip over non-serializable data, e.g. sockets # TODO(cs): is self.log going to be a problem? serializable = FuzzSoftwareSwitch(self.dpid, self.parent_controller_name) # Can't serialize files serializable.log = None # TODO(cs): need a cleaner way to add in the NOM port representation if self.software_switch: serializable.ofp_phy_ports = self.software_switch.ports.values() return pickle.dumps(serializable, protocol=0) def use_delayed_commands(self): ''' Tell the switch to buffer flow mods ''' self.delay_flow_mods = True self.on_message_received = self.on_message_received_delayed # barrier_deque has the structure: [(None, queue_1), (barrier_request_1, queue_2), ...] where... # - its elements have the form (barrier_in_request, next_queue_to_use) # - commands are always processed from the queue of the first element (i.e. barrier_deque[0][1]) # - when a new barrier_in request is received, a new tuple is appended to barrier_deque, containing: # (<the just-received request>, <queue for subsequent non-barrier_in commands until all previous commands have been processed>) # - the very first barrier_in is None because, there is no request to respond when we first start buffering commands self.barrier_deque = [(None, Queue.PriorityQueue())] def randomize_flow_mods(self, seed=None): ''' Initialize the RNG and tell switch to randomize order in which flow_mods are processed ''' self.randomize_flow_mod_order = True self.random = random.Random() if seed is not None: self.random.seed(seed) @property def current_cmd_queue(self): ''' Alias for the current epoch's pending flow_mods. ''' assert(len(self.barrier_deque) > 0) return self.barrier_deque[0][1] def _buffer_flow_mod(self, connection, msg, weight, buffr): ''' Called by on_message_received_delayed. Inserts a PendingReceive into self.openflow_buffer and sticks the message and receipt into the provided priority queue buffer for later retrieval. ''' forwarder = TableInserter.instance_for_connection(connection=connection, insert_method=super(FuzzSoftwareSwitch, self).on_message_received) receive = self.openflow_buffer.insert_pending_receipt(self.dpid, connection.cid, msg, forwarder) buffr.put((weight, msg, receive)) def on_message_received_delayed(self, connection, msg): ''' Precondition: use_delayed_commands() has been called. Replacement for NXSoftwareSwitch.on_message_received when delaying command processing ''' # TODO(jl): use exponential moving average (define in params) rather than uniform distribution # to prioritize oldest flow_mods assert(self.delay_flow_mods) def choose_weight(): ''' Return an appropriate weight to associate with a received command with when buffering. ''' if self.randomize_flow_mod_order: # TODO(jl): use exponential moving average (define in params) rather than uniform distribution # to prioritize oldest flow_mods return self.random.random() else: # behave like a normal FIFO queue return time.time() def handle_with_active_barrier_in(connection, msg): ''' Handling of flow_mods and barriers while operating under a barrier_in request''' if isinstance(msg, ofp_barrier_request): # create a new priority queue for all subsequent flow_mods self.barrier_deque.append((msg, Queue.PriorityQueue())) elif isinstance(msg, ofp_flow_mod): # stick the flow_mod on the queue of commands since the last barrier request weight = choose_weight() # N.B. len(self.barrier_deque) > 1, because an active barrier_in implies we appended a queue to barrier_deque, which # already always has at least one element: the default queue self._buffer_flow_mod(connection, msg, weight, buffr=self.barrier_deque[-1][1]) else: raise TypeError("Unsupported type for command buffering") def handle_without_active_barrier_in(connection, msg): if isinstance(msg, ofp_barrier_request): if self.current_cmd_queue.empty(): # if no commands waiting, reply to barrier immediately self.log.debug("Barrier request %s %s", self.name, str(msg)) barrier_reply = ofp_barrier_reply(xid = msg.xid) self.send(barrier_reply) else: self.barrier_deque.append((msg, Queue.PriorityQueue())) elif isinstance(msg, ofp_flow_mod): # proceed normally (no active or pending barriers) weight = choose_weight() self._buffer_flow_mod(connection, msg, weight, buffr=self.current_cmd_queue) else: raise TypeError("Unsupported type for command buffering") if isinstance(msg, ofp_flow_mod) or isinstance(msg, ofp_barrier_request): # Check if switch is currently operating under a barrier request # Note that we start out with len(self.barrier_deque) == 1, add an element for each barrier_in request, and never # delete when len(self.barrier_deque) == 1 if len(self.barrier_deque) > 1: handle_with_active_barrier_in(connection, msg) else: handle_without_active_barrier_in(connection, msg) else: # Immediately process all other messages super(FuzzSoftwareSwitch, self).on_message_received(connection, msg) def has_pending_commands(self): return not self.current_cmd_queue.empty() def get_next_command(self): """ Precondition: use_delayed_commands() has been invoked. Invoked periodically from fuzzer. Retrieves the next buffered command and its PendingReceive receipt. Throws Queue.Empty if the queue is empty. """ assert(self.delay_flow_mods) # tuples in barrier are of the form (weight, buffered command, pending receipt) (buffered_cmd, buffered_cmd_receipt) = self.current_cmd_queue.get_nowait()[1:] while self.current_cmd_queue.empty() and len(self.barrier_deque) > 1: # It's time to move to the next epoch and reply to the most recent barrier_request. # barrier_deque has the structure: [(None, queue_1), (barrier_request_1, queue_2), ...] # so when we empty queue_x, we just finished processing and thus must reply to barrier_request_x, which is coupled in # the next element in barrier_deque: (barrier_request_x, queue_x+1) self.barrier_deque.pop(0) finished_barrier_request = self.barrier_deque[0][0] if finished_barrier_request: self.log.debug("Barrier request %s %s", self.name, str(finished_barrier_request)) barrier_reply = ofp_barrier_reply(xid = finished_barrier_request.xid) self.send(barrier_reply) return (buffered_cmd, buffered_cmd_receipt) def process_delayed_command(self, buffered_cmd_receipt): """ Precondition: use_delayed_commands() has been invoked and buffered_cmd_receipt is the PendingReceive for a previously received and buffered command (i.e. was returned by get_next_command()) Returns the original buffered command """ assert(self.delay_flow_mods) return self.openflow_buffer.schedule(buffered_cmd_receipt) def show_flow_table(self): dl_types = { 0x0800: "IP", 0x0806: "ARP", 0x8100: "VLAN", 0x88cc: "LLDP", 0x888e: "PAE" } nw_protos = { 1 : "ICMP", 6 : "TCP", 17 : "UDP" } ports = { v: k.replace("OFPP_","") for (k,v) in of.ofp_port_rev_map.iteritems() } def dl_type(e): d = e.match.dl_type if d is None: return d else: return dl_types[d] if d in dl_types else "%x" %d def nw_proto(e): p = e.match.nw_proto return nw_protos[p] if p in nw_protos else p def action(a): if isinstance(a, ofp_action_output): return ports[a.port] if a.port in ports else "output(%d)" % a.port else: return str(a) def actions(e): if len(e.actions) == 0: return "(drop)" else: return ", ".join(action(a) for a in e.actions) t = Tabular((("Prio", lambda e: e.priority), ("in_port", lambda e: e.match.in_port), ("dl_type", dl_type), ("dl_src", lambda e: e.match.dl_src), ("dl_dst", lambda e: e.match.dl_dst), ("nw_proto", nw_proto), ("nw_src", lambda e: e.match.nw_src), ("nw_dst", lambda e: e.match.nw_dst), ("tp_src", lambda e: e.match.tp_src), ("tp_dst", lambda e: e.match.tp_dst), ("actions", actions), )) t.show(self.table.entries)