def start_pcap(self, filename): assert (self.pcap_writer == None) self.pcap_writer = PcapWriter(filename)
class DataPlane(Thread): """ This class provides methods to send and receive packets on the dataplane. It uses the DataPlanePort class, or an alternative implementation of that interface, to do IO on a particular port. A background thread is used to read packets from the dataplane ports and enqueue them to be read by the test. The kill() method must be called to shutdown this thread. """ MAX_QUEUE_LEN = 100 # When poll() fails to find a matching packet and reports the error, it # includes up to this many recent packets as context. POLL_MAX_RECENT_PACKETS = 3 def __init__(self, config=None): Thread.__init__(self) # dict from device number, port number to port object self.ports = {} # dict from device number, port number to list of (timestamp, packet) self.packet_queues = {} # counters of received packets (may include packets which were dropped due to queue overflow) self.rx_counters = defaultdict(int) # counters of transmited packets self.tx_counters = defaultdict(int) # cvar serves double duty as a regular top level lock and # as a condition variable self.cvar = Condition() # Used to wake up the event loop from another thread self.waker = ptfutils.EventDescriptor() self.killed = False self.logger = logging.getLogger("dataplane") self.pcap_writer = None if config is None: self.config = {} else: self.config = config ############################################################ # # The platform/config can provide a custom DataPlanePort class # here if you have a custom implementation with different # behavior. # # Set config.dataplane.portclass = MyDataPlanePortClass # where MyDataPlanePortClass has the same interface as the class # DataPlanePort defined here. # if self.config["platform"] == "nn": # assert is ok here because this is caught earlier in ptf assert (with_nnpy == True) self.dppclass = DataPlanePortNN elif "dataplane" in self.config and "portclass" in self.config[ "dataplane"]: self.dppclass = self.config["dataplane"]["portclass"] elif "linux" in sys.platform: self.dppclass = DataPlanePortLinux elif have_pypcap: self.dppclass = DataPlanePortPcap else: self.logger.warning( "Missing pypcap, VLAN tests may fail. See README for installation instructions." ) self.dppclass = DataPlanePort if "qlen" in self.config: self.qlen = self.config["qlen"] else: self.qlen = self.MAX_QUEUE_LEN self.start() def run(self): """ Activity function for class """ while not self.killed: sockets = set([p.get_packet_source() for p in self.ports.values()]) sockets.add(self.waker) try: sel_in, sel_out, sel_err = select.select(sockets, [], [], 1) except: print sys.exc_info() self.logger.error("Select error, exiting") break with self.cvar: for sel in sel_in: if sel == self.waker: self.waker.wait() continue else: # Enqueue packet t = sel.recv() if t is None: continue device_number, port_number, pkt, timestamp = t self.logger.debug( "Pkt len %d in on device %d, port %d", len(pkt), device_number, port_number) if self.pcap_writer: self.pcap_writer.write(pkt, timestamp, device_number, port_number) queue = self.packet_queues[(device_number, port_number)] if len(queue) >= self.qlen: # Queue full, throw away oldest queue.pop(0) self.logger.debug( "Discarding oldest packet to make room") queue.append((pkt, timestamp)) self.rx_counters[(device_number, port_number)] += 1 self.cvar.notify_all() self.logger.info("Thread exit") def set_qlen(self, qlen): self.qlen = qlen def port_add(self, interface_name, device_number, port_number): """ Add a port to the dataplane @param interface_name The name of the physical interface like eth1 @param device_number The device id used to refer to the device @param port_number The port number used to refer to the port Stashes the port number on the created port object. """ port_id = (device_number, port_number) with self.cvar: self.ports[port_id] = self.dppclass(interface_name, device_number, port_number, self.config) self.ports[port_id]._port_number = port_number self.ports[port_id]._device_number = device_number self.packet_queues[port_id] = [] # Need to wake up event loop to change the sockets being selected # on. self.waker.notify() # Returns true if success def port_remove(self, device_number, port_number): port_id = (device_number, port_number) with self.cvar: if port_id not in self.ports: self.logger.warn( "Invalid port_remove: no port {} for device {}".format( port_number, device_number)) return False del self.ports[port_id] del self.packet_queues[port_id] self.waker.notify() return True def send(self, device_number, port_number, packet): """ Send a packet to the given port @param device_number, port_number The port to send the data to @param packet Raw packet data to send to port """ if (device_number, port_number) not in self.ports: self.logger.error("send: no port %d for device %d", port_number, device_number) return 0 self.logger.debug("Sending %d bytes to device %d, port %d" % (len(packet), device_number, port_number)) if len(packet) < 15 and sys.platform.startswith('linux'): self.logger.warn( "The %s kernel may not send packets smaller than 15 bytes", sys.platform) if self.pcap_writer: self.pcap_writer.write(packet, time.time(), device_number, port_number) bytes = self.ports[(device_number, port_number)].send(packet) self.tx_counters[(device_number, port_number)] += 1 if bytes != len(packet): self.logger.error( "Unhandled send error, length mismatch %d != %d" % (bytes, len(packet))) return bytes def oldest_port_number(self, device): """ Returns the port number with the oldest packet, or None if no packets are queued. """ min_port_number = None min_time = float('inf') for (port_id, queue) in self.packet_queues.items(): if port_id[0] != device: continue if queue and queue[0][1] < min_time: min_time = queue[0][1] min_port_number = port_id[1] return min_port_number # Dequeues and yields packets in the order they were received. # Yields (port, packet, received time). # If port is not specified yields packets from all ports. def packets(self, device, port=None): while True: if port is None: rcv_port = self.oldest_port_number(device) else: rcv_port = port if rcv_port == None: self.logger.debug("Out of packets on all ports") break queue = self.packet_queues[(device, rcv_port)] if len(queue) == 0: self.logger.debug("Out of packets on device %d, port %d", device, rcv_port) break pkt, time = queue.pop(0) yield (rcv_port, pkt, time) PollResult = namedtuple('PollResult', ['device', 'port', 'packet', 'time']) """ A base class used for the result of poll(). No matter what kind of additional information subclasses include, for backwards compatibility callers must be able to unpack them as if they were a tuple with this format. The pair of the @device number and the @port number indicate where the packet was received, @packet is the packet itself, and @time is the time it was received. """ # On success, poll() returns a PollSuccess object which contains the device # number and port number on which a matching packet was found, the packet # itself, and the time at which it was received. class PollSuccess(PollResult): """ Returned by poll() when it successfully finds a matching packet. """ def __new__(cls, device, port, packet, expected_packet, time): """ Initialize. (We're an immutable tuple, so we can't use __init__.) """ self = super(DataPlane.PollSuccess, cls).__new__(cls, device, port, packet, time) self.expected_packet = expected_packet return self def format(self): """ Returns a string containing a nice (but verbose) representation of this packet. If the expected packet is a scapy packet, it's used to include detailed information about the fields in the packet. """ try: # The scapy packet dissection methods print directly to stdout, # so we have to redirect stdout to a string. sys.stdout = StringIO() print "========== RECEIVED ==========" if isinstance(self.expected_packet, scapy.packet.Packet): # Dissect this packet as if it were an instance of # the expected packet's class. scapy.packet.ls(self.expected_packet.__class__( self.packet)) print '--' scapy.utils.hexdump(self.packet) print "==============================" return sys.stdout.getvalue() finally: sys.stdout.close() sys.stdout = sys.__stdout__ # Restore the original stdout. class PollFailure(PollResult): """ Returned by poll() when it fails to match any packets. Contains metadata which can be used to diagnose the failure; callers will often want to include the result of format() in assertion failure messages. For backwards compatibility, when a PollFailure is treated as a tuple and unpacked, all PollResult fields are 'None'. """ def __new__(cls, expected_packet, recent_packets, packet_count): """ Initialize. (We're an immutable tuple, so we can't use __init__.) """ self = super(DataPlane.PollFailure, cls).__new__(cls, None, None, None, None) self.expected_packet = expected_packet self.recent_packets = recent_packets self.packet_count = packet_count return self def format(self): """ Returns a string containing a nice (but verbose) error report based on this PollFailure. If there was an expected packet, it's included in the output. If the expected packet is a scapy packet object, the output will include information about the fields in the packet. """ try: # The scapy packet dissection methods print directly to stdout, # so we have to redirect stdout to a string. sys.stdout = StringIO() if self.expected_packet is not None: print "========== EXPECTED ==========" if isinstance(self.expected_packet, scapy.packet.Packet): scapy.packet.ls(self.expected_packet) print '--' scapy.utils.hexdump(self.expected_packet) elif isinstance(self.expected_packet, mask.Mask): print 'Mask:', str(self.expected_packet) else: scapy.utils.hexdump(self.expected_packet) print "========== RECEIVED ==========" if self.recent_packets: print "%d total packets. Displaying most recent %d packets:" \ % (self.packet_count, len(self.recent_packets)) for packet in self.recent_packets: print "------------------------------" if isinstance(self.expected_packet, scapy.packet.Packet): # Dissect this packet as if it were an instance of # the expected packet's class. scapy.packet.ls( self.expected_packet.__class__(packet)) print '--' scapy.utils.hexdump(packet) else: print "%d total packets." % self.packet_count print "==============================" return sys.stdout.getvalue() finally: sys.stdout.close() sys.stdout = sys.__stdout__ # Restore the original stdout. def poll(self, device_number=0, port_number=None, timeout=-1, exp_pkt=None, filters=[]): """ Poll one or all dataplane ports for a packet If port_number is given, get the oldest packet from that port (and for that device). Otherwise, find the port with the oldest packet and return that packet. If exp_pkt is true, discard all packets until that one is found @param device_number Get packet from this device @param port_number If set, get packet from this port @param timeout If positive and no packet is available, block until a packet is received or for this many seconds @param exp_pkt If not None, look for this packet and ignore any others received. Note that if port_number is None, all packets from all ports will be discarded until the exp_pkt is found @return A PollSuccess object on success, or a PollFailure object on failure. See the definitions of those classes for more details. """ def filter_check(pkt): for f in filters: if not f(pkt): return False return True if exp_pkt and (port_number is None): self.logger.warn("Dataplane poll with exp_pkt but no port number") # A nested function can't assign to variables in its enclosing function # in Python 2, so the conventional hack is to put them in a dict. grab_log = { # A ring buffer to hold recent non-matching packets. 'recent_packets': deque(maxlen=DataPlane.POLL_MAX_RECENT_PACKETS), # A count of the total packets received. Since 'recent_packets' is a # ring buffer, we can't simply check its length. 'packet_count': 0 } # Retrieve the packet. Returns (device number, port number, packet, time). def grab(): self.logger.debug("Grabbing packet") for (rcv_port_number, pkt, time) in self.packets(device_number, port_number): rcv_device_number = device_number grab_log['recent_packets'].append(pkt) grab_log['packet_count'] += 1 self.logger.debug("Checking packet from device %d, port %d", rcv_device_number, rcv_port_number) if not filter_check(pkt): self.logger.debug( "Paket does not match filter, discarding") continue if not exp_pkt or match_exp_pkt(exp_pkt, pkt): return DataPlane.PollSuccess(rcv_device_number, rcv_port_number, pkt, exp_pkt, time) self.logger.debug("Did not find packet") return None with self.cvar: ret = ptfutils.timed_wait(self.cvar, grab, timeout=timeout) if ret is None: self.logger.debug( "Poll timeout, no packet from device %d, port %r", device_number, port_number) return DataPlane.PollFailure(exp_pkt, grab_log['recent_packets'], grab_log['packet_count']) return ret def kill(self): """ Stop the dataplane thread. """ self.killed = True self.waker.notify() self.join() # Explicitly release ports to ensure we don't run out of sockets # even if someone keeps holding a reference to the dataplane. del self.ports def port_down(self, device_number, port_number): """Brings the specified port down""" self.ports[(device_number, port_number)].down() def port_up(self, device_number, port_number): """Brings the specified port up""" self.ports[(device_number, port_number)].up() def get_mac(self, device_number, port_number): """Get the specified mac""" return self.ports[(device_number, port_number)].mac() def get_counters(self, device_number, port_number): """Get the counters mac""" return self.rx_counters[(device_number, port_number)], \ self.tx_counters[(device_number, port_number)] def get_nn_counters(self, device_number, port_number): """Get the specified port counters from nn agent """ return self.ports[(device_number, port_number)].nn_counters() def flush(self): """ Drop any queued packets. """ for port_id in self.packet_queues.keys(): self.packet_queues[port_id] = [] def start_pcap(self, filename): assert (self.pcap_writer == None) self.pcap_writer = PcapWriter(filename) def stop_pcap(self): if self.pcap_writer: with self.cvar: self.pcap_writer.close() self.pcap_writer = None self.cvar.notify_all()
class DataPlane(Thread): """ This class provides methods to send and receive packets on the dataplane. It uses the DataPlanePort class, or an alternative implementation of that interface, to do IO on a particular port. A background thread is used to read packets from the dataplane ports and enqueue them to be read by the test. The kill() method must be called to shutdown this thread. """ MAX_QUEUE_LEN = 100 def __init__(self, config=None): Thread.__init__(self) # dict from port number to port object self.ports = {} # dict from port number to list of (timestamp, packet) self.packet_queues = {} # cvar serves double duty as a regular top level lock and # as a condition variable self.cvar = Condition() # Used to wake up the event loop from another thread self.waker = ofutils.EventDescriptor() self.killed = False self.logger = logging.getLogger("dataplane") self.pcap_writer = None if config is None: self.config = {} else: self.config = config ############################################################ # # The platform/config can provide a custom DataPlanePort class # here if you have a custom implementation with different # behavior. # # Set config.dataplane.portclass = MyDataPlanePortClass # where MyDataPlanePortClass has the same interface as the class # DataPlanePort defined here. # if "dataplane" in self.config and "portclass" in self.config[ "dataplane"]: self.dppclass = self.config["dataplane"]["portclass"] elif have_pypcap: self.dppclass = DataPlanePortPcap else: self.logger.warning( "Missing pypcap, VLAN tests may fail. See README for installation instructions." ) self.dppclass = DataPlanePort self.start() def run(self): """ Activity function for class """ while not self.killed: sockets = [self.waker] + self.ports.values() try: sel_in, sel_out, sel_err = select.select(sockets, [], [], 1) except: print sys.exc_info() self.logger.error("Select error, exiting") break with self.cvar: for port in sel_in: if port == self.waker: self.waker.wait() continue else: # Enqueue packet pkt, timestamp = port.recv() port_number = port._port_number self.logger.debug("Pkt len %d in on port %d", len(pkt), port_number) if self.pcap_writer: self.pcap_writer.write(pkt, timestamp, port_number) queue = self.packet_queues[port_number] if len(queue) >= self.MAX_QUEUE_LEN: # Queue full, throw away oldest queue.pop(0) self.logger.debug( "Discarding oldest packet to make room") queue.append((pkt, timestamp)) self.cvar.notify_all() self.logger.info("Thread exit") def port_add(self, interface_name, port_number): """ Add a port to the dataplane @param interface_name The name of the physical interface like eth1 @param port_number The port number used to refer to the port Stashes the port number on the created port object. """ self.ports[port_number] = self.dppclass(interface_name, port_number) self.ports[port_number]._port_number = port_number self.packet_queues[port_number] = [] # Need to wake up event loop to change the sockets being selected on. self.waker.notify() def send(self, port_number, packet): """ Send a packet to the given port @param port_number The port to send the data to @param packet Raw packet data to send to port """ self.logger.debug("Sending %d bytes to port %d" % (len(packet), port_number)) if self.pcap_writer: self.pcap_writer.write(packet, time.time(), port_number) bytes = self.ports[port_number].send(packet) if bytes != len(packet): self.logger.error( "Unhandled send error, length mismatch %d != %d" % (bytes, len(packet))) return bytes def oldest_port_number(self): """ Returns the port number with the oldest packet, or None if no packets are queued. """ min_port_number = None min_time = float('inf') for (port_number, queue) in self.packet_queues.items(): if queue and queue[0][1] < min_time: min_time = queue[0][1] min_port_number = port_number return min_port_number # Dequeues and yields packets in the order they were received. # Yields (port number, packet, received time). # If port_number is not specified yields packets from all ports. def packets(self, port_number=None): while True: if port_number is None: rcv_port_number = self.oldest_port_number() else: rcv_port_number = port_number if rcv_port_number == None: self.logger.debug("Out of packets on all ports") break queue = self.packet_queues[rcv_port_number] if len(queue) == 0: self.logger.debug("Out of packets on port %d", rcv_port_number) break pkt, time = queue.pop(0) yield (rcv_port_number, pkt, time) def poll(self, port_number=None, timeout=-1, exp_pkt=None): """ Poll one or all dataplane ports for a packet If port_number is given, get the oldest packet from that port. Otherwise, find the port with the oldest packet and return that packet. If exp_pkt is true, discard all packets until that one is found @param port_number If set, get packet from this port @param timeout If positive and no packet is available, block until a packet is received or for this many seconds @param exp_pkt If not None, look for this packet and ignore any others received. Note that if port_number is None, all packets from all ports will be discarded until the exp_pkt is found @return The triple port_number, packet, pkt_time where packet is received from port_number at time pkt_time. If a timeout occurs, return None, None, None """ if exp_pkt and (port_number is None): self.logger.warn("Dataplane poll with exp_pkt but no port number") # Retrieve the packet. Returns (port number, packet, time). def grab(): self.logger.debug("Grabbing packet") for (rcv_port_number, pkt, time) in self.packets(port_number): self.logger.debug("Checking packet from port %d", rcv_port_number) if not exp_pkt or match_exp_pkt(exp_pkt, pkt): return (rcv_port_number, pkt, time) self.logger.debug("Did not find packet") return None with self.cvar: ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout) if ret != None: return ret else: self.logger.debug("Poll time out, no packet from " + str(port_number)) return (None, None, None) def kill(self): """ Stop the dataplane thread. """ self.killed = True self.waker.notify() self.join() # Explicitly release ports to ensure we don't run out of sockets # even if someone keeps holding a reference to the dataplane. del self.ports def port_down(self, port_number): """Brings the specified port down""" self.ports[port_number].down() def port_up(self, port_number): """Brings the specified port up""" self.ports[port_number].up() def flush(self): """ Drop any queued packets. """ for port_number in self.packet_queues.keys(): self.packet_queues[port_number] = [] def start_pcap(self, filename): assert (self.pcap_writer == None) self.pcap_writer = PcapWriter(filename) def stop_pcap(self): if self.pcap_writer: self.pcap_writer.close() self.pcap_writer = None
def start_pcap(self, filename): assert(self.pcap_writer == None) self.pcap_writer = PcapWriter(filename)
class DataPlane(Thread): """ This class provides methods to send and receive packets on the dataplane. It uses the DataPlanePort class, or an alternative implementation of that interface, to do IO on a particular port. A background thread is used to read packets from the dataplane ports and enqueue them to be read by the test. The kill() method must be called to shutdown this thread. """ MAX_QUEUE_LEN = 100 def __init__(self, config=None): Thread.__init__(self) # dict from port number to port object self.ports = {} # dict from port number to list of (timestamp, packet) self.packet_queues = {} # cvar serves double duty as a regular top level lock and # as a condition variable self.cvar = Condition() # Used to wake up the event loop from another thread self.waker = ofutils.EventDescriptor() self.killed = False self.logger = logging.getLogger("dataplane") self.pcap_writer = None if config is None: self.config = {} else: self.config = config; ############################################################ # # The platform/config can provide a custom DataPlanePort class # here if you have a custom implementation with different # behavior. # # Set config.dataplane.portclass = MyDataPlanePortClass # where MyDataPlanePortClass has the same interface as the class # DataPlanePort defined here. # if "dataplane" in self.config and "portclass" in self.config["dataplane"]: self.dppclass = self.config["dataplane"]["portclass"] elif have_pypcap: self.dppclass = DataPlanePortPcap else: self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.") self.dppclass = DataPlanePort self.start() def run(self): """ Activity function for class """ while not self.killed: sockets = [self.waker] + self.ports.values() try: sel_in, sel_out, sel_err = select.select(sockets, [], [], 1) except: print sys.exc_info() self.logger.error("Select error, exiting") break with self.cvar: for port in sel_in: if port == self.waker: self.waker.wait() continue else: # Enqueue packet pkt, timestamp = port.recv() port_number = port._port_number self.logger.debug("Pkt len %d in on port %d", len(pkt), port_number) if self.pcap_writer: self.pcap_writer.write(pkt, timestamp, port_number) queue = self.packet_queues[port_number] if len(queue) >= self.MAX_QUEUE_LEN: # Queue full, throw away oldest queue.pop(0) self.logger.debug("Discarding oldest packet to make room") queue.append((pkt, timestamp)) self.cvar.notify_all() self.logger.info("Thread exit") def port_add(self, interface_name, port_number): """ Add a port to the dataplane @param interface_name The name of the physical interface like eth1 @param port_number The port number used to refer to the port Stashes the port number on the created port object. """ self.ports[port_number] = self.dppclass(interface_name, port_number) self.ports[port_number]._port_number = port_number self.packet_queues[port_number] = [] # Need to wake up event loop to change the sockets being selected on. self.waker.notify() def send(self, port_number, packet): """ Send a packet to the given port @param port_number The port to send the data to @param packet Raw packet data to send to port """ self.logger.debug("Sending %d bytes to port %d" % (len(packet), port_number)) if self.pcap_writer: self.pcap_writer.write(packet, time.time(), port_number) bytes = self.ports[port_number].send(packet) if bytes != len(packet): self.logger.error("Unhandled send error, length mismatch %d != %d" % (bytes, len(packet))) return bytes def oldest_port_number(self): """ Returns the port number with the oldest packet, or None if no packets are queued. """ min_port_number = None min_time = float('inf') for (port_number, queue) in self.packet_queues.items(): if queue and queue[0][1] < min_time: min_time = queue[0][1] min_port_number = port_number return min_port_number # Dequeues and yields packets in the order they were received. # Yields (port number, packet, received time). # If port_number is not specified yields packets from all ports. def packets(self, port_number=None): while True: if port_number is None: rcv_port_number = self.oldest_port_number() else: rcv_port_number = port_number if rcv_port_number == None: self.logger.debug("Out of packets on all ports") break queue = self.packet_queues[rcv_port_number] if len(queue) == 0: self.logger.debug("Out of packets on port %d", rcv_port_number) break pkt, time = queue.pop(0) yield (rcv_port_number, pkt, time) def poll(self, port_number=None, timeout=-1, exp_pkt=None): """ Poll one or all dataplane ports for a packet If port_number is given, get the oldest packet from that port. Otherwise, find the port with the oldest packet and return that packet. If exp_pkt is true, discard all packets until that one is found @param port_number If set, get packet from this port @param timeout If positive and no packet is available, block until a packet is received or for this many seconds @param exp_pkt If not None, look for this packet and ignore any others received. Note that if port_number is None, all packets from all ports will be discarded until the exp_pkt is found @return The triple port_number, packet, pkt_time where packet is received from port_number at time pkt_time. If a timeout occurs, return None, None, None """ if exp_pkt and (port_number is None): self.logger.warn("Dataplane poll with exp_pkt but no port number") # Retrieve the packet. Returns (port number, packet, time). def grab(): self.logger.debug("Grabbing packet") for (rcv_port_number, pkt, time) in self.packets(port_number): self.logger.debug("Checking packet from port %d", rcv_port_number) if not exp_pkt or match_exp_pkt(exp_pkt, pkt): return (rcv_port_number, pkt, time) self.logger.debug("Did not find packet") return None with self.cvar: ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout) if ret != None: return ret else: self.logger.debug("Poll time out, no packet from " + str(port_number)) return (None, None, None) def kill(self): """ Stop the dataplane thread. """ self.killed = True self.waker.notify() self.join() # Explicitly release ports to ensure we don't run out of sockets # even if someone keeps holding a reference to the dataplane. del self.ports def port_down(self, port_number): """Brings the specified port down""" self.ports[port_number].down() def port_up(self, port_number): """Brings the specified port up""" self.ports[port_number].up() def flush(self): """ Drop any queued packets. """ for port_number in self.packet_queues.keys(): self.packet_queues[port_number] = [] def start_pcap(self, filename): assert(self.pcap_writer == None) self.pcap_writer = PcapWriter(filename) def stop_pcap(self): if self.pcap_writer: self.pcap_writer.close() self.pcap_writer = None
class DataPlane(Thread): """ This class provides methods to send and receive packets on the dataplane. It uses the DataPlanePort class, or an alternative implementation of that interface, to do IO on a particular port. A background thread is used to read packets from the dataplane ports and enqueue them to be read by the test. The kill() method must be called to shutdown this thread. """ MAX_QUEUE_LEN = 100 def __init__(self, config=None): Thread.__init__(self) # dict from port number to port object self.ports = {} # dict from port number to list of (timestamp, packet) self.packet_queues = {} # cvar serves double duty as a regular top level lock and # as a condition variable self.cvar = Condition() # Used to wake up the event loop from another thread self.waker = ofutils.EventDescriptor() self.killed = False self.logger = logging.getLogger("dataplane") self.pcap_writer = None if config is None: self.config = {} else: self.config = config; ############################################################ # # The platform/config can provide a custom DataPlanePort class # here if you have a custom implementation with different # behavior. # # Set config.dataplane.portclass = MyDataPlanePortClass # where MyDataPlanePortClass has the same interface as the class # DataPlanePort defined here. # if "dataplane" in self.config and "portclass" in self.config["dataplane"]: self.dppclass = self.config["dataplane"]["portclass"] elif "tstc" in self.config["platform"]: self.logger.info( "dataplane use stc adapter") self.dppclass = DataPlanePortStc elif "test" in self.config["platform"]: self.logger.info( "dataplane use stc adapter") self.dppclass = DataPlanePortTest elif "linux" in sys.platform: self.dppclass = DataPlanePortLinux else: self.dppclass = DataPlanePortPcap if "tstc" in self.config["platform"]: try: self.stc = stc_proxy.STC(remoteRpcServerIp = self.config["stcSeriverIp"], port = self.config["stcServierPort"]) self.chassisIp = self.config["chassisIp"] self.stc_version = self.stc.get("system1","-Version") self.logger.info("Spirent test center version-%s" % self.stc_version) log_filename_stc = time.strftime("remote_%Y_%m_%d_%H_%M_%S.log",time.localtime(time.time())) cResult = self.stc.config("automationoptions -logTo \"" + "log/" + log_filename_stc + "\" -logLevel DEBUG") self.logger.info("enable stc log to file . result %s ." % cResult) cResult = self.stc.connect(self.chassisIp) self.logger.info("connect to chassis . result %s ." % cResult) self.project = [] self.ethPhy = [] prj = self.stc.create("project") assert(prj != "") self.project.append(prj) except: self.logger.error("Configure remote rpc server failed, Please check whether remote server is running") raise self.start() def __del__(self): pass #Thread.__del__(self) def run(self): """ Activity function for class """ while not self.killed: if "tstc" in self.config["platform"] : #print time.asctime() time.sleep(1) else: sockets = [self.waker] + self.ports.values() try: sel_in, sel_out, sel_err = select.select(sockets, [], [], 1) except: print sys.exc_info() self.logger.error("Select error, exiting") break with self.cvar: for port in sel_in: if port == self.waker: self.waker.wait() continue else: # Enqueue packet pkt, timestamp = port.recv() port_number = port._port_number ''' self.logger.debug("Pkt len %d in on port %d", len(pkt), port_number) ''' if self.pcap_writer: self.pcap_writer.write(pkt, timestamp, port_number) queue = self.packet_queues[port_number] if len(queue) >= self.MAX_QUEUE_LEN: # Queue full, throw away oldest queue.pop(0) self.logger.debug("Discarding oldest packet to make room") queue.append((pkt, timestamp)) self.cvar.notify_all() self.logger.info("Thread exit") def port_add(self, interface_name, port_number): """ Add a port to the dataplane @param interface_name The name of the physical interface like eth1 @param port_number The port number used to refer to the port Stashes the port number on the created port object. """ if "tstc" in self.config["platform"]: try: cResult = self.stc.reserve( "//" + self.chassisIp + "/" + interface_name) self.logger.info( "cResult %s" % cResult) stc_port = self.stc.create( "port","-under" , self.project[0]) self.logger.info( "stc_port %s" % stc_port) cResult = self.stc.config( stc_port, " -location " , "//" + self.chassisIp + '/' + interface_name) self.logger.info( "cResult %s" % cResult) EthernetCopper = self.stc.create( "EthernetCopper" ,"-under",stc_port, "-Name" ,"ethernetCopper" + str(port_number)) self.logger.info( "EthernetCopper %s" % EthernetCopper) self.ethPhy.append(EthernetCopper) except: self.logger.error("add stc port error") raise self.ports[port_number] = stc_port else: self.ports[port_number] = self.dppclass(interface_name, port_number) self.ports[port_number]._port_number = port_number self.packet_queues[port_number] = [] # Need to wake up event loop to change the sockets being selected on. self.waker.notify() def send(self, port_number, packet): """ Send a packet to the given port @param port_number The port to send the data to @param packet Raw packet data to send to port """ self.logger.debug("Sending %d bytes to port %d" % (len(packet), port_number)) if self.pcap_writer: self.pcap_writer.write(packet, time.time(), port_number) bytes = self.ports[port_number].send(packet) if bytes != len(packet): self.logger.error("Unhandled send error, length mismatch %d != %d" % (bytes, len(packet))) return bytes def oldest_port_number(self): """ Returns the port number with the oldest packet, or None if no packets are queued. """ min_port_number = None min_time = float('inf') for (port_number, queue) in self.packet_queues.items(): if queue and queue[0][1] < min_time: min_time = queue[0][1] min_port_number = port_number return min_port_number # Dequeues and yields packets in the order they were received. # Yields (port number, packet, received time). # If port_number is not specified yields packets from all ports. def packets(self, port_number=None): while True: rcv_port_number = port_number or self.oldest_port_number() if rcv_port_number == None: self.logger.debug("Out of packets on all ports") break queue = self.packet_queues[rcv_port_number] if len(queue) == 0: self.logger.debug("Out of packets on port %d", rcv_port_number) break pkt, time = queue.pop(0) yield (rcv_port_number, pkt, time) def poll(self, port_number=None, timeout=-1, exp_pkt=None): """ Poll one or all dataplane ports for a packet If port_number is given, get the oldest packet from that port. Otherwise, find the port with the oldest packet and return that packet. If exp_pkt is true, discard all packets until that one is found @param port_number If set, get packet from this port @param timeout If positive and no packet is available, block until a packet is received or for this many seconds @param exp_pkt If not None, look for this packet and ignore any others received. Note that if port_number is None, all packets from all ports will be discarded until the exp_pkt is found @return The triple port_number, packet, pkt_time where packet is received from port_number at time pkt_time. If a timeout occurs, return None, None, None """ if exp_pkt and not port_number: self.logger.warn("Dataplane poll with exp_pkt but no port number") # Retrieve the packet. Returns (port number, packet, time). def grab(): self.logger.debug("Grabbing packet") for (rcv_port_number, pkt, time) in self.packets(port_number): self.logger.debug("Checking packet from port %d", rcv_port_number) if not exp_pkt or match_exp_pkt(exp_pkt, pkt): return (rcv_port_number, pkt, time) self.logger.debug("Did not find packet") return None with self.cvar: ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout) if ret != None: return ret else: self.logger.debug("Poll time out, no packet from " + str(port_number)) return (None, None, None) def kill(self): """ Stop the dataplane thread. """ if "tstc" in self.config["platform"]: for key in self.ports.keys(): self.logger.info( "release port : %s " % self.ports[key]) self.stc.release( self.stc.get(self.ports[key], "-location")) self.stc.disconnect( self.chassisIp) #delete project for prj in self.project: self.logger.info( "delete project : %s " % prj) self.stc.delete( prj) self.stc.perform( "ResetConfig" ,"-config", "system1") self.killed = True self.waker.notify() self.join() del self.waker # Explicitly release ports to ensure we don't run out of sockets # even if someone keeps holding a reference to the dataplane. del self.ports def port_down(self, port_number): """Brings the specified port down""" self.ports[port_number].down() def port_up(self, port_number): """Brings the specified port up""" self.ports[port_number].up() def flush(self): """ Drop any queued packets. """ for port_number in self.packet_queues.keys(): self.packet_queues[port_number] = [] def start_pcap(self, filename): assert(self.pcap_writer == None) self.pcap_writer = PcapWriter(filename) def stop_pcap(self): if self.pcap_writer: self.pcap_writer.close() self.pcap_writer = None