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 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))
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 __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 in_whitelist(dp_event): return (self.never_drop_whitelisted_packets and OpenFlowBuffer.in_whitelist(dp_event.fingerprint[0]))
def bootstrap(self, sync_callback, boot_controllers=default_boot_controllers): '''Return a simulation object encapsulating the state of the system in its initial starting point: - boots controllers - connects switches to controllers May be invoked multiple times! ''' def remove_monkey_patch(): if hasattr(select, "_old_select"): # Revert the previous monkeypatch to allow the new true_sockets to # connect select.select = select._old_select socket.socket = socket._old_socket def initialize_io_loop(): ''' boot the IOLoop (needed for the controllers) ''' _io_master = IOMaster() # monkey patch time.sleep for all our friends _io_master.monkey_time_sleep() # tell sts.console to use our io_master msg.set_io_master(_io_master) return _io_master def wire_controller_patch_panel(controller_manager, create_io_worker): patch_panel = None if not self.interpose_on_controllers: return patch_panel # N.B. includes local controllers in network namespaces or VMs. remote_controllers = controller_manager.remote_controllers if len(remote_controllers) != 0: patch_panel = self.controller_patch_panel_class( create_io_worker) for c in remote_controllers: patch_panel.register_controller(c.cid, c.guest_eth_addr, c.host_device) return patch_panel def instantiate_topology(create_io_worker): '''construct a clean topology object from topology_class and topology_params''' log.info("Creating topology...") # If you want to shoot yourself in the foot, feel free :) comma = "" if self._topology_params == "" else "," topology = eval( "%s(%s%screate_io_worker=create_io_worker)" % (self._topology_class.__name__, self._topology_params, comma)) return topology # Instantiate the pieces needed for Simulation's constructor remove_monkey_patch() io_master = initialize_io_loop() sync_connection_manager = STSSyncConnectionManager( io_master, sync_callback) controller_manager = boot_controllers(self.controller_configs, self.snapshot_service, sync_connection_manager) controller_patch_panel = wire_controller_patch_panel( controller_manager, io_master.create_worker_for_socket) topology = instantiate_topology(io_master.create_worker_for_socket) patch_panel = self._patch_panel_class(topology.switches, topology.hosts, topology.get_connected_port) openflow_buffer = OpenFlowBuffer() dataplane_trace = None if self._dataplane_trace_path is not None: dataplane_trace = Trace(self._dataplane_trace_path, topology) if self._violation_persistence_threshold is not None: violation_tracker = ViolationTracker( self._violation_persistence_threshold) else: violation_tracker = ViolationTracker() simulation = Simulation(topology, controller_manager, dataplane_trace, openflow_buffer, io_master, controller_patch_panel, patch_panel, sync_callback, self.multiplex_sockets, violation_tracker, self._kill_controllers_on_exit) self.current_simulation = simulation return simulation
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. """ _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 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)
def bootstrap(self, sync_callback=None, boot_controllers=default_boot_controllers): '''Return a simulation object encapsulating the state of the system in its initial starting point: - boots controllers - connects switches to controllers May be invoked multiple times! ''' if sync_callback is None: sync_callback = ReplaySyncCallback(None) def initialize_io_loop(): ''' boot the IOLoop (needed for the controllers) ''' _io_master = IOMaster() # monkey patch time.sleep for all our friends _io_master.monkey_time_sleep() # tell sts.console to use our io_master msg.set_io_master(_io_master) return _io_master def wire_controller_patch_panel(controller_manager, create_io_worker): patch_panel = None if not self.interpose_on_controllers: return patch_panel # N.B. includes local controllers in network namespaces or VMs. remote_controllers = controller_manager.remote_controllers if len(remote_controllers) != 0: patch_panel = self.controller_patch_panel_class(create_io_worker) for c in remote_controllers: patch_panel.register_controller(c.cid, c.guest_eth_addr, c.host_device) return patch_panel def instantiate_topology(create_io_worker): '''construct a clean topology object from topology_class and topology_params''' log.info("Creating topology...") # If you want to shoot yourself in the foot, feel free :) comma = "" if self._topology_params == "" else "," topology = eval("%s(%s%screate_io_worker=create_io_worker)" % (self._topology_class.__name__, self._topology_params, comma)) return topology def monkeypatch_select(multiplex_sockets, controller_manager): mux_select = None demuxers = [] if multiplex_sockets: log.debug("Monkeypatching STS select") revert_select_monkeypatch() # Monkey patch select to use our deterministic version mux_select = MultiplexedSelect() for c in controller_manager.controller_configs: # Connect the true sockets true_socket = connect_socket_with_backoff(address=c.address, port=c.port) true_socket.setblocking(0) io_worker = mux_select.create_worker_for_socket(true_socket) demux = STSSocketDemultiplexer(io_worker, c.server_info) demuxers.append(demux) # Monkey patch select.select select._old_select = select.select select.select = mux_select.select return (mux_select, demuxers) # Instantiate the pieces needed for Simulation's constructor revert_select_monkeypatch() io_master = initialize_io_loop() sync_connection_manager = STSSyncConnectionManager(io_master, sync_callback) controller_manager = boot_controllers(self.controller_configs, self.snapshot_service, sync_connection_manager, multiplex_sockets=self.multiplex_sockets) controller_patch_panel = wire_controller_patch_panel(controller_manager, io_master.create_worker_for_socket) topology = instantiate_topology(io_master.create_worker_for_socket) patch_panel = self._patch_panel_class(topology.switches, topology.hosts, topology.get_connected_port) openflow_buffer = OpenFlowBuffer() dataplane_trace = None if self._dataplane_trace_path is not None: dataplane_trace = Trace(self._dataplane_trace_path, topology) if self._violation_persistence_threshold is not None: violation_tracker = ViolationTracker(self._violation_persistence_threshold) else: violation_tracker = ViolationTracker() # Connect up MuxSelect if enabled (mux_select, demuxers) = monkeypatch_select(self.multiplex_sockets, controller_manager) simulation = Simulation(topology, controller_manager, dataplane_trace, openflow_buffer, io_master, controller_patch_panel, patch_panel, sync_callback, mux_select, demuxers, violation_tracker, self._kill_controllers_on_exit) if self.ignore_interposition: simulation.set_pass_through() self.current_simulation = simulation return simulation