diff options
Diffstat (limited to 'scripts/automation')
8 files changed, 896 insertions, 146 deletions
diff --git a/scripts/automation/trex_control_plane/stl/console/trex_capture.py b/scripts/automation/trex_control_plane/stl/console/trex_capture.py new file mode 100644 index 00000000..dfd7f0a4 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/console/trex_capture.py @@ -0,0 +1,574 @@ +from trex_stl_lib.api import * +from trex_stl_lib.utils import parsing_opts, text_tables +import threading +import tempfile +import select + +class CaptureMonitorWriter(object): + def init (self, start_ts): + raise NotImplementedError + + def deinit(self): + raise NotImplementedError + + def handle_pkts (self, pkts): + raise NotImplementedError + + def periodic_check (self): + raise NotImplementedError + + +class CaptureMonitorWriterStdout(CaptureMonitorWriter): + def __init__ (self, logger, is_brief): + self.logger = logger + self.is_brief = is_brief + + self.RX_ARROW = u'\u25c0\u2500\u2500' + self.TX_ARROW = u'\u25b6\u2500\u2500' + + def init (self, start_ts): + self.start_ts = start_ts + + self.logger.pre_cmd("Starting stdout capture monitor - verbose: '{0}'".format('low' if self.is_brief else 'high')) + self.logger.post_cmd(RC_OK) + + self.logger.log(format_text("\n*** use 'capture monitor stop' to abort capturing... ***\n", 'bold')) + + + def deinit (self): + pass + + + def periodic_check (self): + return RC_OK() + + def handle_pkts (self, pkts): + byte_count = 0 + + for pkt in pkts: + byte_count += self.__handle_pkt(pkt) + + self.logger.prompt_redraw() + return RC_OK(byte_count) + + + def get_scapy_name (self, pkt_scapy): + layer = pkt_scapy + while layer.payload and layer.payload.name not in('Padding', 'Raw'): + layer = layer.payload + + return layer.name + + + def format_origin (self, origin): + if origin == 'RX': + return u'{0} {1}'.format(self.RX_ARROW, 'RX') + elif origin == 'TX': + return u'{0} {1}'.format(self.TX_ARROW, 'TX') + else: + return '{0}'.format(origin) + + + def __handle_pkt (self, pkt): + pkt_bin = base64.b64decode(pkt['binary']) + + pkt_scapy = Ether(pkt_bin) + self.logger.log(format_text(u'\n\n#{} Port: {} {}\n'.format(pkt['index'], pkt['port'], self.format_origin(pkt['origin'])), 'bold', '')) + self.logger.log(format_text(' Type: {}, Size: {} B, TS: {:.2f} [sec]\n'.format(self.get_scapy_name(pkt_scapy), len(pkt_bin), pkt['ts'] - self.start_ts), 'bold')) + + + if self.is_brief: + self.logger.log(' {0}'.format(pkt_scapy.command())) + else: + pkt_scapy.show(label_lvl = ' ') + self.logger.log('') + + return len(pkt_bin) + +# +class CaptureMonitorWriterPipe(CaptureMonitorWriter): + def __init__ (self, logger): + self.logger = logger + + def init (self, start_ts): + self.start_ts = start_ts + self.fifo_name = tempfile.mktemp() + + try: + self.logger.pre_cmd('Starting pipe capture monitor') + os.mkfifo(self.fifo_name) + self.logger.post_cmd(RC_OK) + + self.logger.log(format_text("*** Please run 'wireshark -k -i {0}' ***".format(self.fifo_name), 'bold')) + + self.logger.pre_cmd("Waiting for Wireshark pipe connection") + self.fifo = os.open(self.fifo_name, os.O_WRONLY) + self.logger.post_cmd(RC_OK()) + + self.logger.log(format_text('\n*** Capture monitoring started ***\n', 'bold')) + + self.writer = RawPcapWriter(self.fifo_name, linktype = 1, sync = True) + self.writer._write_header(None) + + # register a poller + self.poll = select.poll() + self.poll.register(self.fifo, select.EPOLLERR) + + except KeyboardInterrupt as e: + self.logger.post_cmd(RC_ERR("")) + raise STLError("*** pipe monitor aborted...cleaning up") + + except OSError as e: + self.logger.post_cmd(RC_ERR("")) + raise STLError("failed to create pipe {0}\n{1}".format(self.fifo_name, str(e))) + + + def deinit (self): + try: + os.unlink(self.fifo_name) + except OSError: + pass + + + def periodic_check (self): + return self.check_pipe() + + + def check_pipe (self): + if self.poll.poll(0): + return RC_ERR('*** pipe has been disconnected - aborting monitoring ***') + + return RC_OK() + + + def handle_pkts (self, pkts): + rc = self.check_pipe() + if not rc: + return rc + + byte_count = 0 + + for pkt in pkts: + pkt_bin = base64.b64decode(pkt['binary']) + ts = pkt['ts'] + sec = int(ts) + usec = int( (ts - sec) * 1e6 ) + + try: + self.writer._write_packet(pkt_bin, sec = sec, usec = usec) + except IOError: + return RC_ERR("*** failed to write packet to pipe ***") + + byte_count += len(pkt_bin) + + return RC_OK(byte_count) + + +class CaptureMonitor(object): + def __init__ (self, client, cmd_lock): + self.client = client + self.cmd_lock = cmd_lock + self.active = False + self.capture_id = None + self.logger = client.logger + self.writer = None + + def is_active (self): + return self.active + + + def get_capture_id (self): + return self.capture_id + + + def start (self, tx_port_list, rx_port_list, rate_pps, mon_type): + try: + self.start_internal(tx_port_list, rx_port_list, rate_pps, mon_type) + except Exception as e: + self.__stop() + raise e + + def start_internal (self, tx_port_list, rx_port_list, rate_pps, mon_type): + # stop any previous monitors + if self.active: + self.stop() + + self.tx_port_list = tx_port_list + self.rx_port_list = rx_port_list + + if mon_type == 'compact': + self.writer = CaptureMonitorWriterStdout(self.logger, is_brief = True) + elif mon_type == 'verbose': + self.writer = CaptureMonitorWriterStdout(self.logger, is_brief = False) + elif mon_type == 'pipe': + self.writer = CaptureMonitorWriterPipe(self.logger) + else: + raise STLError('unknown writer type') + + + with self.logger.supress(): + data = self.client.start_capture(tx_port_list, rx_port_list, limit = rate_pps) + + self.capture_id = data['id'] + self.start_ts = data['ts'] + + self.writer.init(self.start_ts) + + + self.t = threading.Thread(target = self.__thread_cb) + self.t.setDaemon(True) + + try: + self.active = True + self.t.start() + except Exception as e: + self.active = False + self.stop() + raise e + + # entry point stop + def stop (self): + + if self.active: + self.stop_logged() + else: + self.__stop() + + # wraps stop with a logging + def stop_logged (self): + self.logger.pre_cmd("Stopping capture monitor") + + try: + self.__stop() + except Exception as e: + self.logger.post_cmd(RC_ERR("")) + raise e + + self.logger.post_cmd(RC_OK()) + + # internal stop + def __stop (self): + + # shutdown thread + if self.active: + self.active = False + self.t.join() + + # deinit the writer + if self.writer is not None: + self.writer.deinit() + self.writer = None + + # cleanup capture ID if possible + if self.capture_id is None: + return + + capture_id = self.capture_id + self.capture_id = None + + # if we are disconnected - we cannot cleanup the capture + if not self.client.is_connected(): + return + + try: + captures = [x['id'] for x in self.client.get_capture_status()] + if capture_id not in captures: + return + + with self.logger.supress(): + self.client.stop_capture(capture_id) + + except STLError as e: + self.logger.post_cmd(RC_ERR("")) + raise e + + + def get_mon_row (self): + if not self.is_active(): + return None + + return [self.capture_id, + self.pkt_count, + format_num(self.byte_count, suffix = 'B'), + ', '.join([str(x) for x in self.tx_port_list] if self.tx_port_list else '-'), + ', '.join([str(x) for x in self.rx_port_list] if self.rx_port_list else '-') + ] + + + # sleeps with high freq checks for active + def __sleep (self): + for _ in range(5): + if not self.active: + return False + + time.sleep(0.1) + + return True + + def __lock (self): + while True: + rc = self.cmd_lock.acquire(False) + if rc: + return True + + if not self.active: + return False + time.sleep(0.1) + + def __unlock (self): + self.cmd_lock.release() + + + def __thread_cb (self): + try: + rc = self.__thread_main_loop() + finally: + pass + + if not rc: + self.logger.log(str(rc)) + self.logger.log(format_text('\n*** monitor is inactive - please restart the monitor ***\n', 'bold')) + self.logger.prompt_redraw() + + + def __thread_main_loop (self): + self.pkt_count = 0 + self.byte_count = 0 + + while self.active: + + # sleep + if not self.__sleep(): + break + + # check that the writer is ok + rc = self.writer.periodic_check() + if not rc: + return rc + + # try to lock + if not self.__lock(): + break + + try: + if not self.client.is_connected(): + return RC_ERR('*** client has been disconnected, aborting monitoring ***') + rc = self.client._transmit("capture", params = {'command': 'fetch', 'capture_id': self.capture_id, 'pkt_limit': 10}) + if not rc: + return rc + + finally: + self.__unlock() + + + pkts = rc.data()['pkts'] + if not pkts: + continue + + rc = self.writer.handle_pkts(pkts) + if not rc: + return rc + + self.pkt_count += len(pkts) + self.byte_count += rc.data() + + # graceful shutdown + return RC_OK() + + + +# main class +class CaptureManager(object): + def __init__ (self, client, cmd_lock): + self.c = client + self.cmd_lock = cmd_lock + self.monitor = CaptureMonitor(client, cmd_lock) + self.logger = client.logger + + # install parsers + + self.parser = parsing_opts.gen_parser(self, "capture", self.parse_line_internal.__doc__) + self.subparsers = self.parser.add_subparsers(title = "commands", dest="commands") + + self.install_record_parser() + self.install_monitor_parser() + + # show + self.show_parser = self.subparsers.add_parser('show', help = "show all active captures") + + # reset + self.clear_parser = self.subparsers.add_parser('clear', help = "remove all active captures") + + # register handlers + self.cmds = {'record': self.parse_record, 'monitor' : self.parse_monitor, 'clear': self.parse_clear, 'show' : self.parse_show} + + + def install_record_parser (self): + # recording + self.record_parser = self.subparsers.add_parser('record', help = "PCAP recording") + record_sub = self.record_parser.add_subparsers(title = 'commands', dest = 'record_cmd') + self.record_start_parser = record_sub.add_parser('start', help = "starts a new buffered capture") + self.record_stop_parser = record_sub.add_parser('stop', help = "stops an active buffered capture") + + # start + self.record_start_parser.add_arg_list(parsing_opts.TX_PORT_LIST, + parsing_opts.RX_PORT_LIST, + parsing_opts.LIMIT) + + # stop + self.record_stop_parser.add_arg_list(parsing_opts.CAPTURE_ID, + parsing_opts.OUTPUT_FILENAME) + + + + def install_monitor_parser (self): + # monitor + self.monitor_parser = self.subparsers.add_parser('monitor', help = 'live monitoring') + monitor_sub = self.monitor_parser.add_subparsers(title = 'commands', dest = 'mon_cmd') + self.monitor_start_parser = monitor_sub.add_parser('start', help = 'starts a monitor') + self.monitor_stop_parser = monitor_sub.add_parser('stop', help = 'stops an active monitor') + + self.monitor_start_parser.add_arg_list(parsing_opts.TX_PORT_LIST, + parsing_opts.RX_PORT_LIST, + parsing_opts.MONITOR_TYPE) + + + + def stop (self): + self.monitor.stop() + + + # main entry point for parsing commands from console + def parse_line (self, line): + try: + self.parse_line_internal(line) + except STLError as e: + self.logger.log("\nAction has failed with the following error:\n" + format_text(e.brief() + "\n", 'bold')) + return RC_ERR(e.brief()) + + + def parse_line_internal (self, line): + '''Manage PCAP recorders''' + + # default + if not line: + line = "show" + + opts = self.parser.parse_args(line.split()) + if not opts: + return opts + + # call the handler + self.cmds[opts.commands](opts) + + + # record methods + def parse_record (self, opts): + if opts.record_cmd == 'start': + self.parse_record_start(opts) + elif opts.record_cmd == 'stop': + self.parse_record_stop(opts) + else: + self.record_parser.formatted_error("too few arguments") + + + def parse_record_start (self, opts): + if not opts.tx_port_list and not opts.rx_port_list: + self.record_start_parser.formatted_error('please provide either --tx or --rx') + return + + rc = self.c.start_capture(opts.tx_port_list, opts.rx_port_list, opts.limit) + + self.logger.log(format_text("*** Capturing ID is set to '{0}' ***".format(rc['id']), 'bold')) + self.logger.log(format_text("*** Please call 'capture record stop --id {0} -o <out.pcap>' when done ***\n".format(rc['id']), 'bold')) + + + def parse_record_stop (self, opts): + captures = self.c.get_capture_status() + ids = [c['id'] for c in captures] + + if opts.capture_id == self.monitor.get_capture_id(): + self.record_stop_parser.formatted_error("'{0}' is a monitor, please use 'capture monitor stop'".format(opts.capture_id)) + return + + if opts.capture_id not in ids: + self.record_stop_parser.formatted_error("'{0}' is not an active capture ID".format(opts.capture_id)) + return + + self.c.stop_capture(opts.capture_id, opts.output_filename) + + + # monitor methods + def parse_monitor (self, opts): + if opts.mon_cmd == 'start': + self.parse_monitor_start(opts) + elif opts.mon_cmd == 'stop': + self.parse_monitor_stop(opts) + else: + self.monitor_parser.formatted_error("too few arguments") + + + def parse_monitor_start (self, opts): + mon_type = 'compact' + + if opts.verbose: + mon_type = 'verbose' + elif opts.pipe: + mon_type = 'pipe' + + if not opts.tx_port_list and not opts.rx_port_list: + self.monitor_start_parser.formatted_error('please provide either --tx or --rx') + return + + self.monitor.stop() + self.monitor.start(opts.tx_port_list, opts.rx_port_list, 100, mon_type) + + def parse_monitor_stop (self, opts): + self.monitor.stop() + + def parse_clear (self, opts): + self.monitor.stop() + self.c.remove_all_captures() + + + + def parse_show (self, opts): + data = self.c.get_capture_status() + + # captures + cap_table = text_tables.TRexTextTable() + cap_table.set_cols_align(["c"] * 6) + cap_table.set_cols_width([15] * 6) + + # monitor + mon_table = text_tables.TRexTextTable() + mon_table.set_cols_align(["c"] * 5) + mon_table.set_cols_width([15] * 5) + + for elem in data: + id = elem['id'] + + if self.monitor.get_capture_id() == id: + row = self.monitor.get_mon_row() + mon_table.add_rows([row], header=False) + + else: + row = [id, + format_text(elem['state'], 'bold'), + '[{0}/{1}]'.format(elem['count'], elem['limit']), + format_num(elem['bytes'], suffix = 'B'), + bitfield_to_str(elem['filter']['tx']), + bitfield_to_str(elem['filter']['rx'])] + + cap_table.add_rows([row], header=False) + + cap_table.header(['ID', 'Status', 'Packets', 'Bytes', 'TX Ports', 'RX Ports']) + mon_table.header(['ID', 'Packets Seen', 'Bytes Seen', 'TX Ports', 'RX Ports']) + + if cap_table._rows: + text_tables.print_table_with_header(cap_table, '\nActive Recorders') + + if mon_table._rows: + text_tables.print_table_with_header(mon_table, '\nActive Monitor') + + diff --git a/scripts/automation/trex_control_plane/stl/console/trex_console.py b/scripts/automation/trex_control_plane/stl/console/trex_console.py index c9956472..83f36820 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -29,6 +29,8 @@ import string import os import sys import tty, termios +from threading import Lock +import threading try: import stl_path @@ -39,6 +41,7 @@ from trex_stl_lib.api import * from trex_stl_lib.utils.text_opts import * from trex_stl_lib.utils.common import user_input, get_current_user from trex_stl_lib.utils import parsing_opts +from .trex_capture import CaptureManager try: import trex_tui @@ -172,6 +175,8 @@ class TRexConsole(TRexGeneralCmd): def __init__(self, stateless_client, verbose = False): + self.cmd_lock = Lock() + self.stateless_client = stateless_client TRexGeneralCmd.__init__(self) @@ -184,8 +189,11 @@ class TRexConsole(TRexGeneralCmd): self.intro = "\n-=TRex Console v{ver}=-\n".format(ver=__version__) self.intro += "\nType 'help' or '?' for supported actions\n" + self.cap_mngr = CaptureManager(stateless_client, self.cmd_lock) + self.postcmd(False, "") + ################### internal section ######################## @@ -231,6 +239,7 @@ class TRexConsole(TRexGeneralCmd): lines = line.split(';') try: + self.cmd_lock.acquire() for line in lines: stop = self.onecmd(line) stop = self.postcmd(stop, line) @@ -238,10 +247,15 @@ class TRexConsole(TRexGeneralCmd): return "quit" return "" + except STLError as e: print(e) return '' + finally: + self.cmd_lock.release() + + def postcmd(self, stop, line): self.prompt = self.stateless_client.generate_prompt(prefix = 'trex') @@ -347,12 +361,12 @@ class TRexConsole(TRexGeneralCmd): @verify_connected - def do_set_rx_sniffer (self, line): - '''Sets a port sniffer on RX channel as PCAP recorder''' - self.stateless_client.set_rx_sniffer_line(line) + def do_capture (self, line): + '''Manage PCAP captures''' + self.cap_mngr.parse_line(line) - def help_sniffer (self): - self.do_set_rx_sniffer("-h") + def help_capture (self): + self.do_capture("-h") @verify_connected def do_resolve (self, line): @@ -443,7 +457,9 @@ class TRexConsole(TRexGeneralCmd): def do_disconnect (self, line): '''Disconnect from the server\n''' - + + # stop any monitors before disconnecting + self.cap_mngr.stop() self.stateless_client.disconnect_line(line) @@ -688,19 +704,24 @@ class TRexConsole(TRexGeneralCmd): l=help.splitlines() print("{:<30} {:<30}".format(cmd + " - ",l[0] )) + # a custorm cmdloop wrapper def start(self): - while True: - try: - self.cmdloop() - break - except KeyboardInterrupt as e: - if not readline.get_line_buffer(): - raise KeyboardInterrupt - else: - print("") - self.intro = None - continue + try: + while True: + try: + self.cmdloop() + break + except KeyboardInterrupt as e: + if not readline.get_line_buffer(): + raise KeyboardInterrupt + else: + print("") + self.intro = None + continue + + finally: + self.cap_mngr.stop() if self.terminal: self.terminal.kill() @@ -933,6 +954,8 @@ def main(): with stateless_client.logger.supress(): stateless_client.disconnect(stop_traffic = False) + + if __name__ == '__main__': main() diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 21ae42f1..f7432107 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -17,15 +17,18 @@ from .utils.text_opts import * from functools import wraps from texttable import ansi_len + from collections import namedtuple from yaml import YAMLError import time import datetime +import threading import re import random import json import traceback import os.path +import argparse ############################ logger ############################# ############################ ############################# @@ -600,12 +603,10 @@ class STLClient(object): self.util_stats, self.xstats, self.async_client.monitor) - - - - + ############# private functions - used by the class itself ########### + # some preprocessing for port argument def __ports (self, port_id_list): @@ -832,27 +833,6 @@ class STLClient(object): return rc - def __set_rx_sniffer (self, port_id_list, base_filename, limit): - port_id_list = self.__ports(port_id_list) - rc = RC() - - for port_id in port_id_list: - head, tail = os.path.splitext(base_filename) - filename = "{0}-{1}{2}".format(head, port_id, tail) - rc.add(self.ports[port_id].set_rx_sniffer(filename, limit)) - - return rc - - - def __remove_rx_sniffer (self, port_id_list): - port_id_list = self.__ports(port_id_list) - rc = RC() - - for port_id in port_id_list: - rc.add(self.ports[port_id].remove_rx_sniffer()) - - return rc - def __set_rx_queue (self, port_id_list, size): port_id_list = self.__ports(port_id_list) rc = RC() @@ -1071,7 +1051,7 @@ class STLClient(object): ############ functions used by other classes but not users ############## - def _validate_port_list (self, port_id_list): + def _validate_port_list (self, port_id_list, allow_empty = False): # listfiy single int if isinstance(port_id_list, int): port_id_list = [port_id_list] @@ -1080,7 +1060,7 @@ class STLClient(object): if not isinstance(port_id_list, list): raise STLTypeError('port_id_list', type(port_id_list), list) - if not port_id_list: + if not port_id_list and not allow_empty: raise STLError('No ports provided') valid_ports = self.get_all_ports() @@ -2084,9 +2064,9 @@ class STLClient(object): self.set_port_attr(ports, promiscuous = False, link_up = True if restart else None) - self.set_service_mode(ports, False) - self.remove_rx_sniffer(ports) self.remove_rx_queue(ports) + self.set_service_mode(ports, False) + except STLError as e: self.logger.post_cmd(False) @@ -2996,7 +2976,7 @@ class STLClient(object): Resolves ports (ARP resolution) :parameters: - ports - for which ports to apply a unique sniffer (each port gets a unique file) + ports - which ports to resolve retries - how many times to retry on each port (intervals of 100 milliseconds) verbose - log for each request the response :raises: @@ -3025,57 +3005,166 @@ class STLClient(object): @__api_check(True) - def set_rx_sniffer (self, ports = None, base_filename = 'rx.pcap', limit = 1000): + def start_capture (self, tx_ports, rx_ports, limit = 1000): """ - Sets a RX sniffer for port(s) written to a PCAP file + Starts a capture to PCAP on port(s) :parameters: - ports - for which ports to apply a unique sniffer (each port gets a unique file) - base_filename - filename will be appended with '-<port_number>', e.g. rx.pcap --> rx-0.pcap, rx-1.pcap etc. + tx_ports - on which ports to capture TX + rx_ports - on which ports to capture RX limit - limit how many packets will be written + + :returns: + returns a dictionary containing + {'id: <new_id>, 'ts': <starting timestamp>} + :raises: + :exe:'STLError' """ - ports = ports if ports is not None else self.get_acquired_ports() - ports = self._validate_port_list(ports) - + + tx_ports = self._validate_port_list(tx_ports, allow_empty = True) + rx_ports = self._validate_port_list(rx_ports, allow_empty = True) + merge_ports = set(tx_ports + rx_ports) + + if not merge_ports: + raise STLError("start_capture - must get at least one port to capture") + # check arguments - validate_type('base_filename', base_filename, basestring) validate_type('limit', limit, (int)) if limit <= 0: raise STLError("'limit' must be a positive value") - self.logger.pre_cmd("Setting RX sniffers on port(s) {0}:".format(ports)) - rc = self.__set_rx_sniffer(ports, base_filename, limit) + non_service_ports = list_difference(set(tx_ports + rx_ports), self.get_service_enabled_ports()) + if non_service_ports: + raise STLError("Port(s) {0} are not under service mode. PCAP capturing requires all ports to be in service mode".format(non_service_ports)) + + + self.logger.pre_cmd("Starting PCAP capturing up to {0} packets".format(limit)) + + rc = self._transmit("capture", params = {'command': 'start', 'limit': limit, 'tx': tx_ports, 'rx': rx_ports}) self.logger.post_cmd(rc) if not rc: raise STLError(rc) + return {'id': rc.data()['capture_id'], 'ts': rc.data()['ts']} + + + def __fetch_capture_packets (self, capture_id, output_filename, pkt_count): + self.logger.pre_cmd("Writing {0} packets to '{1}'".format(pkt_count, output_filename)) + + # create a PCAP file + writer = RawPcapWriter(output_filename, linktype = 1) + writer._write_header(None) + + # fetch + pending = pkt_count + rc = RC_OK() + while pending > 0: + rc = self._transmit("capture", params = {'command': 'fetch', 'capture_id': capture_id, 'pkt_limit': 50}) + if not rc: + self.logger.post_cmd(rc) + raise STLError(rc) + + pkts = rc.data()['pkts'] + pending = rc.data()['pending'] + start_ts = rc.data()['start_ts'] + + for pkt in pkts: + ts = pkt['ts'] - start_ts + ts_sec = int(ts) + ts_usec = int( (ts - ts_sec) * 1e6 ) + + pkt_bin = base64.b64decode(pkt['binary']) + writer._write_packet(pkt_bin, sec = ts_sec, usec = ts_usec) + + + + + self.logger.post_cmd(rc) + + @__api_check(True) - def remove_rx_sniffer (self, ports = None): + def stop_capture (self, capture_id, output_filename = None): """ - Removes RX sniffer from port(s) + Stops an active capture + + :parameters: + capture_id - an active capture ID to stop + output_filename - output filename to save capture :raises: + :exe:'STLError' """ - ports = ports if ports is not None else self.get_acquired_ports() - ports = self._validate_port_list(ports) - - self.logger.pre_cmd("Removing RX sniffers on port(s) {0}:".format(ports)) - rc = self.__remove_rx_sniffer(ports) + + # stopping a capture requires: + # 1. stopping + # 2. fetching + # 3. saving to file + + # stop + + self.logger.pre_cmd("Stopping PCAP capture {0}".format(capture_id)) + rc = self._transmit("capture", params = {'command': 'stop', 'capture_id': capture_id}) self.logger.post_cmd(rc) + if not rc: + raise STLError(rc) + + # pkt count + pkt_count = rc.data()['pkt_count'] + + # fetch packets + if output_filename: + self.__fetch_capture_packets(capture_id, output_filename, pkt_count) + + # remove + self.logger.pre_cmd("Removing PCAP capture {0} from server".format(capture_id)) + rc = self._transmit("capture", params = {'command': 'remove', 'capture_id': capture_id}) + self.logger.post_cmd(rc) + if not rc: + raise STLError(rc) + + + @__api_check(True) + def get_capture_status (self): + """ + returns a list of all active captures + each element in the list is an object containing + info about the capture + + """ + + rc = self._transmit("capture", params = {'command': 'status'}) if not rc: raise STLError(rc) - + return rc.data() + + @__api_check(True) + def remove_all_captures (self): + """ + Removes any existing captures + """ + captures = self.get_capture_status() + + self.logger.pre_cmd("Removing all PCAP captures from server") + + for c in captures: + # remove + rc = self._transmit("capture", params = {'command': 'remove', 'capture_id': c['id']}) + if not rc: + raise STLError(rc) + + self.logger.post_cmd(RC_OK()) + + + @__api_check(True) def set_rx_queue (self, ports = None, size = 1000): """ @@ -3196,6 +3285,7 @@ class STLClient(object): return wrap + @__console def ping_line (self, line): '''pings the server / specific IP''' @@ -3789,30 +3879,10 @@ class STLClient(object): opts.link, opts.led, opts.flow_ctrl) - + + - - @__console - def set_rx_sniffer_line (self, line): - '''Sets a port sniffer on RX channel in form of a PCAP file''' - - parser = parsing_opts.gen_parser(self, - "set_rx_sniffer", - self.set_rx_sniffer_line.__doc__, - parsing_opts.PORT_LIST_WITH_ALL, - parsing_opts.OUTPUT_FILENAME, - parsing_opts.LIMIT) - - opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) - if not opts: - return opts - - self.set_rx_sniffer(opts.ports, opts.output_filename, opts.limit) - - return RC_OK() - - @__console def resolve_line (self, line): '''Performs a port ARP resolution''' @@ -4010,3 +4080,4 @@ class STLClient(object): self.set_service_mode(ports = opts.ports, enabled = opts.enabled) + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py index a9509ee9..31d752af 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py @@ -56,7 +56,8 @@ class Port(object): def __init__ (self, port_id, user, comm_link, session_id, info): self.port_id = port_id - self.state = self.STATE_IDLE + self.state = self.STATE_IDLE + self.service_mode = False self.handler = None self.comm_link = comm_link @@ -247,14 +248,16 @@ class Port(object): raise Exception("port {0}: bad state received from server '{1}'".format(self.port_id, port_state)) self.owner = rc.data()['owner'] - + self.next_available_id = int(rc.data()['max_stream_id']) + 1 self.status = rc.data() - + # replace the attributes in a thread safe manner self.set_ts_attr(rc.data()['attr']) - + + self.service_mode = rc.data()['service'] + return self.ok() @@ -490,33 +493,17 @@ class Port(object): @owned - def set_rx_sniffer (self, pcap_filename, limit): + def start_capture (self, pcap_filename, mode, limit): - if not self.is_service_mode_on(): + if mode != 'tx' and not self.is_service_mode_on(): return self.err('port service mode must be enabled for performing RX capturing. Please enable service mode') params = {"handler": self.handler, "port_id": self.port_id, - "type": "capture", - "enabled": True, - "pcap_filename": pcap_filename, + "mode": mode, "limit": limit} - rc = self.transmit("set_rx_feature", params) - if rc.bad(): - return self.err(rc.err()) - - return self.ok() - - - @owned - def remove_rx_sniffer (self): - params = {"handler": self.handler, - "port_id": self.port_id, - "type": "capture", - "enabled": False} - - rc = self.transmit("set_rx_feature", params) + rc = self.transmit("start_capture", params) if rc.bad(): return self.err(rc.err()) @@ -719,23 +706,21 @@ class Port(object): @owned def set_service_mode (self, enabled): - rc = self.set_attr(rx_filter_mode = 'all' if enabled else 'hw') - if not rc: - return rc - - if not enabled: - rc = self.remove_rx_queue() - if not rc: - return rc - - rc = self.remove_rx_sniffer() - if not rc: - return rc - + params = {"handler": self.handler, + "port_id": self.port_id, + "enabled": enabled} + + rc = self.transmit("service", params) + if rc.bad(): + return self.err(rc.err()) + + self.service_mode = enabled return self.ok() + def is_service_mode_on (self): - return self.get_rx_filter_mode() == 'all' + return self.service_mode + @writeable def push_remote (self, pcap_filename, ipg_usec, speedup, count, duration, is_dual, slave_handler, min_ipg_usec): @@ -902,11 +887,6 @@ class Port(object): # RX info rx_info = self.status['rx_info'] - # RX sniffer - sniffer = rx_info['sniffer'] - info['rx_sniffer'] = '{0}\n[{1} / {2}]'.format(sniffer['pcap_filename'], sniffer['count'], sniffer['limit']) if sniffer['is_active'] else 'off' - - # RX queue queue = rx_info['queue'] info['rx_queue'] = '[{0} / {1}]'.format(queue['count'], queue['size']) if queue['is_active'] else 'off' @@ -928,8 +908,6 @@ class Port(object): def get_layer_cfg (self): return self.__attr['layer_cfg'] - def get_rx_filter_mode (self): - return self.__attr['rx_filter_mode'] def is_virtual(self): return self.info.get('is_virtual') @@ -1005,7 +983,6 @@ class Port(object): "layer mode": format_text(info['layer_mode'], 'green' if info['layer_mode'] == 'IPv4' else 'magenta'), "RX Filter Mode": info['rx_filter_mode'], "RX Queueing": info['rx_queue'], - "RX sniffer": info['rx_sniffer'], "Grat ARP": info['grat_arp'], } diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 38726062..21c9af87 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -682,7 +682,6 @@ class CTRexInfoGenerator(object): ("-----", []), ("RX Filter Mode", []), ("RX Queueing", []), - ("RX sniffer", []), ("Grat ARP", []), ] ) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py index cbbacb27..c386451b 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py @@ -107,4 +107,18 @@ def list_remove_dup (l): return tmp - +def bitfield_to_list (bf): + rc = [] + bitpos = 0 + + while bf > 0: + if bf & 0x1: + rc.append(bitpos) + bitpos += 1 + bf = bf >> 1 + + return rc + +def bitfield_to_str (bf): + lst = bitfield_to_list(bf) + return "-" if not lst else ', '.join([str(x) for x in lst]) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py index f5dab30c..8d3aedbe 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -63,9 +63,14 @@ PKT_SIZE SERVICE_OFF +TX_PORT_LIST +RX_PORT_LIST + SRC_IPV4 DST_IPV4 +CAPTURE_ID + GLOBAL_STATS PORT_STATS PORT_STATUS @@ -78,12 +83,18 @@ EXTENDED_INC_ZERO_STATS STREAMS_MASK CORE_MASK_GROUP +CAPTURE_PORTS_GROUP + +MONITOR_TYPE_VERBOSE +MONITOR_TYPE_PIPE +MONITOR_TYPE # ALL_STREAMS # STREAM_LIST_WITH_ALL # list of ArgumentGroup types MUTEX +NON_MUTEX ''' @@ -389,7 +400,6 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'help': 'Output PCAP filename', 'dest': 'output_filename', 'default': None, - 'required': True, 'type': str}), @@ -591,6 +601,45 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'default': True, 'help': 'Deactivates services on port(s)'}), + TX_PORT_LIST: ArgumentPack(['--tx'], + {'nargs': '+', + 'dest':'tx_port_list', + 'metavar': 'TX', + 'action': 'merge', + 'type': int, + 'help': 'A list of ports to capture on the TX side', + 'default': []}), + + + RX_PORT_LIST: ArgumentPack(['--rx'], + {'nargs': '+', + 'dest':'rx_port_list', + 'metavar': 'RX', + 'action': 'merge', + 'type': int, + 'help': 'A list of ports to capture on the RX side', + 'default': []}), + + + MONITOR_TYPE_VERBOSE: ArgumentPack(['-v', '--verbose'], + {'action': 'store_true', + 'dest': 'verbose', + 'default': False, + 'help': 'output to screen as verbose'}), + + MONITOR_TYPE_PIPE: ArgumentPack(['-p', '--pipe'], + {'action': 'store_true', + 'dest': 'pipe', + 'default': False, + 'help': 'forward packets to a pipe'}), + + + CAPTURE_ID: ArgumentPack(['-i', '--id'], + {'help': "capture ID to remove", + 'dest': "capture_id", + 'type': int, + 'required': True}), + # advanced options PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST, ALL_PORTS], @@ -615,6 +664,13 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], CORE_MASK], {'required': False}), + CAPTURE_PORTS_GROUP: ArgumentGroup(NON_MUTEX, [TX_PORT_LIST, RX_PORT_LIST], {}), + + + MONITOR_TYPE: ArgumentGroup(MUTEX, [MONITOR_TYPE_VERBOSE, + MONITOR_TYPE_PIPE], + {'required': False}), + } class _MergeAction(argparse._AppendAction): @@ -633,12 +689,30 @@ class _MergeAction(argparse._AppendAction): class CCmdArgParser(argparse.ArgumentParser): - def __init__(self, stateless_client, *args, **kwargs): + def __init__(self, stateless_client = None, x = None, *args, **kwargs): super(CCmdArgParser, self).__init__(*args, **kwargs) self.stateless_client = stateless_client self.cmd_name = kwargs.get('prog') self.register('action', 'merge', _MergeAction) + + def add_arg_list (self, *args): + populate_parser(self, *args) + + def add_subparsers(self, *args, **kwargs): + sub = super(CCmdArgParser, self).add_subparsers(*args, **kwargs) + + add_parser = sub.add_parser + stateless_client = self.stateless_client + + def add_parser_hook (self, *args, **kwargs): + parser = add_parser(self, *args, **kwargs) + parser.stateless_client = stateless_client + return parser + + sub.add_parser = add_parser_hook + return sub + # hook this to the logger def _print_message(self, message, file=None): self.stateless_client.logger.log(message) @@ -709,13 +783,15 @@ class CCmdArgParser(argparse.ArgumentParser): # recover from system exit scenarios, such as "help", or bad arguments. return RC_ERR("'{0}' - {1}".format(self.cmd_name, "no action")) + def formatted_error (self, msg): + self.print_usage() + self._print_message(('%s: error: %s\n') % (self.prog, msg)) + def get_flags (opt): return OPTIONS_DB[opt].name_or_flags -def gen_parser(stateless_client, op_name, description, *args): - parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve', - description=description) +def populate_parser (parser, *args): for param in args: try: @@ -731,6 +807,12 @@ def gen_parser(stateless_client, op_name, description, *args): for sub_argument in argument.args: group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags, **OPTIONS_DB[sub_argument].options) + + elif argument.type == NON_MUTEX: + group = parser.add_argument_group(**argument.options) + for sub_argument in argument.args: + group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags, + **OPTIONS_DB[sub_argument].options) else: # ignore invalid objects continue @@ -743,6 +825,12 @@ def gen_parser(stateless_client, op_name, description, *args): except KeyError as e: cause = e.args[0] raise KeyError("The attribute '{0}' is missing as a field of the {1} option.\n".format(cause, param)) + +def gen_parser(stateless_client, op_name, description, *args): + parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve', + description=description) + + populate_parser(parser, *args) return parser diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py index 63b05bf4..3ffd07e2 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py @@ -133,12 +133,16 @@ def underline(text): # apply attribute on each non-empty line def text_attribute(text, attribute): - return '\n'.join(['{start}{txt}{end}'.format( - start = TEXT_CODES[attribute]['start'], - txt = line, - end = TEXT_CODES[attribute]['end']) - if line else '' for line in ('%s' % text).split('\n')]) - + if isinstance(text, str): + return "{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], + txt=text, + stop=TEXT_CODES[attribute]['end']) + elif isinstance(text, unicode): + return u"{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], + txt=text, + stop=TEXT_CODES[attribute]['end']) + else: + raise Exception("not a string") FUNC_DICT = {'blue': blue, 'bold': bold, |