diff options
71 files changed, 3713 insertions, 1321 deletions
diff --git a/linux/ws_main.py b/linux/ws_main.py index 6dccf597..66d79d13 100755 --- a/linux/ws_main.py +++ b/linux/ws_main.py @@ -112,6 +112,7 @@ main_src = SrcGroup(dir='src', 'utl_yaml.cpp', 'rx_check_header.cpp', 'nat_check.cpp', + 'nat_check_flow_table.cpp', 'timer_wheel_pq.cpp', 'time_histogram.cpp', 'utl_json.cpp', @@ -122,6 +123,7 @@ main_src = SrcGroup(dir='src', 'flow_stat.cpp', 'flow_stat_parser.cpp', 'trex_watchdog.cpp', + 'trex_client_config.cpp', 'pal/linux/pal_utl.cpp', 'pal/linux/mbuf.cpp', 'pal/common/common_mbuf.cpp', diff --git a/linux_dpdk/ws_main.py b/linux_dpdk/ws_main.py index 72ea5e3d..00e72edf 100755 --- a/linux_dpdk/ws_main.py +++ b/linux_dpdk/ws_main.py @@ -100,6 +100,7 @@ main_src = SrcGroup(dir='src', 'global_io_mode.cpp', 'main_dpdk.cpp', 'trex_watchdog.cpp', + 'trex_client_config.cpp', 'debug.cpp', 'flow_stat.cpp', 'flow_stat_parser.cpp', @@ -116,6 +117,7 @@ main_src = SrcGroup(dir='src', 'utl_json.cpp', 'utl_yaml.cpp', 'nat_check.cpp', + 'nat_check_flow_table.cpp', 'msg_manager.cpp', 'publisher/trex_publisher.cpp', 'pal/linux_dpdk/pal_utl.cpp', diff --git a/scripts/automation/regression/CPlatform.py b/scripts/automation/regression/CPlatform.py index de1c22ce..dc5418cb 100755 --- a/scripts/automation/regression/CPlatform.py +++ b/scripts/automation/regression/CPlatform.py @@ -20,7 +20,7 @@ class CPlatform(object): self.tftp_cfg = None self.config_history = { 'basic_if_config' : False, 'tftp_server_config' : False } - def configure_basic_interfaces(self, mtu = 4000): + def configure_basic_interfaces(self, mtu = 9050): cache = CCommandCache() for dual_if in self.if_mngr.get_dual_if_list(): @@ -46,7 +46,7 @@ class CPlatform(object): - def configure_basic_filtered_interfaces(self, intf_list, mtu = 4000): + def configure_basic_filtered_interfaces(self, intf_list, mtu = 9050): cache = CCommandCache() for intf in intf_list: @@ -70,11 +70,10 @@ class CPlatform(object): res = self.cmd_link.run_single_command(cache) if 'Rollback Done' not in res: print('Failed to load clean config, trying again') + time.sleep(2) if i < 4: continue raise Exception('Could not load clean config, response: %s' % res) - if i > 0: # were errors, better to wait - time.sleep(2) def config_pbr (self, mode = 'config'): idx = 1 diff --git a/scripts/automation/regression/functional_tests/stl_basic_tests.py b/scripts/automation/regression/functional_tests/stl_basic_tests.py index a4e28ca9..e03c0742 100644 --- a/scripts/automation/regression/functional_tests/stl_basic_tests.py +++ b/scripts/automation/regression/functional_tests/stl_basic_tests.py @@ -85,7 +85,7 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): pkts1 = list(RawPcapReader(output)) pkts2 = list(RawPcapReader(golden)) - assert_equal(len(pkts1), len(pkts2)) + assert_equal(len(pkts1), len(pkts2), 'Lengths of generated pcap (%s) and golden (%s) are different' % (output, golden)) for pkt1, pkt2, i in zip(pkts1, pkts2, range(1, len(pkts1))): ts1 = float(pkt1[1][0]) + (float(pkt1[1][1]) / 1e6) @@ -143,7 +143,7 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): os.unlink(output_cap) try: rc = self.run_sim(input_file, output_cap, options, silent) - assert_equal(rc, True) + assert_equal(rc, True, 'Simulation on profile %s failed.' % profile) #s='cp '+output_cap+' '+golden_file; #print s #os.system(s) @@ -165,7 +165,7 @@ class CStlBasic_Test(functional_general_test.CGeneralFunctional_Test): profile.dump_to_code(generated_filename) rc = self.run_sim(generated_filename, output_cap, options, silent) - assert_equal(rc, True) + assert_equal(rc, True, 'Simulation on profile %s (generated) failed.' % profile) if compare: self.compare_caps(output_cap, golden_file) diff --git a/scripts/automation/regression/setups/kiwi02/benchmark.yaml b/scripts/automation/regression/setups/kiwi02/benchmark.yaml index 3332aa5e..e6621085 100644 --- a/scripts/automation/regression/setups/kiwi02/benchmark.yaml +++ b/scripts/automation/regression/setups/kiwi02/benchmark.yaml @@ -69,6 +69,8 @@ test_nat_simple_mode1: &test_nat_simple test_nat_simple_mode2: *test_nat_simple +test_nat_simple_mode3: *test_nat_simple + test_nat_learning: << : *test_nat_simple nat_opened : 100000 diff --git a/scripts/automation/regression/setups/trex-dan/benchmark.yaml b/scripts/automation/regression/setups/trex-dan/benchmark.yaml index e9f12c45..de56089b 100644 --- a/scripts/automation/regression/setups/trex-dan/benchmark.yaml +++ b/scripts/automation/regression/setups/trex-dan/benchmark.yaml @@ -69,6 +69,8 @@ test_nat_simple_mode1: &test_nat_simple test_nat_simple_mode2: *test_nat_simple +test_nat_simple_mode3: *test_nat_simple + test_nat_learning: << : *test_nat_simple bw_per_core : 7.377 diff --git a/scripts/automation/regression/setups/trex07/benchmark.yaml b/scripts/automation/regression/setups/trex07/benchmark.yaml index 4778de91..0dc340b0 100644 --- a/scripts/automation/regression/setups/trex07/benchmark.yaml +++ b/scripts/automation/regression/setups/trex07/benchmark.yaml @@ -2,169 +2,243 @@ #### TRex benchmark configuration file #### ############################################################### -test_nbar_simple : - multiplier : 7.5 - cores : 2 - exp_gbps : 3.5 - cpu_to_core_ratio : 20800000 - cpu2core_custom_dev: YES - cpu2core_dev : 0.07 - exp_max_latency : 1000 - - nbar_classification: - rtp : 32.57 - http : 30.25 - oracle-sqlnet : 11.23 - exchange : 10.80 - citrix : 5.62 - rtsp : 2.84 - dns : 1.95 - smtp : 0.57 - pop3 : 0.36 - ssl : 0.17 - sctp : 0.13 - sip : 0.09 - unknown : 3.41 - -test_rx_check : - multiplier : 13 - cores : 3 - rx_sample_rate : 128 - exp_gbps : 6 - cpu_to_core_ratio : 37270000 - exp_bw : 13 - exp_latency : 1 - -test_nat_simple : &test_nat_simple - stat_route_dict : +#### common templates ### + +stat_route_dict: &stat_route_dict clients_start : 16.0.0.1 servers_start : 48.0.0.1 dual_port_mask : 1.0.0.0 client_destination_mask : 255.0.0.0 server_destination_mask : 255.0.0.0 - nat_dict : + +nat_dict: &nat_dict clients_net_start : 16.0.0.0 client_acl_wildcard_mask : 0.0.0.255 dual_port_mask : 1.0.0.0 pool_start : 200.0.0.0 pool_netmask : 255.255.255.0 - multiplier : 12000 - cores : 1 - cpu_to_core_ratio : 37270000 - exp_bw : 1 - exp_latency : 1 - allow_timeout_dev : YES - -test_nat_simple_mode1 : *test_nat_simple -test_nat_simple_mode2 : *test_nat_simple - -test_nat_learning : - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - multiplier : 12000 - cores : 1 - nat_opened : 40000 - cpu_to_core_ratio : 270 - exp_bw : 8 - exp_latency : 1 - allow_timeout_dev : YES - -test_routing_imix_64 : - multiplier : 430 - cores : 1 - cpu_to_core_ratio : 280 - exp_latency : 1 - -test_routing_imix : - multiplier : 10 - cores : 1 - cpu_to_core_ratio : 1800 - exp_latency : 1 - -test_static_routing_imix : - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - multiplier : 8 - cores : 1 - cpu_to_core_ratio : 1800 - exp_latency : 1 -test_static_routing_imix_asymmetric: - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - multiplier : 8 - cores : 1 - cpu_to_core_ratio : 1800 - exp_latency : 1 - -test_ipv6_simple : - multiplier : 9 - cores : 2 - cpu_to_core_ratio : 30070000 - cpu2core_custom_dev: YES - cpu2core_dev : 0.07 - - -test_rx_check_sfr: - multiplier : 10 - cores : 2 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 - -test_rx_check_http: - multiplier : 15000 - cores : 1 - rx_sample_rate : 16 - # allow 0.03% errors, bad routerifconfig - error_tolerance : 0.03 -test_rx_check_sfr_ipv6: - multiplier : 10 - cores : 2 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 +### stateful ### + +test_jumbo: + multiplier : 17 + cores : 1 + bw_per_core : 543.232 + + +test_routing_imix: + multiplier : 10 + cores : 1 + bw_per_core : 34.128 + + +test_routing_imix_64: + multiplier : 430 + cores : 1 + bw_per_core : 5.893 + + +test_static_routing_imix: &test_static_routing_imix + stat_route_dict : *stat_route_dict + multiplier : 8 + cores : 1 + bw_per_core : 34.339 + +test_static_routing_imix_asymmetric: *test_static_routing_imix + + +test_ipv6_simple: + multiplier : 9 + cores : 2 + bw_per_core : 19.064 + + +test_nat_simple_mode1: &test_nat_simple + stat_route_dict : *stat_route_dict + nat_dict : *nat_dict + multiplier : 6000 + cores : 1 + nat_opened : 500000 + allow_timeout_dev : True + bw_per_core : 44.445 + +test_nat_simple_mode2: *test_nat_simple + +test_nat_simple_mode3: *test_nat_simple + +test_nat_learning: *test_nat_simple + + +test_nbar_simple: + multiplier : 7.5 + cores : 2 + bw_per_core : 17.174 + nbar_classification: + rtp : 32.57 + http : 30.25 + oracle_sqlnet : 11.23 + exchange : 10.80 + citrix : 5.62 + rtsp : 2.84 + dns : 1.95 + smtp : 0.57 + pop3 : 0.36 + ssl : 0.17 + sctp : 0.13 + sip : 0.09 + unknown : 3.41 + + +test_rx_check_http: &rx_http + multiplier : 15000 + cores : 1 + rx_sample_rate : 16 + bw_per_core : 39.560 test_rx_check_http_ipv6: - multiplier : 15000 - cores : 1 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 - -test_rx_check_http_negative: - multiplier : 13000 - cores : 1 - rx_sample_rate : 16 - # allow 0.03% errors, bad router - error_tolerance : 0.03 - stat_route_dict : - clients_start : 16.0.0.1 - servers_start : 48.0.0.1 - dual_port_mask : 1.0.0.0 - client_destination_mask : 255.0.0.0 - server_destination_mask : 255.0.0.0 - nat_dict : - clients_net_start : 16.0.0.0 - client_acl_wildcard_mask : 0.0.0.255 - dual_port_mask : 1.0.0.0 - pool_start : 200.0.0.0 - pool_netmask : 255.255.255.0 + << : *rx_http + bw_per_core : 49.237 + +test_rx_check_http_negative_disabled: + << : *rx_http + stat_route_dict : *stat_route_dict + nat_dict : *nat_dict + + +test_rx_check_sfr: &rx_sfr + multiplier : 10 + cores : 3 + rx_sample_rate : 16 + bw_per_core : 16.082 + +test_rx_check_sfr_ipv6: + << : *rx_sfr + bw_per_core : 19.198 + + + +### stateless ### + +test_CPU_benchmark: + profiles: + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 64} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 64, stream_count: 10} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 64, stream_count: 100} + cpu_util : 1 + bw_per_core : 1 + +# causes queue full +# - name : stl/udp_for_benchmarks.py +# kwargs : {packet_len: 64, stream_count: 1000} +# cpu_util : 1 +# bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 128} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 256} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 512} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 1500} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 4000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 9000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 9000, stream_count: 10} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_for_benchmarks.py + kwargs : {packet_len: 9000, stream_count: 100} + cpu_util : 1 + bw_per_core : 1 + +# not enough memory + queue full if memory increase +# - name : stl/udp_for_benchmarks.py +# kwargs : {packet_len: 9000, stream_count: 1000} +# cpu_util : 1 +# bw_per_core : 1 + + - name : stl/imix.py + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 64} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 128} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 256} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 512} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 1500} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 4000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_1pkt_tuple_gen.py + kwargs : {packet_len: 9000} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/pcap.py + kwargs : {ipg_usec: 2, loop_count: 0} + cpu_util : 1 + bw_per_core : 1 + + - name : stl/udp_rand_len_9k.py + cpu_util : 1 + bw_per_core : 1 + + - name : stl/hlt/hlt_udp_rand_len_9k.py + cpu_util : 1 + bw_per_core : 1 -test_jumbo: - multiplier : 17 - cores : 1 diff --git a/scripts/automation/regression/setups/trex14/benchmark.yaml b/scripts/automation/regression/setups/trex14/benchmark.yaml index aa4ac2d4..04f13e79 100644 --- a/scripts/automation/regression/setups/trex14/benchmark.yaml +++ b/scripts/automation/regression/setups/trex14/benchmark.yaml @@ -57,17 +57,17 @@ test_ipv6_simple: test_nat_simple_mode1: &test_nat_simple stat_route_dict : *stat_route_dict nat_dict : *nat_dict - multiplier : 12000 + multiplier : 6000 cores : 1 - nat_opened : 40000 + nat_opened : 500000 allow_timeout_dev : True bw_per_core : 44.445 test_nat_simple_mode2: *test_nat_simple -test_nat_learning: - << : *test_nat_simple - nat_opened : 40000 +test_nat_simple_mode3: *test_nat_simple + +test_nat_learning: *test_nat_simple test_nbar_simple: @@ -101,7 +101,7 @@ test_rx_check_http_ipv6: << : *rx_http bw_per_core : 49.237 -test_rx_check_http_negative: +test_rx_check_http_negative_disabled: << : *rx_http stat_route_dict : *stat_route_dict nat_dict : *nat_dict diff --git a/scripts/automation/regression/setups/trex25/benchmark.yaml b/scripts/automation/regression/setups/trex25/benchmark.yaml index 19fab1fe..ccbdf6f5 100644 --- a/scripts/automation/regression/setups/trex25/benchmark.yaml +++ b/scripts/automation/regression/setups/trex25/benchmark.yaml @@ -70,6 +70,8 @@ test_nat_simple_mode1: &test_nat_simple test_nat_simple_mode2: *test_nat_simple +test_nat_simple_mode3: *test_nat_simple + test_nat_learning: << : *test_nat_simple nat_opened : 40000 diff --git a/scripts/automation/regression/stateful_tests/trex_general_test.py b/scripts/automation/regression/stateful_tests/trex_general_test.py index 82b1d9d1..8ff4fdaf 100755 --- a/scripts/automation/regression/stateful_tests/trex_general_test.py +++ b/scripts/automation/regression/stateful_tests/trex_general_test.py @@ -139,41 +139,42 @@ class CTRexGeneral_Test(unittest.TestCase): if res[name] != float(val): self.fail('TRex results[%s]==%f and not as expected %f ' % (name, res[name], val)) - def check_CPU_benchmark (self, trex_res, err = 25, minimal_cpu = 30, maximal_cpu = 85): - #cpu_util = float(trex_res.get_last_value("trex-global.data.m_cpu_util")) - cpu_util = sum(trex_res.get_value_list("trex-global.data.m_cpu_util")[-4:-1]) / 3.0 # mean of 3 values before last - - if '1G' in self.modes: - minimal_cpu /= 10.0 - - if not self.is_virt_nics: - if cpu_util > maximal_cpu: - self.fail("CPU is too high (%s%%), probably queue full." % cpu_util ) - #if cpu_util < minimal_cpu: - # self.fail("CPU is too low (%s%%), can't verify performance in such low CPU%%." % cpu_util ) - - test_norm_cpu = sum(trex_res.get_value_list("trex-global.data.m_bw_per_core")[-4:-1]) / 3.0 - - print("TRex CPU utilization: %g%%, norm_cpu is : %g Gb/core" % (round(cpu_util, 2), round(test_norm_cpu))) - - expected_norm_cpu = self.get_benchmark_param('bw_per_core') - if not expected_norm_cpu: - expected_norm_cpu = 1 - - calc_error_precent = abs(100.0 * test_norm_cpu / expected_norm_cpu - 100) - print('Err percent: %s' % calc_error_precent) - #if calc_error_precent > err and cpu_util > 10: - # self.fail('Excepted bw_per_core ratio: %s, got: %g' % (expected_norm_cpu, round(test_norm_cpu))) - - # report benchmarks - if self.GAManager: - try: - setup_test = '%s.%s' % (CTRexScenario.setup_name, self.get_name()) - self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core', value = int(test_norm_cpu)) - self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core_exp', value = int(expected_norm_cpu)) - self.GAManager.emptyAndReportQ() - except Exception as e: - print('Sending GA failed: %s' % e) + def check_CPU_benchmark (self, trex_res, err = 25, minimal_cpu = 10, maximal_cpu = 85): + cpu_util = trex_res.get_avg_steady_state_value('trex-global.data.m_cpu_util_raw') + trex_tx_bps = trex_res.get_avg_steady_state_value('trex-global.data.m_tx_bps') + expected_norm_cpu = self.get_benchmark_param('bw_per_core') + cores = self.get_benchmark_param('cores') + ports_count = trex_res.get_ports_count() + test_norm_cpu = trex_tx_bps / (cpu_util * ports_count * cores * 2.5e6) + + if '1G' in self.modes: + minimal_cpu /= 10.0 + + if not self.is_virt_nics: + if cpu_util > maximal_cpu: + self.fail("CPU is too high (%s%%), probably queue full." % cpu_util ) + #if cpu_util < minimal_cpu: + # self.fail("CPU is too low (%s%%), can't verify performance in such low CPU%%." % cpu_util ) + + print("TRex CPU utilization: %g%%, norm_cpu is : %g Gb/core" % (round(cpu_util, 2), round(test_norm_cpu, 2))) + + if not expected_norm_cpu: + expected_norm_cpu = 1 + + calc_error_precent = abs(100.0 * test_norm_cpu / expected_norm_cpu - 100) + print('Err percent: %s' % calc_error_precent) + #if calc_error_precent > err and cpu_util > 10: + # self.fail('Excepted bw_per_core ratio: %s, got: %g' % (expected_norm_cpu, round(test_norm_cpu))) + + # report benchmarks + if self.GAManager: + try: + setup_test = '%s.%s' % (CTRexScenario.setup_name, self.get_name()) + self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core', value = int(test_norm_cpu)) + self.GAManager.gaAddAction(Event = 'stateful_test', action = setup_test, label = 'bw_per_core_exp', value = int(expected_norm_cpu)) + self.GAManager.emptyAndReportQ() + except Exception as e: + print('Sending GA failed: %s' % e) def check_results_gt (self, res, name, val): if res is None: diff --git a/scripts/automation/regression/stateful_tests/trex_nat_test.py b/scripts/automation/regression/stateful_tests/trex_nat_test.py index 6e030ffe..c23f67c4 100755 --- a/scripts/automation/regression/stateful_tests/trex_nat_test.py +++ b/scripts/automation/regression/stateful_tests/trex_nat_test.py @@ -93,6 +93,9 @@ class CTRexNat_Test(CTRexGeneral_Test):#(unittest.TestCase): def test_nat_simple_mode2(self): self.nat_simple_helper(learn_mode=2) + def test_nat_simple_mode3(self): + self.nat_simple_helper(learn_mode=3) + def nat_simple_helper(self, learn_mode=1): # test initializtion self.router.configure_basic_interfaces() diff --git a/scripts/automation/regression/stateful_tests/trex_rx_test.py b/scripts/automation/regression/stateful_tests/trex_rx_test.py index 40528d16..c08ad1ea 100755 --- a/scripts/automation/regression/stateful_tests/trex_rx_test.py +++ b/scripts/automation/regression/stateful_tests/trex_rx_test.py @@ -250,7 +250,7 @@ class CTRexRx_Test(CTRexGeneral_Test): print('Run until finish, expect errors') old_errors = copy.deepcopy(self.fail_reasons) - nat_dict = self.get_benchmark_param('nat_dict', test_name = 'test_nat_simple') + nat_dict = self.get_benchmark_param('nat_dict', test_name = 'test_nat_simple_mode1') nat_obj = CNatConfig(nat_dict) self.router.config_nat(nat_obj) self.router.config_zbf() diff --git a/scripts/automation/regression/stateless_tests/stl_examples_test.py b/scripts/automation/regression/stateless_tests/stl_examples_test.py index d8b85dfc..71fc3287 100755 --- a/scripts/automation/regression/stateless_tests/stl_examples_test.py +++ b/scripts/automation/regression/stateless_tests/stl_examples_test.py @@ -10,14 +10,12 @@ class STLExamples_Test(CStlGeneral_Test): def explicitSetUp(self): # examples connect by their own if self.is_connected(): - self.recover_after_trex_210_issue() CTRexScenario.stl_trex.disconnect() def explicitTearDown(self): # connect back at end of tests if not self.is_connected(): self.stl_trex.connect() - self.recover_after_trex_210_issue() def test_stl_examples(self): examples_dir = '../trex_control_plane/stl/examples' diff --git a/scripts/automation/regression/stateless_tests/stl_general_test.py b/scripts/automation/regression/stateless_tests/stl_general_test.py index 82738f96..5ae2b326 100644 --- a/scripts/automation/regression/stateless_tests/stl_general_test.py +++ b/scripts/automation/regression/stateless_tests/stl_general_test.py @@ -6,10 +6,6 @@ from trex_stl_lib.api import * import time from nose.tools import nottest -def setUpModule(): - if CTRexScenario.stl_trex.is_connected(): - CStlGeneral_Test.recover_after_trex_210_issue() - class CStlGeneral_Test(CTRexGeneral_Test): """This class defines the general stateless testcase of the TRex traffic generator""" @@ -20,21 +16,6 @@ class CStlGeneral_Test(CTRexGeneral_Test): if CTRexScenario.stl_init_error: self.skip(CTRexScenario.stl_init_error) - # workaround of http://trex-tgn.cisco.com/youtrack/issue/trex-210 - @staticmethod - def recover_after_trex_210_issue(): - return - for i in range(20): - try: - stl_map_ports(CTRexScenario.stl_trex) - break - except: - CTRexScenario.stl_trex.disconnect() - time.sleep(0.5) - CTRexScenario.stl_trex.connect() - # verify problem is solved - stl_map_ports(CTRexScenario.stl_trex) - def connect(self, timeout = 100): # need delay and check only because TRex process might be still starting sys.stdout.write('Connecting') @@ -85,18 +66,22 @@ class STLBasic_Test(CStlGeneral_Test): @nottest def test_connectivity(self): if not self.is_loopback: - if CTRexScenario.router_cfg['forceImageReload']: - CTRexScenario.router.load_clean_config() - CTRexScenario.router.configure_basic_interfaces() - CTRexScenario.router.config_pbr(mode = "config") - - err = 'Client could not connect' - CTRexScenario.stl_init_error = err + try: + if CTRexScenario.router_cfg['forceImageReload']: + CTRexScenario.router.load_clean_config() + CTRexScenario.router.configure_basic_interfaces() + CTRexScenario.router.config_pbr(mode = "config") + except Exception as e: + CTRexScenario.stl_init_error = 'Could not configure device, err: %s' % e + self.fail(CTRexScenario.stl_init_error) + return if not self.connect(): - self.fail(err) - err = 'Client could not map ports' - CTRexScenario.stl_init_error = err + CTRexScenario.stl_init_error = 'Client could not connect' + self.fail(CTRexScenario.stl_init_error) + return + print('Connected') if not self.map_ports(): - self.fail(err) + CTRexScenario.stl_init_error = 'Client could not map ports' + self.fail(CTRexScenario.stl_init_error) + return print('Got ports mapping: %s' % CTRexScenario.stl_ports_map) - CTRexScenario.stl_init_error = None diff --git a/scripts/automation/regression/stateless_tests/stl_rx_test.py b/scripts/automation/regression/stateless_tests/stl_rx_test.py index 238ff53d..090261ff 100644 --- a/scripts/automation/regression/stateless_tests/stl_rx_test.py +++ b/scripts/automation/regression/stateless_tests/stl_rx_test.py @@ -9,7 +9,7 @@ class STLRX_Test(CStlGeneral_Test): """Tests for RX feature""" def setUp(self): - per_driver_params = {"rte_vmxnet3_pmd": [1, 50, 1,False], "rte_ixgbe_pmd": [30, 5000, 1,True,200,400], "rte_i40e_pmd": [80, 5000, 1,True,100,250], + per_driver_params = {"rte_vmxnet3_pmd": [1, 50, 1,False], "rte_ixgbe_pmd": [30, 1000, 1,True,300,400], "rte_i40e_pmd": [80, 1000, 1,True,100,250], "rte_igb_pmd": [80, 500, 1,False], "rte_em_pmd": [1, 50, 1,False], "rte_virtio_pmd": [1, 50, 1,False]} CStlGeneral_Test.setUp(self) @@ -35,6 +35,7 @@ class STLRX_Test(CStlGeneral_Test): self.rate_lat = per_driver_params[drv_name][2] else: self.rate_lat = self.rate_percent + self.lat_pps = 1000 self.drops_expected = False self.c.reset(ports = [self.tx_port, self.rx_port]) @@ -252,15 +253,16 @@ class STLRX_Test(CStlGeneral_Test): for data in streams_data: if data['lat']: flow_stats = STLFlowLatencyStats(pg_id = 5) + mode = STLTXSingleBurst(total_pkts = total_pkts, percentage = self.rate_percent) else: flow_stats = STLFlowStats(pg_id = 5) + mode = STLTXSingleBurst(total_pkts = total_pkts, pps = self.lat_pps) s = STLStream(name = data['name'], packet = data['pkt'], flow_stats = flow_stats, - mode = STLTXSingleBurst(total_pkts = total_pkts, - percentage = self.rate_percent - )) + mode = mode + ) streams.append(s) print("\ninjecting {0} packets on port {1}".format(total_pkts, self.tx_port)) diff --git a/scripts/automation/regression/stateless_tests/trex_client_pkg_test.py b/scripts/automation/regression/stateless_tests/trex_client_pkg_test.py index 905882fe..14ef36f7 100755 --- a/scripts/automation/regression/stateless_tests/trex_client_pkg_test.py +++ b/scripts/automation/regression/stateless_tests/trex_client_pkg_test.py @@ -3,9 +3,6 @@ from .stl_general_test import CStlGeneral_Test, CTRexScenario from misc_methods import run_command from nose.plugins.attrib import attr -def setUpModule(): - CStlGeneral_Test.unzip_client_package() - @attr('client_package') class CTRexClientPKG_Test(CStlGeneral_Test): """This class tests TRex client package""" @@ -14,14 +11,13 @@ class CTRexClientPKG_Test(CStlGeneral_Test): CStlGeneral_Test.setUp(self) # examples connect by their own if CTRexScenario.stl_trex.is_connected(): - self.recover_after_trex_210_issue() CTRexScenario.stl_trex.disconnect() + CStlGeneral_Test.unzip_client_package() def tearDown(self): # connect back at end of tests if not CTRexScenario.stl_trex.is_connected(): CTRexScenario.stl_trex.connect() - self.recover_after_trex_210_issue() CStlGeneral_Test.tearDown(self) def run_client_package_stl_example(self, python_version): diff --git a/scripts/automation/regression/trex.py b/scripts/automation/regression/trex.py index 44f2faba..aad8f041 100644 --- a/scripts/automation/regression/trex.py +++ b/scripts/automation/regression/trex.py @@ -38,8 +38,8 @@ class CTRexScenario: is_copied = False GAManager = None no_daemon = False - router_image = None debug_image = False + test = None class CTRexRunner: """This is an instance for generating a CTRexRunner""" diff --git a/scripts/automation/regression/trex_unit_test.py b/scripts/automation/regression/trex_unit_test.py index 915cd682..4f13a50f 100755 --- a/scripts/automation/regression/trex_unit_test.py +++ b/scripts/automation/regression/trex_unit_test.py @@ -30,7 +30,7 @@ import outer_packages import nose from nose.plugins import Plugin -import logging +from nose.selector import Selector import CustomLogger import misc_methods from rednose import RedNose @@ -43,11 +43,24 @@ from trex_stl_lib.utils.GAObjClass import GAmanager import trex import socket from pprint import pprint -import subprocess -import re import time from distutils.dir_util import mkpath +# nose overrides + +# option to select wanted test by name without file, class etc. +def new_Selector_wantMethod(self, method, orig_Selector_wantMethod = Selector.wantMethod): + result = orig_Selector_wantMethod(self, method) + return result and (not CTRexScenario.test or CTRexScenario.test in getattr(method, '__name__', '')) + +Selector.wantMethod = new_Selector_wantMethod + +def new_Selector_wantFunction(self, function, orig_Selector_wantFunction = Selector.wantFunction): + result = orig_Selector_wantFunction(self, function) + return result and (not CTRexScenario.test or CTRexScenario.test in getattr(function, '__name__', '')) + +Selector.wantFunction = new_Selector_wantFunction + # override nose's strange representation of setUpClass errors def __suite_repr__(self): if hasattr(self.context, '__module__'): # inside class, setUpClass etc. @@ -59,6 +72,8 @@ def __suite_repr__(self): nose.suite.ContextSuite.__repr__ = __suite_repr__ nose.suite.ContextSuite.__str__ = __suite_repr__ +# /nose overrides + def check_trex_path(trex_path): if os.path.isfile('%s/trex_daemon_server' % trex_path): return os.path.abspath(trex_path) @@ -78,7 +93,7 @@ def get_trex_path(): def address_to_ip(address): - for i in range(10): + for i in range(5): try: return socket.gethostbyname(address) except: @@ -149,6 +164,8 @@ class CTRexTestConfiguringPlugin(Plugin): parser.add_option('--trex-args', action='store', default = '', dest="trex_args", help="Additional TRex arguments (--no-watchdog etc.).") + parser.add_option('-t', '--test', action='store', default = '', dest='test', + help='Test name to run (without file, class etc.)') def configure(self, options, conf): @@ -160,6 +177,7 @@ class CTRexTestConfiguringPlugin(Plugin): self.json_verbose = options.json_verbose self.telnet_verbose = options.telnet_verbose self.no_daemon = options.no_daemon + CTRexScenario.test = options.test if self.collect_only or self.functional: return if CTRexScenario.setup_dir and options.config_path: @@ -181,7 +199,7 @@ class CTRexTestConfiguringPlugin(Plugin): self.loggerPath = options.log_path # initialize CTRexScenario global testing class, to be used by all tests CTRexScenario.configuration = self.configuration - CTRexScenario.no_daemon = self.no_daemon + CTRexScenario.no_daemon = options.no_daemon CTRexScenario.benchmark = self.benchmark CTRexScenario.modes = set(self.modes) CTRexScenario.server_logs = self.server_logs @@ -347,15 +365,14 @@ if __name__ == "__main__": nose_argv += sys_args - config_plugin = CTRexTestConfiguringPlugin() - red_nose = RedNose() + addplugins = [RedNose(), CTRexTestConfiguringPlugin()] result = True try: if len(CTRexScenario.test_types['functional_tests']): additional_args = ['--func'] + CTRexScenario.test_types['functional_tests'] if xml_arg: additional_args += ['--with-xunit', xml_arg.replace('.xml', '_functional.xml')] - result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin]) + result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) if len(CTRexScenario.test_types['stateful_tests']): additional_args = ['--stf'] if '--warmup' in sys.argv: @@ -365,14 +382,14 @@ if __name__ == "__main__": additional_args.extend(['-a', '!client_package']) if xml_arg: additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateful.xml')] - result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin]) and result + result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) and result if len(CTRexScenario.test_types['stateless_tests']): additional_args = ['--stl', 'stateless_tests/stl_general_test.py:STLBasic_Test.test_connectivity'] + CTRexScenario.test_types['stateless_tests'] if not test_client_package: additional_args.extend(['-a', '!client_package']) if xml_arg: additional_args += ['--with-xunit', xml_arg.replace('.xml', '_stateless.xml')] - result = nose.run(argv = nose_argv + additional_args, addplugins = [red_nose, config_plugin]) and result + result = nose.run(argv = nose_argv + additional_args, addplugins = addplugins) and result #except Exception as e: # result = False # print(e) diff --git a/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py b/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py index f044f623..ecf6083b 100755 --- a/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py +++ b/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py @@ -73,6 +73,8 @@ class CTRexClient(object): sets a verbose output on supported class method. default value : **False** + trex_args : string + additional arguments passed to TRex. For example, "-w 3 --no-watchdog" :raises: socket errors, in case server could not be reached. @@ -82,22 +84,23 @@ class CTRexClient(object): self.trex_host = socket.gethostbyname(trex_host) except: # give it another try self.trex_host = socket.gethostbyname(trex_host) - self.trex_daemon_port = trex_daemon_port - self.master_daemon_port = master_daemon_port - self.trex_zmq_port = trex_zmq_port - self.seq = None - self._last_sample = time.time() - self.__default_user = get_current_user() - self.verbose = verbose - self.result_obj = CTRexResult(max_history_size, filtered_latency_amount) - self.decoder = JSONDecoder() - self.history = jsonrpclib.history.History() - self.master_daemon_path = "http://{hostname}:{port}/".format( hostname = self.trex_host, port = master_daemon_port ) - self.master_daemon = jsonrpclib.Server(self.master_daemon_path, history = self.history) - self.trex_server_path = "http://{hostname}:{port}/".format( hostname = self.trex_host, port = trex_daemon_port ) - self.server = jsonrpclib.Server(self.trex_server_path, history = self.history) - self.debug_image = debug_image - self.trex_args = trex_args + self.trex_daemon_port = trex_daemon_port + self.master_daemon_port = master_daemon_port + self.trex_zmq_port = trex_zmq_port + self.seq = None + self._last_sample = time.time() + self.__default_user = get_current_user() + self.verbose = verbose + self.result_obj = CTRexResult(max_history_size, filtered_latency_amount) + self.decoder = JSONDecoder() + self.history = jsonrpclib.history.History() + self.master_daemon_path = "http://{hostname}:{port}/".format( hostname = self.trex_host, port = master_daemon_port ) + self.master_daemon = jsonrpclib.Server(self.master_daemon_path, history = self.history) + self.trex_server_path = "http://{hostname}:{port}/".format( hostname = self.trex_host, port = trex_daemon_port ) + self.server = jsonrpclib.Server(self.trex_server_path, history = self.history) + self.debug_image = debug_image + self.trex_args = trex_args + self.sample_to_run_finish = self.sample_until_finish # alias for legacy def add (self, x, y): @@ -310,7 +313,7 @@ class CTRexClient(object): for i in range(int(timeout / poll_rate)): if not self.get_trex_cmds(): return True - sleep(poll_rate) + time.sleep(poll_rate) if self.get_trex_cmds(): return False return True @@ -535,7 +538,7 @@ class CTRexClient(object): finally: self.prompt_verbose_data() - def sample_until_condition (self, condition_func, time_between_samples = 5): + def sample_until_condition (self, condition_func, time_between_samples = 1): """ Automatically sets ongoing sampling of TRex data, with sampling rate described by time_between_samples. @@ -549,7 +552,7 @@ class CTRexClient(object): time_between_samples : int determines the time between each sample of the server - default value : **5** + default value : **1** :return: the first result object (see :class:`CTRexResult` for further details) of the TRex run on which the condition has been met. @@ -579,15 +582,15 @@ class CTRexClient(object): # this could come from provided method 'condition_func' raise - def sample_to_run_finish (self, time_between_samples = 5): + def sample_until_finish (self, time_between_samples = 1): """ - Automatically sets automatically sampling of TRex data with sampling rate described by time_between_samples until TRex run finished. + Automatically samples TRex data with sampling rate described by time_between_samples until TRex run finishes. :parameters: time_between_samples : int determines the time between each sample of the server - default value : **5** + default value : **1** :return: the latest result object (see :class:`CTRexResult` for further details) with sampled data. @@ -609,7 +612,7 @@ class CTRexClient(object): results = self.get_result_obj() return results - def sample_x_seconds (self, sample_time, time_between_samples = 5): + def sample_x_seconds (self, sample_time, time_between_samples = 1): """ Automatically sets ongoing sampling of TRex data for sample_time seconds, with sampling rate described by time_between_samples. Does not stop the TRex afterwards! @@ -623,7 +626,7 @@ class CTRexClient(object): time_between_samples : int determines the time between each sample of the server - default value : **5** + default value : **1** :return: the first result object (see :class:`CTRexResult` for further details) of the TRex run after given sample_time. @@ -637,12 +640,12 @@ class CTRexClient(object): """ # make sure TRex is running. raise exceptions here if any self.wait_until_kickoff_finish() - elapsed_time = 0 + end_time = time.time() + sample_time while self.is_running(): - if elapsed_time >= sample_time: + if time.time() < end_time: + time.sleep(time_between_samples) + else: return self.get_result_obj() - time.sleep(time_between_samples) - elapsed_time += time_between_samples raise UserWarning("TRex has stopped at %s seconds (before expected %s seconds)\nTry increasing test duration or decreasing sample_time" % (elapsed_time, sample_time)) def get_result_obj (self, copy_obj = True): @@ -1271,7 +1274,7 @@ class CTRexResult(object): .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy. | Use '[i]' to access the i'th indexed object of an array. - tree_path_to_key : regex + regex : regex apply a regex to filter results out from a multiple results set. Filter applies only on keys of dictionary type. @@ -1299,7 +1302,7 @@ class CTRexResult(object): .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy. | Use '[i]' to access the i'th indexed object of an array. - tree_path_to_key : regex + regex : regex apply a regex to filter results out from a multiple results set. Filter applies only on keys of dictionary type. @@ -1352,7 +1355,7 @@ class CTRexResult(object): if not len(self._history): return -1 - return len(self.__get_value_by_path(self._history[-1], 'trex-global.data', 'opackets-\d+')) + return len(self.get_last_value('trex-global.data', 'opackets-\d+')) def update_result_data (self, latest_dump): @@ -1383,14 +1386,21 @@ class CTRexResult(object): # check for up to 2% change between expected and actual if (self._current_tx_rate['m_tx_bps'] > 0.98 * self._expected_tx_rate['m_tx_expected_bps']): self._done_warmup = True + latest_dump['warmup_barrier'] = True # handle latency data if self.latency_checked: - latency_per_port = self.get_last_value("trex-latecny-v2.data", "port-") + # fix typos, by "pointer" + if 'trex-latecny-v2' in latest_dump and 'trex-latency-v2' not in latest_dump: + latest_dump['trex-latency-v2'] = latest_dump['trex-latecny-v2'] + if 'trex-latecny' in latest_dump and 'trex-latency' not in latest_dump: + latest_dump['trex-latency'] = latest_dump['trex-latecny'] + + latency_per_port = self.get_last_value("trex-latency-v2.data", "port-") self._max_latency = self.__get_filtered_max_latency(latency_per_port, self.filtered_latency_amount) - avg_latency = self.get_last_value("trex-latecny.data", "avg-") + avg_latency = self.get_last_value("trex-latency.data", "avg-") self._avg_latency = CTRexResult.__avg_all_and_rename_keys(avg_latency) - avg_win_latency_list = self.get_value_list("trex-latecny.data", "avg-") + avg_win_latency_list = self.get_value_list("trex-latency.data", "avg-") self._avg_window_latency = CTRexResult.__calc_latency_win_stats(avg_win_latency_list) tx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_tx_pkts") @@ -1427,12 +1437,12 @@ class CTRexResult(object): for i, p in re.findall(r'(\d+)|([\w|-]+)', tree_path): dct = dct[p or int(i)] if regex is not None and isinstance(dct, dict): - res = {} - for key,val in dct.items(): - match = re.match(regex, key) - if match: - res[key]=val - return res + res = {} + for key,val in dct.items(): + match = re.match(regex, key) + if match: + res[key]=val + return res else: return dct except (KeyError, TypeError): @@ -1499,6 +1509,42 @@ class CTRexResult(object): return result + # history iterator after warmup period + def _get_steady_state_history_iterator(self): + if not self.is_done_warmup(): + raise Exception('Warm-up period not finished') + for index, res in enumerate(self._history): + if 'warmup_barrier' in res: + for steady_state_index in range(index, max(index, len(self._history) - 1)): + yield self._history[steady_state_index] + return + for index in range(len(self._history) - 1): + yield self._history[index] + + + def get_avg_steady_state_value(self, tree_path_to_key): + ''' + Gets average value after warmup period. + For example: <result object>.get_avg_steady_state_value('trex-global.data.m_tx_bps') + Usually more accurate than latest history value. + + :parameters: + tree_path_to_key : str + defines a path to desired data. + + :return: + average value at steady state + + :raises: + Exception in case steady state period was not reached or tree_path_to_key was not found in result. + ''' + values_arr = [self.__get_value_by_path(res, tree_path_to_key) for res in self._get_steady_state_history_iterator()] + values_arr = list(filter(lambda x: x is not None, values_arr)) + if not values_arr: + raise Exception('All the keys are None, probably wrong tree_path_to_key: %s' % tree_path_to_key) + return sum(values_arr) / float(len(values_arr)) + + if __name__ == "__main__": c = CTRexClient('127.0.0.1') print('restarting daemon') 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 ab70d357..9e3f2600 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -459,7 +459,6 @@ class TRexConsole(TRexGeneralCmd): self.stateless_client.start_line(line) - def help_start(self): @@ -567,7 +566,8 @@ class TRexConsole(TRexGeneralCmd): parser = parsing_opts.gen_parser(self, "tui", self.do_tui.__doc__, - parsing_opts.XTERM) + parsing_opts.XTERM, + parsing_opts.LOCKED) opts = parser.parse_args(line.split()) if opts is None: @@ -590,7 +590,7 @@ class TRexConsole(TRexGeneralCmd): with self.stateless_client.logger.supress(): - self.tui.show() + self.tui.show(self.stateless_client, locked = opts.locked) def help_tui (self): @@ -872,7 +872,8 @@ def main(): # TUI if options.tui: - console.do_tui("-x" if options.xtui else "") + console.do_tui("-x" if options.xtui else "-l") + else: console.start() diff --git a/scripts/automation/trex_control_plane/stl/console/trex_tui.py b/scripts/automation/trex_control_plane/stl/console/trex_tui.py index d3be4435..32c6b1e0 100644 --- a/scripts/automation/trex_control_plane/stl/console/trex_tui.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py @@ -4,6 +4,7 @@ import os import time from collections import OrderedDict, deque import datetime +import readline if sys.version_info > (3,0): from io import StringIO @@ -15,6 +16,10 @@ from trex_stl_lib.utils import text_tables from trex_stl_lib import trex_stl_stats from trex_stl_lib.utils.filters import ToggleFilter +class TUIQuit(Exception): + pass + + # for STL exceptions from trex_stl_lib.api import * @@ -210,7 +215,7 @@ class TrexTUILatencyStats(TrexTUIPanel): super(TrexTUILatencyStats, self).__init__(mng, "lstats") self.key_actions = OrderedDict() self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True} - self.key_actions['t'] = {'action': self.action_toggle_histogram, 'legend': 'toggle histogram', 'show': True} + self.key_actions['h'] = {'action': self.action_toggle_histogram, 'legend': 'histogram toggle', 'show': True} self.is_histogram = False @@ -238,6 +243,23 @@ class TrexTUILatencyStats(TrexTUIPanel): self.stateless_client.latency_stats.clear_stats() return "" + +# utilization stats +class TrexTUIUtilizationStats(TrexTUIPanel): + def __init__ (self, mng): + super(TrexTUIUtilizationStats, self).__init__(mng, "ustats") + self.key_actions = {} + + def show (self): + stats = self.stateless_client._get_formatted_stats(port_id_list = None, stats_mask = trex_stl_stats.UT_COMPAT) + # print stats to screen + for stat_type, stat_data in stats.items(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) + + def get_key_actions (self): + return self.key_actions + + # log class TrexTUILog(): def __init__ (self): @@ -258,24 +280,42 @@ class TrexTUILog(): print(msg) +# a predicate to wrap function as a bool +class Predicate(object): + def __init__ (self, func): + self.func = func + + def __nonzero__ (self): + return True if self.func() else False + def __bool__ (self): + return True if self.func() else False + + # Panels manager (contains server panels) class TrexTUIPanelManager(): def __init__ (self, tui): self.tui = tui self.stateless_client = tui.stateless_client self.ports = self.stateless_client.get_all_ports() - + self.locked = False self.panels = {} self.panels['dashboard'] = TrexTUIDashBoard(self) self.panels['sstats'] = TrexTUIStreamsStats(self) self.panels['lstats'] = TrexTUILatencyStats(self) + self.panels['ustats'] = TrexTUIUtilizationStats(self) self.key_actions = OrderedDict() - self.key_actions['q'] = {'action': self.action_quit, 'legend': 'quit', 'show': True} + + # we allow console only when ports are acquired + self.key_actions['ESC'] = {'action': self.action_none, 'legend': 'console', 'show': Predicate(lambda : not self.locked)} + + self.key_actions['q'] = {'action': self.action_none, 'legend': 'quit', 'show': True} self.key_actions['d'] = {'action': self.action_show_dash, 'legend': 'dashboard', 'show': True} self.key_actions['s'] = {'action': self.action_show_sstats, 'legend': 'streams', 'show': True} self.key_actions['l'] = {'action': self.action_show_lstats, 'legend': 'latency', 'show': True} + self.key_actions['u'] = {'action': self.action_show_ustats, 'legend': 'util', 'show': True} + # start with dashboard self.main_panel = self.panels['dashboard'] @@ -290,7 +330,6 @@ class TrexTUIPanelManager(): self.show_log = False - def generate_legend (self): self.legend = "\n{:<12}".format("browse:") @@ -327,14 +366,18 @@ class TrexTUIPanelManager(): # on window switch or turn on / off of the TUI we call this - def init (self, show_log = False): + def init (self, show_log = False, locked = False): self.show_log = show_log + self.locked = locked self.generate_legend() - def show (self): + def show (self, show_legend): self.main_panel.show() self.print_connection_status() - self.print_legend() + + if show_legend: + self.generate_legend() + self.print_legend() if self.show_log: self.log.show() @@ -350,21 +393,22 @@ class TrexTUIPanelManager(): msg = self.main_panel.get_key_actions()[ch]['action']() else: - msg = "" + return False self.generate_legend() - - if msg == None: - return False - else: - if msg: - self.log.add_event(msg) - return True + return True + + #if msg == None: + # return False + #else: + # if msg: + # self.log.add_event(msg) + # return True # actions - def action_quit (self): + def action_none (self): return None def action_show_dash (self): @@ -392,6 +436,11 @@ class TrexTUIPanelManager(): self.init(self.show_log) return "" + def action_show_ustats(self): + self.main_panel = self.panels['ustats'] + self.init(self.show_log) + return "" + # shows a textual top style window class TrexTUI(): @@ -404,18 +453,7 @@ class TrexTUI(): self.stateless_client = stateless_client self.pm = TrexTUIPanelManager(self) - - - - def handle_key_input (self): - # try to read a single key - ch = os.read(sys.stdin.fileno(), 1).decode() - if ch != None and len(ch) > 0: - return (self.pm.handle_key(ch), True) - - else: - return (True, False) - + def clear_screen (self): #os.system('clear') @@ -423,17 +461,16 @@ class TrexTUI(): sys.stdout.write("\x1b[2J\x1b[H") + def show (self, client, show_log = False, locked = False): + with AsyncKeys(client, locked) as async_keys: + self.async_keys = async_keys + self.show_internal(show_log, locked) - def show (self, show_log = False): - # init termios - old_settings = termios.tcgetattr(sys.stdin) - new_settings = termios.tcgetattr(sys.stdin) - new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags - new_settings[6][termios.VMIN] = 0 # cc - new_settings[6][termios.VTIME] = 0 # cc - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings) - self.pm.init(show_log) + + def show_internal (self, show_log, locked): + + self.pm.init(show_log, locked) self.state = self.STATE_ACTIVE self.draw_policer = 0 @@ -441,11 +478,11 @@ class TrexTUI(): try: while True: # draw and handle user input - cont, force_draw = self.handle_key_input() - self.draw_screen(force_draw) - if not cont: - break - time.sleep(0.1) + status = self.async_keys.tick(self.pm) + + self.draw_screen(status) + if status == AsyncKeys.STATUS_NONE: + time.sleep(0.001) # regular state if self.state == self.STATE_ACTIVE: @@ -473,34 +510,402 @@ class TrexTUI(): self.state = self.STATE_LOST_CONT - finally: - # restore - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) + except TUIQuit: + print("\nExiting TUI...") print("") # draw once - def draw_screen (self, force_draw = False): + def draw_screen (self, status): - if (self.draw_policer >= 5) or (force_draw): + # only redraw the keys line + if status == AsyncKeys.STATUS_REDRAW_KEYS: + self.clear_screen() + sys.stdout.write(self.last_snap) + self.async_keys.draw() + sys.stdout.flush() + return + + if (self.draw_policer >= 500) or (status == AsyncKeys.STATUS_REDRAW_ALL): # capture stdout to a string old_stdout = sys.stdout sys.stdout = mystdout = StringIO() - self.pm.show() + self.pm.show(show_legend = self.async_keys.is_legend_mode()) + self.last_snap = mystdout.getvalue() + + self.async_keys.draw() sys.stdout = old_stdout self.clear_screen() - print(mystdout.getvalue()) - + sys.stdout.write(mystdout.getvalue()) + sys.stdout.flush() self.draw_policer = 0 else: self.draw_policer += 1 + def get_state (self): return self.state + + + + +# handles async IO +class AsyncKeys: + + MODE_LEGEND = 1 + MODE_CONSOLE = 2 + + STATUS_NONE = 0 + STATUS_REDRAW_KEYS = 1 + STATUS_REDRAW_ALL = 2 + + def __init__ (self, client, locked = False): + self.engine_console = AsyncKeysEngineConsole(self, client) + self.engine_legend = AsyncKeysEngineLegend(self) + self.locked = locked + + if locked: + self.engine = self.engine_legend + self.locked = True + else: + self.engine = self.engine_console + self.locked = False + + def __enter__ (self): + # init termios + self.old_settings = termios.tcgetattr(sys.stdin) + new_settings = termios.tcgetattr(sys.stdin) + new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags + new_settings[6][termios.VMIN] = 0 # cc + new_settings[6][termios.VTIME] = 0 # cc + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings) + return self + + def __exit__ (self, type, value, traceback): + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings) + + + def is_legend_mode (self): + return self.engine.get_type() == AsyncKeys.MODE_LEGEND + + def is_console_mode (self): + return self.engine.get_type == AsyncKeys.MODE_CONSOLE + + def switch (self): + if self.is_legend_mode(): + self.engine = self.engine_console + else: + self.engine = self.engine_legend + + + def tick (self, pm): + seq = '' + # drain all chars + while True: + ch = os.read(sys.stdin.fileno(), 1).decode() + if not ch: + break + seq += ch + + if not seq: + return self.STATUS_NONE + + # ESC for switch + if seq == '\x1b': + if not self.locked: + self.switch() + return self.STATUS_REDRAW_ALL + + # EOF (ctrl + D) + if seq == '\x04': + raise TUIQuit() + + # pass tick to engine + return self.engine.tick(seq, pm) + + + def draw (self): + self.engine.draw() + + + +# Legend engine +class AsyncKeysEngineLegend: + def __init__ (self, async): + self.async = async + + def get_type (self): + return self.async.MODE_LEGEND + + def tick (self, seq, pm): + + if seq == 'q': + raise TUIQuit() + + # ignore escapes + if len(seq) > 1: + return AsyncKeys.STATUS_NONE + + rc = pm.handle_key(seq) + return AsyncKeys.STATUS_REDRAW_ALL if rc else AsyncKeys.STATUS_NONE + + def draw (self): + pass + + +# console engine +class AsyncKeysEngineConsole: + def __init__ (self, async, client): + self.async = async + self.lines = deque(maxlen = 100) + + self.ac = {'start' : client.start_line, + 'stop' : client.stop_line, + 'pause' : client.pause_line, + 'resume': client.resume_line, + 'update': client.update_line, + 'quit' : self.action_quit, + 'q' : self.action_quit, + 'exit' : self.action_quit, + 'help' : self.action_help, + '?' : self.action_help} + + # fetch readline history and add relevants + for i in range(0, readline.get_current_history_length()): + cmd = readline.get_history_item(i) + if cmd and cmd.split()[0] in self.ac: + self.lines.appendleft(CmdLine(cmd)) + + # new line + self.lines.appendleft(CmdLine('')) + self.line_index = 0 + self.last_status = '' + + def action_quit (self, _): + raise TUIQuit() + + def action_help (self, _): + return ' '.join([format_text(cmd, 'bold') for cmd in self.ac.keys()]) + + def get_type (self): + return self.async.MODE_CONSOLE + + + def handle_escape_char (self, seq): + # up + if seq == '\x1b[A': + self.line_index = min(self.line_index + 1, len(self.lines) - 1) + + # down + elif seq == '\x1b[B': + self.line_index = max(self.line_index - 1, 0) + + # left + elif seq == '\x1b[D': + self.lines[self.line_index].go_left() + + # right + elif seq == '\x1b[C': + self.lines[self.line_index].go_right() + + # del + elif seq == '\x1b[3~': + self.lines[self.line_index].del_key() + + # home + elif seq == '\x1b[H': + self.lines[self.line_index].home_key() + + # end + elif seq == '\x1b[F': + self.lines[self.line_index].end_key() + return True + + # unknown key + else: + return AsyncKeys.STATUS_NONE + + return AsyncKeys.STATUS_REDRAW_KEYS + + + def tick (self, seq, _): + + # handle escape chars + if len(seq) > 1: + return self.handle_escape_char(seq) + + # handle each char + for ch in seq: + return self.handle_single_key(ch) + + + + def handle_single_key (self, ch): + + # newline + if ch == '\n': + self.handle_cmd() + + # backspace + elif ch == '\x7f': + self.lines[self.line_index].backspace() + + # TAB + elif ch == '\t': + cur = self.lines[self.line_index].get() + if not cur: + return + + matching_cmds = [x for x in self.ac if x.startswith(cur)] + + common = os.path.commonprefix([x for x in self.ac if x.startswith(cur)]) + if common: + if len(matching_cmds) == 1: + self.lines[self.line_index].set(common + ' ') + self.last_status = '' + else: + self.lines[self.line_index].set(common) + self.last_status = 'ambigious: '+ ' '.join([format_text(cmd, 'bold') for cmd in matching_cmds]) + + + # simple char + else: + self.lines[self.line_index] += ch + + return AsyncKeys.STATUS_REDRAW_KEYS + + + def split_cmd (self, cmd): + s = cmd.split(' ', 1) + op = s[0] + param = s[1] if len(s) == 2 else '' + return op, param + + + def handle_cmd (self): + cmd = self.lines[self.line_index].get().strip() + if not cmd: + return + + op, param = self.split_cmd(cmd) + + func = self.ac.get(op) + if func: + func_rc = func(param) + + # take out the empty line + empty_line = self.lines.popleft() + assert(empty_line.ro_line == '') + + if not self.lines or self.lines[0].ro_line != cmd: + self.lines.appendleft(CmdLine(cmd)) + + # back in + self.lines.appendleft(empty_line) + self.line_index = 0 + readline.add_history(cmd) + + # back to readonly + for line in self.lines: + line.invalidate() + + assert(self.lines[0].modified == False) + if not func: + self.last_status = "unknown command: '{0}'".format(format_text(cmd.split()[0], 'bold')) + else: + if isinstance(func_rc, str): + self.last_status = func_rc + else: + self.last_status = format_text("[OK]", 'green') if func_rc else format_text(str(func_rc).replace('\n', ''), 'red') + + + def draw (self): + sys.stdout.write("\nPress 'ESC' for navigation panel...\n") + sys.stdout.write("status: {0}\n".format(self.last_status)) + sys.stdout.write("\ntui>") + self.lines[self.line_index].draw() + + +# a readline alike command line - can be modified during edit +class CmdLine(object): + def __init__ (self, line): + self.ro_line = line + self.w_line = None + self.modified = False + self.cursor_index = len(line) + + def get (self): + if self.modified: + return self.w_line + else: + return self.ro_line + + def set (self, line, cursor_pos = None): + self.w_line = line + self.modified = True + + if cursor_pos is None: + self.cursor_index = len(self.w_line) + else: + self.cursor_index = cursor_pos + + + def __add__ (self, other): + assert(0) + + + def __str__ (self): + return self.get() + + + def __iadd__ (self, other): + + self.set(self.get()[:self.cursor_index] + other + self.get()[self.cursor_index:], + cursor_pos = self.cursor_index + len(other)) + + return self + + + def backspace (self): + if self.cursor_index == 0: + return + + self.set(self.get()[:self.cursor_index - 1] + self.get()[self.cursor_index:], + self.cursor_index - 1) + + + def del_key (self): + if self.cursor_index == len(self.get()): + return + + self.set(self.get()[:self.cursor_index] + self.get()[self.cursor_index + 1:], + self.cursor_index) + + def home_key (self): + self.cursor_index = 0 + + def end_key (self): + self.cursor_index = len(self.get()) + + def invalidate (self): + self.modified = False + self.w_line = None + self.cursor_index = len(self.ro_line) + + def go_left (self): + self.cursor_index = max(0, self.cursor_index - 1) + + def go_right (self): + self.cursor_index = min(len(self.get()), self.cursor_index + 1) + + def draw (self): + sys.stdout.write(self.get()) + sys.stdout.write('\b' * (len(self.get()) - self.cursor_index)) + diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_flow_latency_stats.py b/scripts/automation/trex_control_plane/stl/examples/stl_flow_latency_stats.py index ac0e212b..d8a99479 100644 --- a/scripts/automation/trex_control_plane/stl/examples/stl_flow_latency_stats.py +++ b/scripts/automation/trex_control_plane/stl/examples/stl_flow_latency_stats.py @@ -1,10 +1,12 @@ +# Example showing how to define stream for latency measurement, and how to parse the latency information + import stl_path from trex_stl_lib.api import * import time import pprint -def rx_example (tx_port, rx_port, burst_size, bw): +def rx_example (tx_port, rx_port, burst_size, pps): print("\nGoing to inject {0} packets on port {1} - checking RX stats on port {2}\n".format(burst_size, tx_port, rx_port)) @@ -19,7 +21,7 @@ def rx_example (tx_port, rx_port, burst_size, bw): packet = pkt, flow_stats = STLFlowLatencyStats(pg_id = 5), mode = STLTXSingleBurst(total_pkts = total_pkts, - percentage = bw)) + pps = pps)) # connect to server c.connect() @@ -32,7 +34,7 @@ def rx_example (tx_port, rx_port, burst_size, bw): print("\nInjecting {0} packets on port {1}\n".format(total_pkts, tx_port)) - rc = rx_iteration(c, tx_port, rx_port, total_pkts, pkt.get_pkt_len(), bw) + rc = rx_iteration(c, tx_port, rx_port, total_pkts, pkt.get_pkt_len()) if not rc: passed = False @@ -44,12 +46,12 @@ def rx_example (tx_port, rx_port, burst_size, bw): c.disconnect() if passed: - print("\nTest has passed :-)\n") + print("\nTest passed :-)\n") else: - print("\nTest has failed :-(\n") + print("\nTest failed :-(\n") # RX one iteration -def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): +def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len): c.clear_stats() @@ -58,7 +60,8 @@ def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): stats = c.get_stats() flow_stats = stats['flow_stats'].get(5) - lat_stats = stats['latency'].get(5) + global_lat_stats = stats['latency'] + lat_stats = global_lat_stats.get(5) if not flow_stats: print("no flow stats available") return False @@ -74,6 +77,8 @@ def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): dup = lat_stats['err_cntrs']['dup'] sth = lat_stats['err_cntrs']['seq_too_high'] stl = lat_stats['err_cntrs']['seq_too_low'] + old_flow = global_lat_stats['global']['old_flow'] + bad_hdr = global_lat_stats['global']['bad_hdr'] lat = lat_stats['latency'] jitter = lat['jitter'] avg = lat['average'] @@ -89,6 +94,10 @@ def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): return False print('Error counters: dropped:{0}, ooo:{1} dup:{2} seq too high:{3} seq too low:{4}'.format(drops, ooo, dup, sth, stl)) + if old_flow: + print ('Packets arriving too late after flow stopped: {0}'.format(old_flow)) + if bad_hdr: + print ('Latency packets with corrupted info: {0}'.format(bad_hdr)) print('Latency info:') print(" Maximum latency(usec): {0}".format(tot_max)) print(" Minimum latency(usec): {0}".format(tot_min)) @@ -131,5 +140,5 @@ def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): return True # run the tests -rx_example(tx_port = 1, rx_port = 0, burst_size = 500000, bw = 50) +rx_example(tx_port = 0, rx_port = 1, burst_size = 1000, pps = 1000) diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_flow_stats.py b/scripts/automation/trex_control_plane/stl/examples/stl_flow_stats.py index ed4902fa..3c630ece 100644 --- a/scripts/automation/trex_control_plane/stl/examples/stl_flow_stats.py +++ b/scripts/automation/trex_control_plane/stl/examples/stl_flow_stats.py @@ -1,3 +1,5 @@ +# Example showing how to define stream for getting per flow statistics, and how to parse the received statistics + import stl_path from trex_stl_lib.api import * @@ -27,18 +29,14 @@ def rx_example (tx_port, rx_port, burst_size, bw): # prepare our ports c.reset(ports = [tx_port, rx_port]) - # add both streams to ports + # add stream to port c.add_streams([s1], ports = [tx_port]) - print("\ninjecting {0} packets on port {1}\n".format(total_pkts, tx_port)) - - for i in range(0, 10): - print("\nStarting iteration: {0}:".format(i)) - rc = rx_iteration(c, tx_port, rx_port, total_pkts, pkt.get_pkt_len(), bw) - if not rc: - passed = False - break + print("\ngoing to inject {0} packets on port {1}\n".format(total_pkts, tx_port)) + rc = rx_iteration(c, tx_port, rx_port, total_pkts, s1.get_pkt_len()) + if not rc: + passed = False except STLError as e: passed = False @@ -48,19 +46,21 @@ def rx_example (tx_port, rx_port, burst_size, bw): c.disconnect() if passed: - print("\nTest has passed :-)\n") + print("\nTest passed :-)\n") else: - print("\nTest has failed :-(\n") + print("\nTest failed :-(\n") # RX one iteration -def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): - +def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len): + ret = True + c.clear_stats() c.start(ports = [tx_port]) c.wait_on_traffic(ports = [tx_port]) - flow_stats = c.get_stats()['flow_stats'].get(5) + global_flow_stats = c.get_stats()['flow_stats'] + flow_stats = global_flow_stats.get(5) if not flow_stats: print("no flow stats available") return False @@ -78,26 +78,33 @@ def rx_iteration (c, tx_port, rx_port, total_pkts, pkt_len, bw): if tx_pkts != total_pkts: print("TX pkts mismatch - got: {0}, expected: {1}".format(tx_pkts, total_pkts)) pprint.pprint(flow_stats) - return False + ret = False else: print("TX pkts match - {0}".format(tx_pkts)) if tx_bytes != (total_pkts * pkt_len): print("TX bytes mismatch - got: {0}, expected: {1}".format(tx_bytes, (total_pkts * pkt_len))) pprint.pprint(flow_stats) - return False + ret = False else: print("TX bytes match - {0}".format(tx_bytes)) if rx_pkts != total_pkts: print("RX pkts mismatch - got: {0}, expected: {1}".format(rx_pkts, total_pkts)) pprint.pprint(flow_stats) - return False + ret = False else: print("RX pkts match - {0}".format(rx_pkts)) - return True + + for field in ['rx_err', 'tx_err']: + for port in global_flow_stats['global'][field].keys(): + if global_flow_stats['global'][field][port] != 0: + print ("\n{0} on port {1}: {2} - You should consider increasing rx_delay_ms value in wait_on_traffic" + .format(field, port, global_flow_stats['global'][field][port])) + + return ret # run the tests -rx_example(tx_port = 1, rx_port = 2, burst_size = 500000, bw = 50) +rx_example(tx_port = 0, rx_port = 1, burst_size = 500, bw = 50) diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_imix.py b/scripts/automation/trex_control_plane/stl/examples/stl_imix.py index 46d86b2b..875186ba 100644 --- a/scripts/automation/trex_control_plane/stl/examples/stl_imix.py +++ b/scripts/automation/trex_control_plane/stl/examples/stl_imix.py @@ -5,6 +5,7 @@ import time import json from pprint import pprint import argparse +import sys # IMIX test # it maps the ports to sides @@ -97,6 +98,7 @@ def imix_test (server, mult): except STLError as e: passed = False print(e) + sys.exit(1) finally: c.disconnect() 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 c1833754..7c8a5fbf 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 @@ -511,8 +511,7 @@ class STLClient(object): self.connected = False # API classes - self.api_vers = [ {'type': 'core', 'major': 1, 'minor':2 } - ] + self.api_vers = [ {'type': 'core', 'major': 1, 'minor': 3 } ] self.api_h = {'core': None} # logger @@ -555,14 +554,17 @@ class STLClient(object): self.latency_stats = trex_stl_stats.CLatencyStats(self.ports) + self.util_stats = trex_stl_stats.CUtilStats(self) + self.stats_generator = trex_stl_stats.CTRexInfoGenerator(self.global_stats, self.ports, self.flow_stats, self.latency_stats, + self.util_stats, self.async_client.monitor) - + ############# private functions - used by the class itself ########### @@ -1284,7 +1286,7 @@ class STLClient(object): **total** and per port statistics contain dictionary with following format. - Most of the bytes counters (unless specified otherwise) are in L2 layer including the FCS. e.g. minimum packet size in 64 bytes + Most of the bytes counters (unless specified otherwise) are in L2 layer, including the Ethernet FCS. e.g. minimum packet size is 64 bytes =============================== =============== key Meaning @@ -1303,21 +1305,34 @@ class STLClient(object): .. _flow_stats: - **flow_stats** contains dictionaries per packet group id (pg id). Each one with the following structure. + **flow_stats** contains :ref:`global dictionary <flow_stats_global>`, and dictionaries per packet group id (pg id). See structures below. + + **per pg_id flow stat** dictionaries have following structure: ================= =============== key Meaning ================= =============== - rx_bps Receivd bytes per second rate - rx_bps_l1 Receivd bytes per second rate, including layer one + rx_bps Received bytes per second rate + rx_bps_l1 Received bytes per second rate, including layer one rx_bytes Total number of received bytes rx_pkts Total number of received packets rx_pps Received packets per second - tx_bps Transmitted bytes per second rate - tx_bps_l1 Transmitted bytes per second rate, including layer one + tx_bps Transmit bytes per second rate + tx_bps_l1 Transmit bytes per second rate, including layer one tx_bytes Total number of sent bytes tx_pkts Total number of sent packets - tx_pps Transmit packets per second + tx_pps Transmit packets per second rate + ================= =============== + + .. _flow_stats_global: + + **global flow stats** dictionary has the following structure: + + ================= =============== + key Meaning + ================= =============== + rx_err Number of flow statistics packets received that we could not associate to any pg_id. This can happen if latency on the used setup is large. See :ref:`wait_on_traffic <wait_on_traffic>` rx_delay_ms parameter for details. + tx_err Number of flow statistics packets transmitted that we could not associate to any pg_id. This is never expected. If you see this different than 0, please report. ================= =============== .. _global: @@ -1340,7 +1355,9 @@ class STLClient(object): .. _latency: - **latency** contains dictionary per packet group id (pg id). Each one with the following structure. + **latency** contains :ref:`global dictionary <lat_stats_global>`, and dictionaries per packet group id (pg id). Each one with the following structure. + + **per pg_id latency stat** dictionaries have following structure: =========================== =============== key Meaning @@ -1394,7 +1411,16 @@ class STLClient(object): total_min Minimum latency measured over the stream lifetime (in usec). ================= =============== + .. _lat_stats_global: + + **global latency stats** dictionary has the following structure: + ================= =============== + key Meaning + ================= =============== + old_flow Number of latency statistics packets received that we could not associate to any pg_id. This can happen if latency on the used setup is large. See :ref:`wait_on_traffic <wait_on_traffic>` rx_delay_ms parameter for details. + bad_hdr Number of latency packets received with bad latency data. This can happen becuase of garbage packets in the network, or if the DUT causes packet corruption. + ================= =============== :raises: None @@ -1664,6 +1690,23 @@ class STLClient(object): if not rc: raise STLError(rc) + @__api_check(True) + def get_util_stats(self): + """ + Get utilization stats: + History of TRex CPU utilization per thread (list of lists) + MBUFs memory consumption per CPU socket. + + :parameters: + None + + :raises: + + :exc:`STLError` + + """ + self.logger.pre_cmd('Getting Utilization stats') + return self.util_stats.get_stats() + @__api_check(True) def reset(self, ports = None): @@ -1878,8 +1921,6 @@ class STLClient(object): raise STLError(rc) - - @__api_check(True) def stop (self, ports = None, rx_delay_ms = 10): """ @@ -2274,6 +2315,8 @@ class STLClient(object): @__api_check(True) def wait_on_traffic (self, ports = None, timeout = 60, rx_delay_ms = 10): """ + .. _wait_on_traffic: + Block until traffic on specified port(s) has ended :parameters: @@ -2284,12 +2327,11 @@ class STLClient(object): timeout in seconds rx_delay_ms : int - time to wait until RX filters are removed - this value should reflect the time it takes - packets which were transmitted to arrive + Time to wait (in milliseconds) after last packet was sent, until RX filters used for + measuring flow statistics and latency are removed. + This value should reflect the time it takes packets which were transmitted to arrive to the destination. - after this time the RX filters will be removed - + After this time, RX filters will be removed, and packets arriving for per flow statistics feature and latency flows will be counted as errors. :raises: + :exc:`STLTimeoutError` - in case timeout has expired @@ -2385,13 +2427,14 @@ class STLClient(object): rc = f(*args) except STLError as e: client.logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold')) - return + return RC_ERR(e.brief()) # if got true - print time if rc: delta = time.time() - time1 client.logger.log(format_time(delta) + "\n") + return rc return wrap @@ -2544,14 +2587,14 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'start'") active_ports = list_intersect(self.get_active_ports(), opts.ports) if active_ports: if not opts.force: msg = "Port(s) {0} are active - please stop them or add '--force'\n".format(active_ports) self.logger.log(format_text(msg, 'bold')) - return + return RC_ERR(msg) else: self.stop(active_ports) @@ -2569,8 +2612,10 @@ class STLClient(object): else: # must be exact if len(opts.ports) != len(opts.tunables): - self.logger.log('tunables section count must be 1 or exactly as the number of ports: got {0}'.format(len(opts.tunables))) - return + msg = 'tunables section count must be 1 or exactly as the number of ports: got {0}'.format(len(opts.tunables)) + self.logger.log(msg) + return RC_ERR(msg) + tunables = opts.tunables @@ -2590,9 +2635,10 @@ class STLClient(object): self.add_streams(profile.get_streams(), ports = port) except STLError as e: - self.logger.log(format_text("\nError while loading profile '{0}'\n".format(opts.file[0]), 'bold')) + msg = format_text("\nError while loading profile '{0}'\n".format(opts.file[0]), 'bold') + self.logger.log(msg) self.logger.log(e.brief() + "\n") - return + return RC_ERR(msg) if opts.dry: @@ -2604,8 +2650,7 @@ class STLClient(object): opts.duration, opts.total) - # true means print time - return True + return RC_OK() @@ -2619,23 +2664,25 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'stop'") # find the relevant ports ports = list_intersect(opts.ports, self.get_active_ports()) if not ports: if not opts.ports: - self.logger.log('stop - no active ports') + msg = 'stop - no active ports' else: - self.logger.log('stop - no active traffic on ports {0}'.format(opts.ports)) - return + msg = 'stop - no active traffic on ports {0}'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) # call API self.stop(ports) # true means print time - return True + return RC_OK() @__console @@ -2651,22 +2698,24 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_active_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'update'") # find the relevant ports ports = list_intersect(opts.ports, self.get_active_ports()) if not ports: if not opts.ports: - self.logger.log('update - no active ports') + msg = 'update - no active ports' else: - self.logger.log('update - no active traffic on ports {0}'.format(opts.ports)) - return + msg = 'update - no active traffic on ports {0}'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) self.update(ports, opts.mult, opts.total, opts.force) # true means print time - return True + return RC_OK() @__console @@ -2679,26 +2728,29 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_transmitting_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'pause'") # check for already paused case if opts.ports and is_sub_list(opts.ports, self.get_paused_ports()): - self.logger.log('pause - all of port(s) {0} are already paused'.format(opts.ports)) - return + msg = 'pause - all of port(s) {0} are already paused'.format(opts.ports) + self.logger.log(msg) + return RC_ERR(msg) # find the relevant ports ports = list_intersect(opts.ports, self.get_transmitting_ports()) if not ports: if not opts.ports: - self.logger.log('pause - no transmitting ports') + msg = 'pause - no transmitting ports' else: - self.logger.log('pause - none of ports {0} are transmitting'.format(opts.ports)) - return + msg = 'pause - none of ports {0} are transmitting'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) self.pause(ports) # true means print time - return True + return RC_OK() @__console @@ -2711,22 +2763,24 @@ class STLClient(object): opts = parser.parse_args(line.split(), default_ports = self.get_paused_ports(), verify_acquired = True) if opts is None: - return + return RC_ERR("invalid arguments for 'resume'") # find the relevant ports ports = list_intersect(opts.ports, self.get_paused_ports()) if not ports: if not opts.ports: - self.logger.log('resume - no paused ports') + msg = 'resume - no paused ports' else: - self.logger.log('resume - none of ports {0} are paused'.format(opts.ports)) - return + msg = 'resume - none of ports {0} are paused'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) self.resume(ports) # true means print time - return True + return RC_OK() @__console @@ -2931,7 +2985,7 @@ class STLClient(object): if profile_type == 'python': self.logger.log('Type: {:^12}'.format('Python Module')) - self.logger.log('Tunables: {:^12}'.format(['{0} = {1}'.format(k ,v) for k, v in info['tunables'].items()])) + self.logger.log('Tunables: {:^12}'.format(str(['{0} = {1}'.format(k ,v) for k, v in info['tunables'].items()]))) elif profile_type == 'yaml': self.logger.log('Type: {:^12}'.format('YAML')) 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 3effa1f0..af4d6f69 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 @@ -11,7 +11,6 @@ import datetime import time import re import math -import copy import threading import pprint @@ -22,13 +21,16 @@ PORT_STATUS = 'ps' STREAMS_STATS = 's' LATENCY_STATS = 'ls' LATENCY_HISTOGRAM = 'lh' +CPU_STATS = 'c' +MBUF_STATS = 'm' -ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS, LATENCY_STATS, PORT_GRAPH, LATENCY_HISTOGRAM] +ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS, LATENCY_STATS, PORT_GRAPH, LATENCY_HISTOGRAM, CPU_STATS, MBUF_STATS] COMPACT = [GLOBAL_STATS, PORT_STATS] GRAPH_PORT_COMPACT = [GLOBAL_STATS, PORT_GRAPH] SS_COMPAT = [GLOBAL_STATS, STREAMS_STATS] # stream stats LS_COMPAT = [GLOBAL_STATS, LATENCY_STATS] # latency stats LH_COMPAT = [GLOBAL_STATS, LATENCY_HISTOGRAM] # latency histogram +UT_COMPAT = [GLOBAL_STATS, CPU_STATS, MBUF_STATS] # utilization ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) @@ -104,6 +106,13 @@ def calculate_diff_raw (samples): return total +get_number_of_bytes_cache = {} +# get number of bytes: '64b'->64, '9kb'->9000 etc. +def get_number_of_bytes(val): + if val not in get_number_of_bytes_cache: + get_number_of_bytes_cache[val] = int(val[:-1].replace('k', '000')) + return get_number_of_bytes_cache[val] + # a simple object to keep a watch over a field class WatchedField(object): @@ -138,11 +147,12 @@ class CTRexInfoGenerator(object): STLClient and the ports. """ - def __init__(self, global_stats_ref, ports_dict_ref, rx_stats_ref, latency_stats_ref, async_monitor): + def __init__(self, global_stats_ref, ports_dict_ref, rx_stats_ref, latency_stats_ref, util_stats_ref, async_monitor): self._global_stats = global_stats_ref self._ports_dict = ports_dict_ref self._rx_stats_ref = rx_stats_ref self._latency_stats_ref = latency_stats_ref + self._util_stats_ref = util_stats_ref self._async_monitor = async_monitor def generate_single_statistic(self, port_id_list, statistic_type): @@ -167,6 +177,12 @@ class CTRexInfoGenerator(object): elif statistic_type == LATENCY_HISTOGRAM: return self._generate_latency_histogram() + elif statistic_type == CPU_STATS: + return self._generate_cpu_util_stats() + + elif statistic_type == MBUF_STATS: + return self._generate_mbuf_util_stats() + else: # ignore by returning empty object return {} @@ -404,6 +420,73 @@ class CTRexInfoGenerator(object): stats_table.header(header) return {"latency_histogram": ExportableStats(None, stats_table)} + def _generate_cpu_util_stats(self): + util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True) + stats_table = text_tables.TRexTextTable() + if util_stats: + if 'cpu' not in util_stats: + raise Exception("Excepting 'cpu' section in stats %s" % util_stats) + cpu_stats = util_stats['cpu'] + hist_len = len(cpu_stats[0]) + avg_len = min(5, hist_len) + show_len = min(15, hist_len) + stats_table.header(['Thread', 'Avg', 'Latest'] + list(range(-1, 0 - show_len, -1))) + stats_table.set_cols_align(['l'] + ['r'] * (show_len + 1)) + stats_table.set_cols_width([8, 3, 6] + [3] * (show_len - 1)) + stats_table.set_cols_dtype(['t'] * (show_len + 2)) + for i in range(min(14, len(cpu_stats))): + avg = int(round(sum(cpu_stats[i][:avg_len]) / avg_len)) + stats_table.add_row([i, avg] + cpu_stats[i][:show_len]) + else: + stats_table.add_row(['No Data.']) + return {'cpu_util(%)': ExportableStats(None, stats_table)} + + def _generate_mbuf_util_stats(self): + util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True) + stats_table = text_tables.TRexTextTable() + if util_stats: + if 'mbuf_stats' not in util_stats: + raise Exception("Excepting 'mbuf_stats' section in stats %s" % util_stats) + mbuf_stats = util_stats['mbuf_stats'] + for mbufs_per_socket in mbuf_stats.values(): + first_socket_mbufs = mbufs_per_socket + break + if not self._util_stats_ref.mbuf_types_list: + mbuf_keys = list(first_socket_mbufs.keys()) + mbuf_keys.sort(key = get_number_of_bytes) + self._util_stats_ref.mbuf_types_list = mbuf_keys + types_len = len(self._util_stats_ref.mbuf_types_list) + stats_table.set_cols_align(['l'] + ['r'] * (types_len + 1)) + stats_table.set_cols_width([10] + [7] * (types_len + 1)) + stats_table.set_cols_dtype(['t'] * (types_len + 2)) + stats_table.header([''] + self._util_stats_ref.mbuf_types_list + ['RAM(MB)']) + total_list = [] + sum_totals = 0 + for mbuf_type in self._util_stats_ref.mbuf_types_list: + sum_totals += first_socket_mbufs[mbuf_type][1] * get_number_of_bytes(mbuf_type) + 64 + total_list.append(first_socket_mbufs[mbuf_type][1]) + sum_totals *= len(list(mbuf_stats.values())) + total_list.append(int(sum_totals/1e6)) + stats_table.add_row(['Total:'] + total_list) + stats_table.add_row(['Used:'] + [''] * (types_len + 1)) + for socket_name in sorted(list(mbuf_stats.keys())): + mbufs = mbuf_stats[socket_name] + socket_show_name = socket_name.replace('cpu-', '').replace('-', ' ').capitalize() + ':' + sum_used = 0 + used_list = [] + percentage_list = [] + for mbuf_type in self._util_stats_ref.mbuf_types_list: + used = mbufs[mbuf_type][1] - mbufs[mbuf_type][0] + sum_used += used * get_number_of_bytes(mbuf_type) + 64 + used_list.append(used) + percentage_list.append('%s%%' % int(100 * used / mbufs[mbuf_type][1])) + used_list.append(int(sum_used/1e6)) + stats_table.add_row([socket_show_name] + used_list) + stats_table.add_row(['Percent:'] + percentage_list + ['']) + else: + stats_table.add_row(['No Data.']) + return {'mbuf_util': ExportableStats(None, stats_table)} + @staticmethod def _get_rational_block_char(value, range_start, interval): # in Konsole, utf-8 is sometimes printed with artifacts, return ascii for now @@ -1010,6 +1093,13 @@ class CLatencyStats(CTRexStats): snapshot = {} output = {} + output['global'] = {} + for field in ['bad_hdr', 'old_flow']: + if 'global' in snapshot and field in snapshot['global']: + output['global'][field] = snapshot['global'][field] + else: + output['global'][field] = 0 + # we care only about the current active keys pg_ids = list(filter(is_intable, snapshot.keys())) @@ -1107,6 +1197,14 @@ class CRxStats(CTRexStats): # copy timestamp field output['ts'] = current['ts'] + # global (not per pg_id) error counters + output['global'] = {} + for field in ['rx_err', 'tx_err']: + output['global'][field] = {} + if 'global' in current and field in current['global']: + for port in current['global'][field]: + output['global'][field][int(port)] = current['global'][field][port] + # we care only about the current active keys pg_ids = list(filter(is_intable, current.keys())) @@ -1254,6 +1352,9 @@ class CRxStats(CTRexStats): for pg_id, value in self.latest_stats.items(): # skip non ints if not is_intable(pg_id): + # 'global' stats are in the same level of the pg_ids. We do want them to go to the user + if pg_id == 'global': + stats[pg_id] = value continue # bare counters stats[int(pg_id)] = {} @@ -1276,7 +1377,27 @@ class CRxStats(CTRexStats): return stats - +class CUtilStats(CTRexStats): + + def __init__(self, client): + super(CUtilStats, self).__init__() + self.client = client + self.history = deque(maxlen = 1) + self.mbuf_types_list = None + self.last_update_ts = 0 + + def get_stats(self, use_1sec_cache = False): + time_now = time.time() + if self.last_update_ts + 1 < time_now or not self.history or not use_1sec_cache: + if self.client.is_connected(): + rc = self.client._transmit('get_utilization') + if not rc: + raise Exception(rc) + self.last_update_ts = time_now + self.history.append(rc.data()) + else: + self.history.append({}) + return self.history[-1] if __name__ == "__main__": pass 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 bb28d3af..1b417d65 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 @@ -33,12 +33,15 @@ NO_PROMISCUOUS = 20 PROMISCUOUS_SWITCH = 21 TUNABLES = 22 REMOTE_FILE = 23 +LOCKED = 24 GLOBAL_STATS = 50 PORT_STATS = 51 PORT_STATUS = 52 STREAMS_STATS = 53 STATS_MASK = 54 +CPU_STATS = 55 +MBUF_STATS = 56 STREAMS_MASK = 60 # ALL_STREAMS = 61 @@ -319,6 +322,11 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'default': False, 'help': "Starts TUI in xterm window"}), + LOCKED: ArgumentPack(['-l', '--locked'], + {'action': 'store_true', + 'dest': 'locked', + 'default': False, + 'help': "Locks TUI on legend mode"}), FULL_OUTPUT: ArgumentPack(['--full'], {'action': 'store_true', @@ -340,6 +348,14 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'action': 'store_true', 'help': "Fetch only streams stats"}), + CPU_STATS: ArgumentPack(['-c'], + {'action': 'store_true', + 'help': "Fetch only CPU utilization stats"}), + + MBUF_STATS: ArgumentPack(['-m'], + {'action': 'store_true', + 'help': "Fetch only MBUF utilization stats"}), + STREAMS_MASK: ArgumentPack(['--streams'], {"nargs": '+', 'dest':'streams', @@ -365,7 +381,9 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS, PORT_STATS, PORT_STATUS, - STREAMS_STATS], + STREAMS_STATS, + CPU_STATS, + MBUF_STATS], {}) } diff --git a/scripts/cfg/client_cfg_template.yaml b/scripts/cfg/client_cfg_template.yaml new file mode 100644 index 00000000..8257b981 --- /dev/null +++ b/scripts/cfg/client_cfg_template.yaml @@ -0,0 +1,56 @@ +# +# Client configuration example file +# The file must contain the following fields +# +# 'vlan' - is the entire configuration under VLAN +# if so, each client group must include vlan +# configuration +# +# 'groups' - each client group must contain a range of IP +# and initiator and responder maps +# 'count' represents the number of MAC devices +# on the group. +# +# initiator and responder can contain 'vlan', 'src_mac', 'dst_mac' +# + +vlan: true + +groups: + +- ip_start : 5.0.0.1 + ip_end : 5.0.0.4 + initiator : + vlan : 100 + dst_mac : "00:00:00:01:00:00" + responder : + vlan : 200 + dst_mac : "00:00:00:01:00:00" + + count : 2 + +- ip_start : 5.0.0.5 + ip_end : 5.0.0.12 + initiator : + vlan : 101 + dst_mac : "01:00:00:00:01:01" + + responder: + vlan : 201 + dst_mac : "01:00:00:00:02:01" + + count : 4 + +- ip_start : 5.0.0.13 + ip_end : 5.0.0.30 + + initiator : + vlan : 103 + dst_mac : "02:00:00:00:01:01" + responder : + + vlan : 203 + dst_mac : "02:00:00:00:02:01" + + count : 8 + diff --git a/scripts/exp/no_tcp_syn_ack.pcap b/scripts/exp/no_tcp_syn_ack.pcap Binary files differnew file mode 100644 index 00000000..0cdcf91b --- /dev/null +++ b/scripts/exp/no_tcp_syn_ack.pcap diff --git a/scripts/exp/tcp_2_pkts.pcap b/scripts/exp/tcp_2_pkts.pcap Binary files differnew file mode 100644 index 00000000..37c4f46b --- /dev/null +++ b/scripts/exp/tcp_2_pkts.pcap diff --git a/scripts/exp/tcp_low_ipg.pcap b/scripts/exp/tcp_low_ipg.pcap Binary files differnew file mode 100644 index 00000000..78fb987e --- /dev/null +++ b/scripts/exp/tcp_low_ipg.pcap diff --git a/scripts/ko/src/readme.txt b/scripts/ko/src/readme.txt index a8d028c9..0e9cddad 100755 --- a/scripts/ko/src/readme.txt +++ b/scripts/ko/src/readme.txt @@ -5,7 +5,7 @@ Fedora $sudo yum install kernel-headers or -Ubunto +Ubuntu $sudo apt-get install linux-headers-$(uname -r) 2. from this dir do this diff --git a/scripts/master_daemon.py b/scripts/master_daemon.py index aa49f207..3a9d7ffe 100755 --- a/scripts/master_daemon.py +++ b/scripts/master_daemon.py @@ -181,7 +181,7 @@ parser.add_argument('--type', '--daemon-type', '--daemon_type', choices = daemon action = 'store', help = 'Specify daemon type to start/stop etc.\nDefault is master_daemon.') args = parser.parse_args() -args.trex_dir = os.path.normpath(args.trex_dir) +args.trex_dir = os.path.abspath(args.trex_dir) args.daemon_type = args.daemon_type or 'master_daemon' stl_rpc_proxy_dir = os.path.join(args.trex_dir, 'automation', 'trex_control_plane', 'stl', 'examples') @@ -221,9 +221,13 @@ if args.action != 'show': raise Exception('%s does not have function %s' % (daemon.name, args.action)) try: func() - except Exception as e: - print(termstyle.red(e)) - sys.exit(1) + except: + try: # give it another try + sleep(1) + func() + except Exception as e: + print(termstyle.red(e)) + sys.exit(1) passive = {'start': 'started', 'restart': 'restarted', 'stop': 'stopped', 'show': 'running'} diff --git a/scripts/stl/udp_1pkt_src_ip_split.py b/scripts/stl/udp_1pkt_src_ip_split.py index 48e02433..99e7496c 100644 --- a/scripts/stl/udp_1pkt_src_ip_split.py +++ b/scripts/stl/udp_1pkt_src_ip_split.py @@ -8,11 +8,15 @@ class STLS1(object): def __init__ (self): self.fsize =64; - def create_stream (self): + def create_stream (self, direction, cache_size): # Create base packet and pad it to size size = self.fsize - 4; # HW will add 4 bytes ethernet FCS + src_ip = '16.0.0.1' + dst_ip = '48.0.0.1' + if direction: + src_ip, dst_ip = dst_ip, src_ip - base_pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) + base_pkt = Ether()/IP(src=src_ip,dst=dst_ip)/UDP(dport=12,sport=1025) pad = max(0, size - len(base_pkt)) * 'x' @@ -22,7 +26,7 @@ class STLS1(object): STLVmFixIpv4(offset = "IP") # fix checksum ] ,split_by_field = "ip_src" # split to cores base on the tuple generator - ,cache_size =255 # the cache size + ,cache_size = cache_size # the cache size ); pkt = STLPktBuilder(pkt = base_pkt/pad, @@ -33,9 +37,9 @@ class STLS1(object): return stream - def get_streams (self, direction = 0, **kwargs): + def get_streams (self, direction = 0, cache_size = 255, **kwargs): # create 1 stream - return [ self.create_stream() ] + return [ self.create_stream(direction, cache_size) ] # dynamic load - used for trex console or simulator diff --git a/scripts/trex-cfg b/scripts/trex-cfg index 7a533b1f..44ef678c 100755 --- a/scripts/trex-cfg +++ b/scripts/trex-cfg @@ -11,7 +11,7 @@ fi MACHINE_TYPE=`uname -m` if [ ${MACHINE_TYPE} != 'x86_64' ]; then - echo "ERROR, only 64bit OS are supported, please reinstall x86 64 bit OS" + echo "ERROR, only 64bit operating systems are supported, please reinstall x86 64 bit OS" exit 1 fi @@ -20,7 +20,7 @@ fi if [ -d /mnt/huge ]; then echo >> /dev/null else - echo "Create huge node" + echo "Creating huge node" mkdir -p /mnt/huge fi @@ -38,19 +38,27 @@ for file in /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepag done if ! lsmod | grep -q igb_uio ; then - echo "Load the drivers for the first time " + echo "Loading kernel drivers for the first time" modprobe uio + if [ $? -ne 0 ]; then + echo "Failed inserting uio module, please check if it is installed" + exit 1 + fi km=ko/$SYS/igb_uio.ko if [ -e $km ] ; then insmod $km + if [ $? -ne 0 ]; then + echo "Failed inserting igb_uio module" + exit 1 + fi else - echo "ERROR kernel module is not supported for this OS" - echo "Please run the following commands: " + echo "ERROR: We don't have precompiled igb_uio.ko module for your kernel version" + echo "You can try compiling yourself, using the following commands:" echo "\$cd ko/src " echo "\$make " echo "\$make install " echo "\$cd - " - echo "and try to run it again " + echo "Then try to run Trex again" exit 1 fi fi @@ -60,5 +68,3 @@ PARENT_ARGS="$0 $@" if ! ./dpdk_setup_ports.py --parent "$PARENT_ARGS"; then exit 1 fi - - diff --git a/src/bp_gtest.cpp b/src/bp_gtest.cpp index 79ea2458..a1f80407 100755 --- a/src/bp_gtest.cpp +++ b/src/bp_gtest.cpp @@ -32,6 +32,7 @@ limitations under the License. #include <common/cgen_map.h> #include "platform_cfg.h" #include "latency.h" +#include "nat_check_flow_table.h" int test_policer(){ CPolicer policer; @@ -2400,7 +2401,20 @@ TEST_F(rx_check_system, rx_json) { printf(" %s \n",json.c_str()); } +class nat_check_flow_table : public testing::Test { + protected: + virtual void SetUp() { + } + + virtual void TearDown() { + } +public: + CNatCheckFlowTable m_ft; +}; +TEST_F(nat_check_flow_table, test1) { + m_ft.test(); +}; ////////////////////////////////////////////////////////////// @@ -2416,7 +2430,7 @@ public: assert(ipv4->getTimeToLive()==255); /* ip option packet */ printf(" rx got ip option packet ! \n"); - mg->handle_packet_ipv4(option,ipv4); + mg->handle_packet_ipv4(option, ipv4, true); delay(10); // delay for queue flush mg->handle_aging(); // flush the RxRing } @@ -2481,8 +2495,10 @@ protected: m_flow_info.Delete(); } public: - CCapFileFlowInfo m_flow_info; + void load_cap_file_errors_helper(std::string cap_file, enum CCapFileFlowInfo::load_cap_file_err expect); +public: + CCapFileFlowInfo m_flow_info; }; TEST_F(file_flow_info, f1) { @@ -2612,30 +2628,58 @@ TEST_F(file_flow_info, http_add_ipv6_option) { po->preview.set_ipv6_mode_enable(false); } +void file_flow_info::load_cap_file_errors_helper(std::string cap_file, enum CCapFileFlowInfo::load_cap_file_err expect) { + enum CCapFileFlowInfo::load_cap_file_err err; + + err = m_flow_info.load_cap_file(cap_file, 1, 0); + if (err == 0) err = m_flow_info.is_valid_template_load_time(); + if (err != expect) { + printf("Error in testing file %s. Expected error to be %d, but it is %d\n", cap_file.c_str(), expect, err); + } + assert (err == expect); +} + // Test error conditions when loading cap file TEST_F(file_flow_info, load_cap_file_errors) { - enum CCapFileFlowInfo::load_cap_file_err err; - CParserOption * po =&CGlobalInfo::m_options; - po->m_learn_mode = CParserOption::LEARN_MODE_TCP_ACK; + CParserOption *po = &CGlobalInfo::m_options; - // file does not exist - err = m_flow_info.load_cap_file("/tmp/not_exist",1,0); - assert (err == CCapFileFlowInfo::kFileNotExist); + po->m_learn_mode = CParserOption::LEARN_MODE_DISABLED; + load_cap_file_errors_helper("/tmp/not_exist", CCapFileFlowInfo::kFileNotExist); // file format not supported - err = m_flow_info.load_cap_file("cap2/dns.yaml",1,0); - assert (err == CCapFileFlowInfo::kFileNotExist); - // udp in tcp learn mode - err = m_flow_info.load_cap_file("./cap2/dns.pcap",1,0); - assert (err == CCapFileFlowInfo::kNoTCPFromServer); - // First TCP packet without syn - err = m_flow_info.load_cap_file("./exp/tcp_no_syn.pcap",1,0); - assert (err == CCapFileFlowInfo::kNoSyn); - // TCP flags offset is too big - err = m_flow_info.load_cap_file("./exp/many_ip_options.pcap",1,0); - assert (err == CCapFileFlowInfo::kTCPOffsetTooBig); + load_cap_file_errors_helper("cap2/dns.yaml", CCapFileFlowInfo::kFileNotExist); + load_cap_file_errors_helper("cap2/dns.pcap", CCapFileFlowInfo::kOK); + load_cap_file_errors_helper("./exp/tcp_no_syn.pcap", CCapFileFlowInfo::kOK); + load_cap_file_errors_helper("./exp/many_ip_options.pcap", CCapFileFlowInfo::kOK); // Non IP packet - err = m_flow_info.load_cap_file("./exp/bad_not_ip.pcap",1,0); - assert (err == CCapFileFlowInfo::kPktProcessFail); + load_cap_file_errors_helper("./exp/bad_not_ip.pcap", CCapFileFlowInfo::kPktProcessFail); + load_cap_file_errors_helper("./exp/tcp_2_pkts.pcap", CCapFileFlowInfo::kOK); + // more than 1 flow in cap file + load_cap_file_errors_helper("./exp/syn_attack.pcap", CCapFileFlowInfo::kCapFileErr); + + po->m_learn_mode = CParserOption::LEARN_MODE_IP_OPTION; + load_cap_file_errors_helper("cap2/dns.pcap", CCapFileFlowInfo::kOK); + load_cap_file_errors_helper("./exp/tcp_no_syn.pcap", CCapFileFlowInfo::kOK); + load_cap_file_errors_helper("./exp/many_ip_options.pcap", CCapFileFlowInfo::kIPOptionNotAllowed); + load_cap_file_errors_helper("./exp/tcp_2_pkts.pcap", CCapFileFlowInfo::kOK); + + po->m_learn_mode = CParserOption::LEARN_MODE_TCP_ACK_NO_SERVER_SEQ_RAND; + // udp in tcp learn mode + load_cap_file_errors_helper("cap2/dns.pcap", CCapFileFlowInfo::kNoTCPFromServer); + // no SYN in first packet + load_cap_file_errors_helper("./exp/tcp_no_syn.pcap", CCapFileFlowInfo::kNoSyn); + // TCP flags offset is too big. We don't allow IP option, so can comment this. + // open this if we do allow IP options in the future + // load_cap_file_errors_helper("./exp/many_ip_options.pcap", CCapFileFlowInfo::kTCPOffsetTooBig); + load_cap_file_errors_helper("./exp/tcp_2_pkts.pcap", CCapFileFlowInfo::kOK); + load_cap_file_errors_helper("./exp/no_tcp_syn_ack.pcap", CCapFileFlowInfo::kOK); + + po->m_learn_mode = CParserOption::LEARN_MODE_TCP_ACK; + // too short. only two packets + load_cap_file_errors_helper("./exp/tcp_2_pkts.pcap", CCapFileFlowInfo::kTCPLearnModeBadFlow); + // no SYN+ACK + load_cap_file_errors_helper("./exp/no_tcp_syn_ack.pcap", CCapFileFlowInfo::kNoTCPSynAck); + // IPG between TCP handshake packets too low + load_cap_file_errors_helper("./exp/tcp_low_ipg.pcap", CCapFileFlowInfo::kTCPIpgTooLow); } ////////////////////////////////////////////////////////////// diff --git a/src/bp_sim.cpp b/src/bp_sim.cpp index db90107d..b229d9bf 100755 --- a/src/bp_sim.cpp +++ b/src/bp_sim.cpp @@ -498,40 +498,23 @@ void CRteMemPool::dump_in_case_of_error(FILE *fd){ dump(fd); } -std::string CRteMemPool::add_to_json( - std::string name, - rte_mempool_t * pool, - bool last){ +void CRteMemPool::add_to_json(Json::Value &json, std::string name, rte_mempool_t * pool){ uint32_t p_free = rte_mempool_count(pool); uint32_t p_size = pool->size; - char buff[200]; - sprintf(buff,"\"%s\":[%llu,%llu]",name.c_str(),(unsigned long long)p_free,(unsigned long long)p_size); - std::string json = std::string(buff) + (last?std::string(""):std::string(",")); - return (json); + json[name].append((unsigned long long)p_free); + json[name].append((unsigned long long)p_size); } -std::string CRteMemPool::dump_as_json(uint8_t id,bool last){ - - char buff[200]; - sprintf(buff,"\"socket-%d\":{", (int)id); - - std::string json=std::string(buff); - - json+=add_to_json("64b",m_small_mbuf_pool); - json+=add_to_json("128b",m_mbuf_pool_128); - json+=add_to_json("256b",m_mbuf_pool_256); - json+=add_to_json("512b",m_mbuf_pool_512); - json+=add_to_json("1024b",m_mbuf_pool_1024); - json+=add_to_json("2048b",m_mbuf_pool_2048); - json+=add_to_json("4096b",m_mbuf_pool_4096); - json+=add_to_json("9kb",m_mbuf_pool_9k,true); - - json+="}" ; - if (last==false) { - json+="," ; - } - return (json); +void CRteMemPool::dump_as_json(Json::Value &json){ + add_to_json(json, "64b", m_small_mbuf_pool); + add_to_json(json, "128b", m_mbuf_pool_128); + add_to_json(json, "256b", m_mbuf_pool_256); + add_to_json(json, "512b", m_mbuf_pool_512); + add_to_json(json, "1024b", m_mbuf_pool_1024); + add_to_json(json, "2048b", m_mbuf_pool_2048); + add_to_json(json, "4096b", m_mbuf_pool_4096); + add_to_json(json, "9kb", m_mbuf_pool_9k); } @@ -548,30 +531,24 @@ void CRteMemPool::dump(FILE *fd){ DUMP_MBUF("mbuf_9k",m_mbuf_pool_9k); } -//////////////////////////////////////// - -std::string CGlobalInfo::dump_pool_as_json(void){ +//////////////////////////////////////// - std::string json="\"mbuf_stats\":{"; +void CGlobalInfo::dump_pool_as_json(Json::Value &json){ CPlatformSocketInfo * lpSocket =&m_socket; - int last_socket=-1; - /* calc the last socket */ - int i; - for (i=0; i<(int)MAX_SOCKETS_SUPPORTED; i++) { + for (int i=0; i<(int)MAX_SOCKETS_SUPPORTED; i++) { if (lpSocket->is_sockets_enable((socket_id_t)i)) { - last_socket=i; + std::string socket_id = "cpu-socket-" + std::to_string(i); + m_mem_pool[i].dump_as_json(json["mbuf_stats"][socket_id]); } } +} - for (i=0; i<(int)MAX_SOCKETS_SUPPORTED; i++) { - if (lpSocket->is_sockets_enable((socket_id_t)i)) { - json+=m_mem_pool[i].dump_as_json(i,last_socket==i?true:false); - } - } - json+="},"; - return json; +std::string CGlobalInfo::dump_pool_as_json_str(void){ + Json::Value json; + dump_pool_as_json(json); + return (json.toStyledString()); } void CGlobalInfo::free_pools(){ @@ -771,9 +748,8 @@ void CPreviewMode::Dump(FILE *fd){ fprintf(fd," 1g mode : %d\n", (int)get_1g_mode() ); fprintf(fd," zmq_publish : %d\n", (int)get_zmq_publish_enable() ); fprintf(fd," vlan_enable : %d\n", (int)get_vlan_mode_enable() ); + fprintf(fd," client_cfg : %d\n", (int)get_is_client_cfg_enable() ); fprintf(fd," mbuf_cache_disable : %d\n", (int)isMbufCacheDisabled() ); - fprintf(fd," mac_ip_features : %d\n", (int)get_mac_ip_features_enable()?1:0 ); - fprintf(fd," mac_ip_map : %d\n", (int)get_mac_ip_mapping_enable()?1:0 ); fprintf(fd," vm mode : %d\n", (int)get_vm_one_queue_enable()?1:0 ); } @@ -785,8 +761,10 @@ void CFlowGenStats::clear(){ m_total_close_flows =0; m_nat_lookup_no_flow_id=0; m_nat_lookup_remove_flow_id=0; + m_nat_lookup_wait_ack_state = 0; m_nat_lookup_add_flow_id=0; m_nat_flow_timeout=0; + m_nat_flow_timeout_wait_ack = 0; m_nat_flow_learn_error=0; } @@ -815,9 +793,12 @@ void CFlowGenStats::dump(FILE *fd){ DP(m_nat_lookup_no_flow_id); DP(m_nat_lookup_remove_flow_id); + DP(m_nat_lookup_wait_ack_state); DP(m_nat_lookup_add_flow_id); DP(m_nat_flow_timeout); + DP(m_nat_flow_timeout_wait_ack); DP_name("active_nat",(m_nat_lookup_add_flow_id-m_nat_lookup_remove_flow_id)); + DP_name("active_nat_wait_syn", (m_nat_lookup_add_flow_id - m_nat_lookup_wait_ack_state)); DP(m_nat_flow_learn_error); } @@ -1962,42 +1943,115 @@ typedef CTmpFlowInfo * flow_tmp_t; typedef std::map<uint16_t, flow_tmp_t> flow_tmp_map_t; typedef flow_tmp_map_t::iterator flow_tmp_map_iter_t; - - -bool CCapFileFlowInfo::is_valid_template_load_time(std::string & err){ - err=""; +enum CCapFileFlowInfo::load_cap_file_err CCapFileFlowInfo::is_valid_template_load_time(){ int i; for (i=0; i<Size(); i++) { CFlowPktInfo * lp= GetPacket((uint32_t)i); CPacketIndication * lpd=&lp->m_pkt_indication; if ( lpd->getEtherOffset() !=0 ){ - err=" supported template Ether offset start is 0 \n"; - return (false); + fprintf(stderr, "Error: Bad CAP file. Ether offset start is not 0 in packet %d \n", i+1); + return kPktNotSupp; } - if ( lpd->getIpOffset() !=14 ){ - err=" supported template ip offset is 14 \n"; - return (false); - } - if ( lpd->is_ipv6() ){ - if ( lpd->getTcpOffset() != (14+40) ){ - err=" supported template tcp/udp offset is 54, no ipv6 option header is supported \n"; - return (false); + + if ( CGlobalInfo::is_learn_mode() ) { + // We change TCP ports. Need them to be in first 64 byte mbuf. + // Since we also add IP option, and rx-check feature might add another IP option, better not allow + // OP options in this mode. If needed this limitation can be refined a bit. + if ( lpd->getTcpOffset() - lpd->getIpOffset() != 20 ) { + fprintf(stderr, "Error: Bad CAP file. In learn (NAT) mode, no IP options allowed \n"); + return kIPOptionNotAllowed; } - }else{ - if ( lpd->getTcpOffset() != (14+20) ){ - err=" supported template tcp/udp offset is 34, no ipv4 option is allowed in this version \n"; - return (false); + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP)) { + if (lpd->getIpProto() != IPPROTO_TCP && !lpd->m_desc.IsInitSide()) { + fprintf(stderr, "Error: In the chosen learn mode, all packets from server to client in CAP file should be TCP.\n"); + fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); + return kNoTCPFromServer; + } } } } if ( CGlobalInfo::is_learn_mode() ) { - if ( GetPacket(0)->m_pkt_indication.m_desc.IsPluginEnable() ) { - err="plugins are not supported with --learn mode \n"; - return(false); + CPacketIndication &pkt_0_indication = GetPacket(0)->m_pkt_indication; + + if ( pkt_0_indication.m_desc.IsPluginEnable() ) { + fprintf(stderr, "Error: plugins are not supported with --learn mode \n"); + return kPlugInWithLearn; + } + + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP)) { + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + if (Size() < 3) { + fprintf(stderr + , "Error: In the chosen learn mode, need at least the 3 TCP handshake packets.\n"); + fprintf(stderr + , " Please give different CAP file, or try different --learn-mode\n"); + return kTCPLearnModeBadFlow; + } + } + CPacketIndication &pkt_1_indication = GetPacket(1)->m_pkt_indication; + + + // verify first packet is TCP SYN from client + TCPHeader *tcp = (TCPHeader *)(pkt_0_indication.getBasePtr() + pkt_0_indication.getTcpOffset()); + if ( (! pkt_0_indication.m_desc.IsInitSide()) || (! tcp->getSynFlag()) ) { + fprintf(stderr, "Error: In the chosen learn mode, first TCP packet should be SYN from client side.\n"); + fprintf(stderr, " In cap file, first packet side direction is %s. TCP header is:\n" + , pkt_0_indication.m_desc.IsInitSide() ? "outside":"inside"); + tcp->dump(stderr); + fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); + return kNoSyn; + } + + // We want at least the TCP flags to be inside first mbuf + if (pkt_0_indication.getTcpOffset() + 14 > FIRST_PKT_SIZE) { + fprintf(stderr + , "Error: In the chosen learn mode, TCP flags offset should be less than %d, but it is %d.\n" + , FIRST_PKT_SIZE, pkt_0_indication.getTcpOffset() + 14); + fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); + return kTCPOffsetTooBig; + } + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + // To support TCP seq randomization from server to client, we need second packet in flow to be the server SYN+ACK + bool error = false; + if (pkt_1_indication.getIpProto() != IPPROTO_TCP) { + error = true; + } else { + TCPHeader *tcp = (TCPHeader *)(pkt_1_indication.getBasePtr() + pkt_1_indication.getTcpOffset()); + if ( (! tcp->getSynFlag()) || (! tcp->getAckFlag()) || ( pkt_1_indication.m_desc.IsInitSide())) { + error = true; + } + } + if (error) { + fprintf(stderr, "Error: In the chosen learn mode, second packet should be SYN+ACK from server.\n"); + fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); + return kNoTCPSynAck; + } + + CPacketIndication &pkt_2_indication = GetPacket(2)->m_pkt_indication; + if ( (! pkt_2_indication.m_desc.IsInitSide()) ) { + fprintf(stderr + , "Error: Wrong third packet. In the chosen learn mode, need at least the 3 TCP handshake packets.\n"); + fprintf(stderr + , " Please give different CAP file, or try different --learn-mode\n"); + return kTCPLearnModeBadFlow; + } + if ((pkt_0_indication.m_cap_ipg < (double)LEARN_MODE_MIN_IPG / 1000) + || (pkt_1_indication.m_cap_ipg < (double)LEARN_MODE_MIN_IPG / 1000)) { + fprintf(stderr + , "Error: Bad cap file timings. In the chosen learn mode"); + fprintf(stderr, "IPG between TCP handshake packets should be at least %d msec.\n", LEARN_MODE_MIN_IPG); + fprintf(stderr, " Current delay is %f between second and first, %f between third and second" + , pkt_0_indication.m_cap_ipg, pkt_1_indication.m_cap_ipg); + fprintf(stderr + , " Please give different CAP file, try different --learn-mode, or edit ipg parameters in template file\n"); + return kTCPIpgTooLow; + } + } } } - return(true); + + return(kOK); } @@ -2085,6 +2139,13 @@ void CCapFileFlowInfo::update_info(){ if ( lp->m_pkt_indication.m_desc.IsBiDirectionalFlow() ){ lp->mask_as_learn(); } + + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + // In this mode, we need to see the SYN+ACK as well. + lp = GetPacket(1); + assert(lp); + lp->m_pkt_indication.setTTL(TTL_RESERVE_DUPLICATE); + } } if ( ft.empty() ) @@ -2124,7 +2185,6 @@ enum CCapFileFlowInfo::load_cap_file_err CCapFileFlowInfo::load_cap_file(std::st m_total_errors=0; CFlow * first_flow=0; bool first_flow_fif_is_swap=false; - bool time_was_set=false; double last_time=0.0; CCapPktRaw raw_packet; @@ -2199,35 +2259,10 @@ enum CCapFileFlowInfo::load_cap_file_err CCapFileFlowInfo::load_cap_file(std::st m_total_errors++; } } - - if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { - // in this mode, first TCP packet must be SYN from client. - if (pkt_indication.getIpProto() == IPPROTO_TCP) { - TCPHeader *tcp = (TCPHeader *)(pkt_indication.getBasePtr() + pkt_indication.getTcpOffset()); - if ( (! pkt_indication.m_desc.IsInitSide()) || (! tcp->getSynFlag()) ) { - fprintf(stderr, "Error: In the chosen learn mode, first TCP packet should be SYN from client side.\n"); - fprintf(stderr, " In cap file, first packet side direction is %s. TCP header is:\n", pkt_indication.m_desc.IsInitSide() ? "outside":"inside"); - tcp->dump(stderr); - fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); - - return kNoSyn; - } - // We want at least the TCP flags to be inside first mbuf - if (pkt_indication.getTcpOffset() + 14 > FIRST_PKT_SIZE) { - fprintf(stderr, "Error: In the chosen learn mode, first TCP packet TCP flags offset should be less than %d, but it is %d.\n" - , FIRST_PKT_SIZE, pkt_indication.getTcpOffset() + 14); - fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); - return kTCPOffsetTooBig; - } - } - } - }else{ /* no FIF */ - pkt_indication.m_desc.SetFlowId(lpflow->flow_id); if ( multi_flow_enable ==false ){ - if (lpflow == first_flow) { // add to bool init_side= @@ -2249,16 +2284,6 @@ enum CCapFileFlowInfo::load_cap_file_err CCapFileFlowInfo::load_cap_file(std::st } } - - if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { - // This test must be down here, after initializing init side indication - if (pkt_indication.getIpProto() != IPPROTO_TCP && !pkt_indication.m_desc.IsInitSide()) { - fprintf(stderr, "Error: In the chosen learn mode, all packets from server to client in CAP file should be TCP.\n"); - fprintf(stderr, " Please give different CAP file, or try different --learn-mode\n"); - return kNoTCPFromServer; - } - } - }else{ fprintf(stderr, "ERROR packet %d is not supported, should be Ethernet/IP(0x0800)/(TCP|UDP) format try to convert it using Wireshark !\n",cnt); return kPktNotSupp; @@ -2269,7 +2294,6 @@ enum CCapFileFlowInfo::load_cap_file_err CCapFileFlowInfo::load_cap_file(std::st } } - /* set the last */ CFlowPktInfo * last_pkt =GetPacket((uint32_t)(Size()-1)); last_pkt->m_pkt_indication.m_desc.SetIsLastPkt(true); @@ -2282,9 +2306,6 @@ enum CCapFileFlowInfo::load_cap_file_err CCapFileFlowInfo::load_cap_file(std::st lp_prev->m_pkt_indication.m_cap_ipg = lp->m_pkt_indication.m_cap_ipg- lp_prev->m_pkt_indication.m_cap_ipg; - - - if ( lp->m_pkt_indication.m_desc.IsInitSide() != lp_prev->m_pkt_indication.m_desc.IsInitSide()) { lp_prev->m_pkt_indication.m_desc.SetRtt(true); @@ -2449,26 +2470,6 @@ void CCapFileFlowInfo::Delete(){ RemoveAll(); } -void operator >> (const YAML::Node& node, mac_mapping_t &fi) { - utl_yaml_read_ip_addr(node,"ip", fi.ip); - const YAML::Node& mac_info = node["mac"]; - for(unsigned i=0;i<mac_info.size();i++) { - const YAML::Node & node_2 =mac_info; - uint32_t value; - node_2[i] >> value; - fi.mac.mac[i] = value; - } -} - -void operator >> (const YAML::Node& node, std::map<uint32_t, mac_addr_align_t> &mac_info) { - const YAML::Node& mac_node = node["items"]; - mac_mapping_t mac_mapping; - for (unsigned i=0;i<mac_node.size();i++) { - mac_node[i] >> mac_mapping; - mac_info[mac_mapping.ip] = mac_mapping.mac; - } -} - void operator >> (const YAML::Node& node, CFlowYamlDpPkt & fi) { uint32_t val; node["pkt_id"] >> val; @@ -3166,11 +3167,8 @@ bool CFlowGeneratorRec::Create(CFlowYamlInfo * info, int res=m_flow_info.load_cap_file(info->m_name.c_str(),_id,m_info->m_plugin_id); if ( res==0 ) { fixup_ipg_if_needed(); - std::string err; - /* verify that template are valid */ - bool is_valid=m_flow_info.is_valid_template_load_time(err); - if (!is_valid) { - printf("\n ERROR template file is not valid '%s' \n",err.c_str()); + + if (m_flow_info.is_valid_template_load_time() != 0) { return (false); } m_flow_info.update_info(); @@ -3348,7 +3346,7 @@ bool CFlowGenListPerThread::Create(uint32_t thread_id, /* split the clients to threads */ CTupleGenYamlInfo * tuple_gen = &m_flow_list->m_yaml_info.m_tuple_gen; - m_smart_gen.Create(0,m_thread_id,m_flow_list->get_is_mac_conf()); + m_smart_gen.Create(0,m_thread_id); /* split the clients to threads using the mask */ CIpPortion portion; @@ -3358,14 +3356,14 @@ bool CFlowGenListPerThread::Create(uint32_t thread_id, portion); m_smart_gen.add_client_pool(tuple_gen->m_client_pool[i].m_dist, - portion.m_ip_start, - portion.m_ip_end, - get_longest_flow(i,true), - get_total_kcps(i,true)*1000, - &m_flow_list->m_mac_info, - tuple_gen->m_client_pool[i].m_tcp_aging_sec, - tuple_gen->m_client_pool[i].m_udp_aging_sec - ); + portion.m_ip_start, + portion.m_ip_end, + get_longest_flow(i,true), + get_total_kcps(i,true)*1000, + m_flow_list->m_client_config_info, + tuple_gen->m_client_pool[i].m_tcp_aging_sec, + tuple_gen->m_client_pool[i].m_udp_aging_sec + ); } for (int i=0;i<tuple_gen->m_server_pool.size();i++) { split_ips(m_thread_id, m_max_threads, getDualPortId(), @@ -3744,11 +3742,11 @@ inline int CNodeGenerator::teardown(CFlowGenListPerThread * thread, } return (0); } - + template<int SCH_MODE> -inline int CNodeGenerator::flush_file_realtime(dsec_t max_time, +inline int CNodeGenerator::flush_file_realtime(dsec_t max_time, dsec_t d_time, bool always, CFlowGenListPerThread * thread, @@ -3767,9 +3765,9 @@ inline int CNodeGenerator::flush_file_realtime(dsec_t max_time, sch_state_t state = scINIT; node = m_p_queue.top(); - n_time = node->m_time + offset; + n_time = node->m_time + offset; cur_time = now_sec(); - + while (state!=scTERMINATE) { switch (state) { @@ -3795,7 +3793,7 @@ inline int CNodeGenerator::flush_file_realtime(dsec_t max_time, break; } node = m_p_queue.top(); - n_time = node->m_time + offset; + n_time = node->m_time + offset; if ((n_time-cur_time)>EAT_WINDOW_DTIME) { state=scINIT; @@ -3805,7 +3803,7 @@ inline int CNodeGenerator::flush_file_realtime(dsec_t max_time, break; case scWAIT: - do_sleep(cur_time,thread,n_time); // estimate loop + do_sleep(cur_time,thread,n_time); // estimate loop state=scWORK; break; default: @@ -3816,7 +3814,7 @@ inline int CNodeGenerator::flush_file_realtime(dsec_t max_time, return (teardown(thread,always,old_offset,offset)); } -FORCE_NO_INLINE int CNodeGenerator::flush_file_sim(dsec_t max_time, +FORCE_NO_INLINE int CNodeGenerator::flush_file_sim(dsec_t max_time, dsec_t d_time, bool always, CFlowGenListPerThread * thread, @@ -3843,7 +3841,7 @@ FORCE_NO_INLINE int CNodeGenerator::flush_file_sim(dsec_t max_time, return (teardown(thread,always,old_offset,0)); } -int CNodeGenerator::flush_file(dsec_t max_time, +int CNodeGenerator::flush_file(dsec_t max_time, dsec_t d_time, bool always, CFlowGenListPerThread * thread, @@ -3864,7 +3862,7 @@ int CNodeGenerator::flush_file(dsec_t max_time, void CNodeGenerator::handle_flow_pkt(CGenNode *node, CFlowGenListPerThread *thread) { - /*repeat and NAT is not supported */ + /*repeat and NAT is not supported together */ if ( node->is_nat_first_state() ) { node->set_nat_wait_state(); flush_one_node_to_file(node); @@ -3875,7 +3873,7 @@ void CNodeGenerator::handle_flow_pkt(CGenNode *node, CFlowGenListPerThread *thre if ( node->is_nat_wait_state() ) { if (node->is_responder_pkt()) { m_p_queue.pop(); - /* time out, need to free the flow and remove the association , we didn't get convertion yet*/ + /* time out, need to free the flow and remove the association , we didn't get conversion yet*/ thread->terminate_nat_flows(node); return; @@ -3886,7 +3884,22 @@ void CNodeGenerator::handle_flow_pkt(CGenNode *node, CFlowGenListPerThread *thre #endif } } else { - assert(0); + if ( node->is_nat_wait_ack_state() ) { + if (node->is_initiator_pkt()) { + m_p_queue.pop(); + /* time out, need to free the flow and remove the association , we didn't get conversion yet*/ + thread->terminate_nat_flows(node); + return; + + } else { + flush_one_node_to_file(node); +#ifdef _DEBUG + update_stats(node); +#endif + } + } else { + assert(0); + } } } m_p_queue.pop(); @@ -4166,6 +4179,11 @@ int CFlowGenListPerThread::reschedule_flow(CGenNode *node){ void CFlowGenListPerThread::terminate_nat_flows(CGenNode *p){ m_stats.m_nat_flow_timeout++; m_stats.m_nat_lookup_remove_flow_id++; + if (p->is_nat_wait_ack_state()) { + m_stats.m_nat_flow_timeout_wait_ack++; + } else { + m_stats.m_nat_lookup_wait_ack_state++; + } m_flow_id_to_node_lookup.remove_no_lookup(p->get_short_fid()); free_last_flow_node( p); } @@ -4190,38 +4208,74 @@ void CFlowGenListPerThread::handle_latency_pkt_msg(CGenNodeLatencyPktInfo * msg) m_node_gen.m_v_if->send_one_pkt((pkt_dir_t)msg->m_dir,msg->m_pkt); } - void CFlowGenListPerThread::handle_nat_msg(CGenNodeNatInfo * msg){ int i; + bool first = true, second = true; + for (i=0; i<msg->m_cnt; i++) { + first = true; + second = true; CNatFlowInfo * nat_msg=&msg->m_data[i]; CGenNode * node=m_flow_id_to_node_lookup.lookup(nat_msg->m_fid); if (!node) { - /* this should be move to a notification module */ - #ifdef NAT_TRACE_ + /* this should be moved to a notification module */ +#ifdef NAT_TRACE_ printf(" ERORR not valid flow_id %d probably flow was aged \n",nat_msg->m_fid); - #endif +#endif m_stats.m_nat_lookup_no_flow_id++; continue; } - #ifdef NAT_TRACE_ - printf(" %.03f RX :set node %p:%x %x:%x:%x \n",now_sec() ,node,nat_msg->m_fid,nat_msg->m_external_ip,nat_msg->m_external_ip_server,nat_msg->m_external_port); - #endif - node->set_nat_ipv4_addr(nat_msg->m_external_ip); - node->set_nat_ipv4_port(nat_msg->m_external_port); - node->set_nat_ipv4_addr_server(nat_msg->m_external_ip_server); - - assert(node->is_nat_wait_state()); - if ( CGlobalInfo::is_learn_verify_mode() ){ - if (!node->is_external_is_eq_to_internal_ip() ){ - m_stats.m_nat_flow_learn_error++; + + // Calculate diff between tcp seq of SYN packet, and TCP ack of SYN+ACK packet + // For supporting firewalls who do TCP seq num randomization + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP)) { + if (node->is_nat_wait_state()) { + char *syn_pkt = node->m_flow_info->GetPacket(0)->m_packet->raw; + TCPHeader *tcp = (TCPHeader *)(syn_pkt + node->m_pkt_info->m_pkt_indication.getFastTcpOffset()); + node->set_nat_tcp_seq_diff_client(nat_msg->m_tcp_seq - tcp->getSeqNumber()); + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + node->set_nat_wait_ack_state(); + m_stats.m_nat_lookup_wait_ack_state++; + second = false; + } else { + node->set_nat_learn_state(); + } + } else { + char *syn_ack_pkt = node->m_flow_info->GetPacket(1)->m_packet->raw; + TCPHeader *tcp = (TCPHeader *)(syn_ack_pkt + node->m_pkt_info->m_pkt_indication.getFastTcpOffset()); + node->set_nat_tcp_seq_diff_server(nat_msg->m_tcp_seq - tcp->getSeqNumber()); + assert(node->is_nat_wait_ack_state()); + node->set_nat_learn_state(); + first = false; + } + } else { + assert(node->is_nat_wait_state()); + node->set_nat_learn_state(); + } + + if (first) { +#ifdef NAT_TRACE_ + printf(" %.03f RX :set node %p:%x %x:%x TCP diff %x\n" + , now_sec(), node,nat_msg->m_fid, nat_msg->m_external_ip, nat_msg->m_external_port + , node->get_nat_tcp_seq_diff_client()); +#endif + + node->set_nat_ipv4_addr(nat_msg->m_external_ip); + node->set_nat_ipv4_port(nat_msg->m_external_port); + + if ( CGlobalInfo::is_learn_verify_mode() ){ + if (!node->is_external_is_eq_to_internal_ip() || + node->get_nat_tcp_seq_diff_client() != 0) { + m_stats.m_nat_flow_learn_error++; + } } } - node->set_nat_learn_state(); - /* remove from the hash */ - m_flow_id_to_node_lookup.remove_no_lookup(nat_msg->m_fid); - m_stats.m_nat_lookup_remove_flow_id++; + if (second) { + /* remove from the hash */ + m_flow_id_to_node_lookup.remove_no_lookup(nat_msg->m_fid); + m_stats.m_nat_lookup_remove_flow_id++; + } } } @@ -4317,7 +4371,7 @@ void CFlowGenListPerThread::start_generate_stateful(std::string erf_file_name, fprintf(stderr," nothing to generate no template loaded \n"); return; } - + m_preview_mode = preview; m_node_gen.open_file(erf_file_name,&m_preview_mode); dsec_t d_time_flow=get_delta_flow_is_sec(); @@ -4419,32 +4473,11 @@ void CFlowGenList::clean_p_thread_info(void){ m_threads_info.clear(); } - - -int CFlowGenList::load_from_mac_file(std::string file_name) { - if ( !utl_is_file_exists (file_name) ){ - printf(" ERROR no mac_file is set, file %s does not exist \n",file_name.c_str()); - exit(-1); - } - m_mac_info.set_configured(true); - - try { - std::ifstream fin((char *)file_name.c_str()); - YAML::Parser parser(fin); - YAML::Node doc; - - parser.GetNextDocument(doc); - doc[0] >> m_mac_info.get_mac_info(); - } catch ( const std::exception& e ) { - std::cout << e.what() << "\n"; - m_mac_info.clear(); - exit(-1); - } - - return (0); +int CFlowGenList::load_client_config_file(std::string file_name) { + m_client_config_info.load_yaml_file(file_name); + return (0); } - int CFlowGenList::load_from_yaml(std::string file_name, uint32_t num_threads){ uint8_t idx; @@ -4524,6 +4557,16 @@ double CFlowGenList::GetCpuUtil(){ return (c/m_threads_info.size()); } +double CFlowGenList::GetCpuUtilRaw(){ + int i; + double c=0.0; + for (i=0; i<(int)m_threads_info.size(); i++) { + CFlowGenListPerThread * lp=m_threads_info[i]; + c+=lp->m_cpu_cp_u.GetValRaw(); + } + return (c/m_threads_info.size()); +} + void CFlowGenList::UpdateFast(){ @@ -4755,21 +4798,20 @@ bool CParserOption::is_valid_opt_val(int val, int min, int max, const std::strin void CParserOption::dump(FILE *fd){ preview.Dump(fd); - fprintf(fd," cfg file : %s \n",cfg_file.c_str()); - fprintf(fd," mac file : %s \n",mac_file.c_str()); - fprintf(fd," out file : %s \n",out_file.c_str()); - fprintf(fd," duration : %.0f \n",m_duration); - fprintf(fd," factor : %.0f \n",m_factor); - fprintf(fd," mbuf_factor : %.0f \n",m_mbuf_factor); - fprintf(fd," latency : %d pkt/sec \n",m_latency_rate); - fprintf(fd," zmq_port : %d \n",m_zmq_port); - fprintf(fd," telnet_port : %d \n",m_telnet_port); - fprintf(fd," expected_ports : %d \n",m_expected_portd); + fprintf(fd," cfg file : %s \n",cfg_file.c_str()); + fprintf(fd," mac file : %s \n",client_cfg_file.c_str()); + fprintf(fd," out file : %s \n",out_file.c_str()); + fprintf(fd," client cfg file : %s \n",out_file.c_str()); + fprintf(fd," duration : %.0f \n",m_duration); + fprintf(fd," factor : %.0f \n",m_factor); + fprintf(fd," mbuf_factor : %.0f \n",m_mbuf_factor); + fprintf(fd," latency : %d pkt/sec \n",m_latency_rate); + fprintf(fd," zmq_port : %d \n",m_zmq_port); + fprintf(fd," telnet_port : %d \n",m_telnet_port); + fprintf(fd," expected_ports : %d \n",m_expected_portd); if (preview.get_vlan_mode_enable() ) { - fprintf(fd," vlans : [%d,%d] \n",m_vlan_port[0],m_vlan_port[1]); + fprintf(fd," vlans : [%d,%d] \n",m_vlan_port[0],m_vlan_port[1]); } - fprintf(fd," mac spreading: %d \n",(int)m_mac_splitter); - int i; for (i = 0; i < TREX_MAX_PORTS; i++) { @@ -4782,6 +4824,15 @@ void CParserOption::dump(FILE *fd){ } } +void CParserOption::verify() { + /* check for mutual exclusion options */ + if (preview.get_is_client_cfg_enable()) { + if (preview.get_vlan_mode_enable() || preview.get_mac_ip_overide_enable()) { + throw std::runtime_error("VLAN / MAC override cannot be combined with client configuration"); + } + } +} + #if 0 void CTupleGlobalGenerator::Dump(FILE *fd){ @@ -5068,37 +5119,68 @@ int CErfIFStl::send_node(CGenNode * _no_to_use){ return (0); } +void CErfIF::add_vlan(uint16_t vlan_id) { + uint8_t *buffer =(uint8_t *)m_raw->raw; + uint16_t vlan_protocol = EthernetHeader::Protocol::VLAN; + uint32_t vlan_tag = (vlan_protocol << 16) | vlan_id; + vlan_tag = PKT_HTONL(vlan_tag); -int CErfIF::send_node(CGenNode * node){ + /* insert vlan tag and adjust packet size */ + memcpy(cbuff+4, buffer + 12, m_raw->pkt_len - 12); + memcpy(cbuff, &vlan_tag, 4); + memcpy(buffer + 12, cbuff, m_raw->pkt_len - 8); - if ( m_preview_mode->getFileWrite() ){ + m_raw->pkt_len += 4; +} - CFlowPktInfo * lp=node->m_pkt_info; - rte_mbuf_t * m=lp->generate_new_mbuf(node); - pkt_dir_t dir=node->cur_interface_dir(); +void CErfIF::apply_client_config(const ClientCfg *cfg, pkt_dir_t dir) { + assert(cfg); + uint8_t *p = (uint8_t *)m_raw->raw; - fill_raw_packet(m,node,dir); + const ClientCfgDir &cfg_dir = ( (dir == CLIENT_SIDE) ? cfg->m_initiator : cfg->m_responder); + + /* dst mac */ + if (cfg_dir.has_dst_mac_addr()) { + memcpy(p, cfg_dir.get_dst_mac_addr(), 6); + } + + /* src mac */ + if (cfg_dir.has_src_mac_addr()) { + memcpy(p + 6, cfg_dir.get_src_mac_addr(), 6); + } + + /* VLAN */ + if (cfg_dir.has_vlan()) { + add_vlan(cfg_dir.get_vlan()); + } +} + +int CErfIF::send_node(CGenNode *node){ + + if (!m_preview_mode->getFileWrite()) { + return (0); + } + + CFlowPktInfo *lp = node->m_pkt_info; + rte_mbuf_t *m = lp->generate_new_mbuf(node); + pkt_dir_t dir = node->cur_interface_dir(); + + fill_raw_packet(m, node, dir); /* update mac addr dest/src 12 bytes */ uint8_t *p=(uint8_t *)m_raw->raw; int p_id=(int)dir; memcpy(p,CGlobalInfo::m_options.get_dst_src_mac_addr(p_id),12); - /* If vlan is enabled, add vlan header */ - if ( unlikely( CGlobalInfo::m_options.preview.get_vlan_mode_enable() ) ){ - /* retrieve vlan ID and form vlan tag */ - uint8_t vlan_port = (node->m_src_ip &1); - uint16_t vlan_protocol = EthernetHeader::Protocol::VLAN; - uint16_t vlan_id = CGlobalInfo::m_options.m_vlan_port[vlan_port]; - uint32_t vlan_tag = (vlan_protocol << 16) | vlan_id; - vlan_tag = PKT_HTONL(vlan_tag); + /* if a client configuration was provided - apply the config */ + if (CGlobalInfo::m_options.preview.get_is_client_cfg_enable()) { + apply_client_config(node->m_client_cfg, dir); - /* insert vlan tag and adjust packet size */ - memcpy(cbuff+4, p+12, m_raw->pkt_len-12); - memcpy(cbuff, &vlan_tag, 4); - memcpy(p+12, cbuff, m_raw->pkt_len-8); - m_raw->pkt_len += 4; + } else if (CGlobalInfo::m_options.preview.get_vlan_mode_enable()) { + uint8_t vlan_port = (node->m_src_ip & 1); + uint16_t vlan_id = CGlobalInfo::m_options.m_vlan_port[vlan_port]; + add_vlan(vlan_id); } //utl_DumpBuffer(stdout,p, 12,0); @@ -5107,8 +5189,8 @@ int CErfIF::send_node(CGenNode * node){ BP_ASSERT(rc == 0); rte_pktmbuf_free(m); - } - return (0); + + return (0); } int CErfIF::flush_tx_queue(void){ @@ -6438,7 +6520,7 @@ void CGenNodeBase::free_base(){ CGenNodePCAP *p = (CGenNodePCAP *)this; p->destroy(); return; - } + } if ( m_type == COMMAND ) { CGenNodeCommand* p=(CGenNodeCommand*)this; @@ -6446,4 +6528,3 @@ void CGenNodeBase::free_base(){ } } - diff --git a/src/bp_sim.h b/src/bp_sim.h index 18db61ca..b4ef54d1 100755 --- a/src/bp_sim.h +++ b/src/bp_sim.h @@ -59,6 +59,7 @@ limitations under the License. #include "platform_cfg.h" #include "flow_stat.h" #include "trex_watchdog.h" +#include "trex_client_config.h" #include <trex_stateless_dp_core.h> @@ -68,8 +69,6 @@ limitations under the License. class CGenNodePCAP; -#undef NAT_TRACE_ - #define FORCE_NO_INLINE __attribute__ ((noinline)) #define FORCE_INLINE __attribute__((always_inline)) @@ -87,10 +86,6 @@ typedef struct { */ #define INET_PORTSTRLEN 5 - - - - /* VM commands */ class CMiniVMCmdBase { @@ -186,6 +181,12 @@ inline int ip_to_str(uint32_t ip,char * str){ return(strlen(str)); } +inline std::string ip_to_str(uint32_t ip) { + char tmp[INET_ADDRSTRLEN]; + ip_to_str(ip, tmp); + return tmp; +} + // Routine to create IPv6 address string inline int ipv6_to_str(ipaddr_t *ip,char * str){ int idx=0; @@ -627,7 +628,7 @@ public: void set_mac_ip_overide_enable(bool enable){ btSetMaskBit32(m_flags,30,30,enable?1:0); if (enable) { - set_mac_ip_features_enable(enable); + set_slowpath_features_on(enable); } } @@ -639,26 +640,27 @@ public: btSetMaskBit32(m_flags,31,31,enable?1:0); } - - bool get_mac_ip_features_enable(){ - return (btGetMaskBit32(m_flags1,0,0) ? true:false); + bool get_is_slowpath_features_on() { + return (btGetMaskBit32(m_flags1, 0, 0) ? true : false); } - void set_mac_ip_features_enable(bool enable){ - btSetMaskBit32(m_flags1,0,0,enable?1:0); + void set_slowpath_features_on(bool enable) { + btSetMaskBit32(m_flags1, 0, 0, enable ? 1 : 0); } - bool get_mac_ip_mapping_enable(){ - return (btGetMaskBit32(m_flags1,1,1) ? true:false); + bool get_is_client_cfg_enable() { + return (btGetMaskBit32(m_flags1, 1, 1) ? true : false); } - void set_mac_ip_mapping_enable(bool enable){ - btSetMaskBit32(m_flags1,1,1,enable?1:0); + void set_client_cfg_enable(bool enable){ + btSetMaskBit32(m_flags1, 1, 1, enable ? 1 : 0); if (enable) { - set_mac_ip_features_enable(enable); + set_slowpath_features_on(enable); } } + + bool get_vm_one_queue_enable(){ return (btGetMaskBit32(m_flags1,2,2) ? true:false); } @@ -684,16 +686,6 @@ public: return (btGetMaskBit32(m_flags1,3,3) ? true:false); } - - /* split mac is enabled */ - void setDestMacSplit(bool enable){ - btSetMaskBit32(m_flags1,4,4,enable?1:0); - } - - bool getDestMacSplit(){ - return (btGetMaskBit32(m_flags1,4,4) ? true:false); - } - /* split mac is enabled */ void setWDDisable(bool wd_disable){ btSetMaskBit32(m_flags1,6,6,wd_disable?1:0); @@ -771,7 +763,10 @@ public: LEARN_MODE_DISABLED=0, LEARN_MODE_TCP_ACK=1, LEARN_MODE_IP_OPTION=2, - LEARN_MODE_MAX=LEARN_MODE_IP_OPTION + LEARN_MODE_TCP_ACK_NO_SERVER_SEQ_RAND=3, + LEARN_MODE_MAX=LEARN_MODE_TCP_ACK_NO_SERVER_SEQ_RAND, + // This is used to check if 1 or 3 exist + LEARN_MODE_TCP=100 }; public: @@ -782,6 +777,7 @@ public: m_latency_rate =0; m_latency_mask =0xffffffff; m_latency_prev=0; + m_wait_before_traffic=1; m_zmq_port=4500; m_telnet_port =4501; m_platform_factor=1.0; @@ -793,7 +789,6 @@ public: m_io_mode=1; m_run_flags=0; prefix=""; - m_mac_splitter=0; m_run_mode = RUN_MODE_INVALID; m_l_pkt_mode = 0; m_rx_thread_enabled = false; @@ -812,6 +807,7 @@ public: uint32_t m_latency_rate; /* pkt/sec for each thread/port zero disable */ uint32_t m_latency_mask; uint32_t m_latency_prev; + uint32_t m_wait_before_traffic; uint16_t m_rx_check_sample; /* the sample rate of flows */ uint16_t m_rx_check_hops; uint16_t m_zmq_port; @@ -819,7 +815,6 @@ public: uint16_t m_expected_portd; uint16_t m_io_mode; //0,1,2 0 disable, 1- normal , 2 - short uint16_t m_run_flags; - uint8_t m_mac_splitter; uint8_t m_l_pkt_mode; uint8_t m_learn_mode; uint16_t m_debug_pkt_proto; @@ -829,7 +824,7 @@ public: std::string cfg_file; - std::string mac_file; + std::string client_cfg_file; std::string platform_cfg_file; std::string out_file; @@ -887,6 +882,8 @@ public: } void dump(FILE *fd); bool is_valid_opt_val(int val, int min, int max, const std::string &opt_name); + + void verify(); }; @@ -1196,13 +1193,10 @@ public: void dump_in_case_of_error(FILE *fd); - std::string dump_as_json(uint8_t id,bool last); + void dump_as_json(Json::Value &json); private: - std::string add_to_json(std::string name, - rte_mempool_t * pool, - bool last=false); - + void add_to_json(Json::Value &json, std::string name, rte_mempool_t * pool); public: rte_mempool_t * m_small_mbuf_pool; /* pool for start packets */ @@ -1261,7 +1255,11 @@ public: } static inline bool is_learn_mode(CParserOption::trex_learn_mode_e mode){ - return ( (m_options.m_learn_mode == mode)); + if (mode == CParserOption::LEARN_MODE_TCP) { + return ((m_options.m_learn_mode == CParserOption::LEARN_MODE_TCP_ACK_NO_SERVER_SEQ_RAND) + || (m_options.m_learn_mode == CParserOption::LEARN_MODE_TCP_ACK)); + } else + return (m_options.m_learn_mode == mode); } static inline bool is_ipv6_enable(void){ @@ -1296,7 +1294,8 @@ public: } - static std::string dump_pool_as_json(void); + static void dump_pool_as_json(Json::Value &json); + static std::string dump_pool_as_json_str(void); public: @@ -1572,12 +1571,12 @@ public: CTupleGeneratorSmart *m_tuple_gen; // cache line 1 - 64bytes waste of space ! - uint32_t m_nat_external_ipv4; /* client */ - uint32_t m_nat_external_ipv4_server; - uint16_t m_nat_external_port; - - uint16_t m_nat_pad[3]; - mac_addr_align_t m_src_mac; + uint32_t m_nat_external_ipv4; // NAT client IP + uint32_t m_nat_tcp_seq_diff_client; // support for firewalls that do TCP seq num randomization + uint32_t m_nat_tcp_seq_diff_server; // And some do seq num randomization for server->client also + uint16_t m_nat_external_port; // NAT client port + uint16_t m_nat_pad[1]; + const ClientCfg *m_client_cfg; uint32_t m_src_idx; uint32_t m_dest_idx; uint32_t m_end_of_cache_line[6]; @@ -1713,6 +1712,15 @@ public: return (btGetMaskBit16(m_flags,4,3)==2?true:false) ; } + // We saw first TCP SYN. Waiting for SYN+ACK + inline void set_nat_wait_ack_state() { + btSetMaskBit16(m_flags, 4, 3, 3); + } + + inline bool is_nat_wait_ack_state(){ + return (btGetMaskBit16(m_flags,4,3) == 3) ? true : false; + } + inline void set_nat_learn_state(){ m_type=FLOW_PKT; /* normal operation .. repeat might work too */ } @@ -1726,14 +1734,21 @@ public: return (m_thread_id); } - inline void set_nat_ipv4_addr_server(uint32_t ip){ - m_nat_external_ipv4_server =ip; + inline void set_nat_tcp_seq_diff_client(uint32_t diff) { + m_nat_tcp_seq_diff_client = diff; + } + + inline uint32_t get_nat_tcp_seq_diff_client() { + return m_nat_tcp_seq_diff_client; } - inline uint32_t get_nat_ipv4_addr_server(){ - return ( m_nat_external_ipv4_server ); + inline void set_nat_tcp_seq_diff_server(uint32_t diff) { + m_nat_tcp_seq_diff_server = diff; } + inline uint32_t get_nat_tcp_seq_diff_server() { + return m_nat_tcp_seq_diff_server; + } inline void set_nat_ipv4_addr(uint32_t ip){ m_nat_external_ipv4 =ip; @@ -1754,8 +1769,7 @@ public: bool is_external_is_eq_to_internal_ip(){ /* this API is used to check TRex itself */ if ( (get_nat_ipv4_addr() == m_src_ip ) && - (get_nat_ipv4_port()==m_src_port) && - ( get_nat_ipv4_addr_server() == m_dest_ip) ) { + (get_nat_ipv4_port()==m_src_port)) { return (true); }else{ return (false); @@ -1869,8 +1883,10 @@ public: uint64_t m_total_close_flows; uint64_t m_nat_lookup_no_flow_id; uint64_t m_nat_lookup_remove_flow_id; + uint64_t m_nat_lookup_wait_ack_state; uint64_t m_nat_lookup_add_flow_id; uint64_t m_nat_flow_timeout; + uint64_t m_nat_flow_timeout_wait_ack; uint64_t m_nat_flow_learn_error; public: @@ -1923,7 +1939,8 @@ public: protected: - + void add_vlan(uint16_t vlan_id); + void apply_client_config(const ClientCfg *cfg, pkt_dir_t dir); virtual void fill_raw_packet(rte_mbuf_t * m,CGenNode * node,pkt_dir_t dir); CFileWriterBase * m_writer; @@ -3017,6 +3034,8 @@ inline void CFlowPktInfo::update_pkt_info(char *p, (void)et; uint16_t src_port = node->m_src_port; + uint32_t tcp_seq_diff_client = 0; + uint32_t tcp_seq_diff_server = 0; pkt_dir_t ip_dir = node->cur_pkt_ip_addr_dir(); pkt_dir_t port_dir = node->cur_pkt_port_addr_dir(); @@ -3037,7 +3056,6 @@ inline void CFlowPktInfo::update_pkt_info(char *p, }else{ if ( unlikely ( CGlobalInfo::is_learn_mode() ) ){ - if (m_pkt_indication.m_desc.IsLearn()) { /* might be done twice */ #ifdef NAT_TRACE_ @@ -3046,42 +3064,48 @@ inline void CFlowPktInfo::update_pkt_info(char *p, ipv4->setTimeToLive(TTL_RESERVE_DUPLICATE); /* first ipv4 option add the info in case of learn packet, usualy only the first packet */ - if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_IP_OPTION)) { - CNatOption *lpNat =(CNatOption *)ipv4->getOption(); - lpNat->set_fid(node->get_short_fid()); - lpNat->set_thread_id(node->get_thread_id()); - } else { - // This method only work on first TCP SYN - if (ipv4->getProtocol() == IPPROTO_TCP) { - TCPHeader *tcp = (TCPHeader *)(((uint8_t *)ipv4) + ipv4->getHeaderLength()); - if (tcp->getSynFlag()) { - tcp->setAckNumber(CNatRxManager::calc_tcp_ack_val(node->get_short_fid(), node->get_thread_id())); - } + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_IP_OPTION)) { + CNatOption *lpNat =(CNatOption *)ipv4->getOption(); + lpNat->set_fid(node->get_short_fid()); + lpNat->set_thread_id(node->get_thread_id()); + } else { + // This method only work on first TCP SYN + if (ipv4->getProtocol() == IPPROTO_TCP) { + TCPHeader *tcp = (TCPHeader *)(((uint8_t *)ipv4) + ipv4->getHeaderLength()); + if (tcp->getSynFlag()) { + tcp->setAckNumber(CNatRxManager::calc_tcp_ack_val(node->get_short_fid(), node->get_thread_id())); + } #ifdef NAT_TRACE_ - printf(" %.3f : flow_id: %x thread_id %x TCP ack %x\n",now_sec(), node->get_short_fid(), node->get_thread_id(), tcp->getAckNumber()); + printf(" %.3f : flow_id: %x thread_id %x TCP ack %x seq %x\n" + ,now_sec(), node->get_short_fid(), node->get_thread_id(), tcp->getAckNumber() + , tcp->getSeqNumber()); #endif - } - } + } + } } /* in all cases update the ip using the outside ip */ if ( m_pkt_indication.m_desc.IsInitSide() ) { #ifdef NAT_TRACE_ if (node->m_flags != CGenNode::NODE_FLAGS_LATENCY ) { - printf(" %.3f : DP : i %x:%x -> %x flow_id: %lx\n",now_sec(),node->m_src_ip,node->m_src_port,node->m_dest_ip,node->m_flow_id); + printf(" %.3f : DP : i %x:%x -> %x flow_id: %lx\n",now_sec(), node->m_src_ip + , node->m_src_port, node->m_dest_ip, node->m_flow_id); } #endif + tcp_seq_diff_server = node->get_nat_tcp_seq_diff_server(); ipv4->updateIpSrc(node->m_src_ip); ipv4->updateIpDst(node->m_dest_ip); - }else{ + } else { #ifdef NAT_TRACE_ if (node->m_flags != CGenNode::NODE_FLAGS_LATENCY ) { - printf(" %.3f : r %x -> %x:%x flow_id: %lx \n",now_sec(),node->m_dest_ip,node->m_src_ip,node->m_src_port,node->m_flow_id); + printf(" %.3f : r %x -> %x:%x flow_id: %lx \n", now_sec(), node->m_dest_ip + , node->m_src_ip, node->m_src_port, node->m_flow_id); } #endif src_port = node->get_nat_ipv4_port(); - ipv4->updateIpSrc(node->get_nat_ipv4_addr_server()); + tcp_seq_diff_client = node->get_nat_tcp_seq_diff_client(); + ipv4->updateIpSrc(node->m_dest_ip); ipv4->updateIpDst(node->get_nat_ipv4_addr()); } @@ -3089,7 +3113,7 @@ inline void CFlowPktInfo::update_pkt_info(char *p, #ifdef NAT_TRACE_ if (node->m_flags != CGenNode::NODE_FLAGS_LATENCY ) { if ( m_pkt_indication.m_desc.IsInitSide() ==false ){ - printf(" %.3f : pkt ==> %x:%x %x:%x \n",now_sec(),node->get_nat_ipv4_addr(),node->get_nat_ipv4_addr_server(), + printf(" %.3f : pkt ==> %x %x:%x \n",now_sec(),node->get_nat_ipv4_addr(), node->get_nat_ipv4_port(),node->m_src_port); }else{ printf(" %.3f : pkt ==> init pkt sent \n",now_sec()); @@ -3137,8 +3161,10 @@ inline void CFlowPktInfo::update_pkt_info(char *p, /* replace port */ if ( port_dir == CLIENT_SIDE ) { m_tcp->setSourcePort(src_port); + m_tcp->setAckNumber(m_tcp->getAckNumber() + tcp_seq_diff_server); }else{ m_tcp->setDestPort(src_port); + m_tcp->setAckNumber(m_tcp->getAckNumber() + tcp_seq_diff_client); } #ifdef RTE_DPDK @@ -3409,6 +3435,8 @@ public: class CCapFileFlowInfo { public: + const int LEARN_MODE_MIN_IPG = 10; // msec + enum load_cap_file_err { kOK = 0, kFileNotExist, @@ -3416,9 +3444,14 @@ public: kNoSyn, kTCPOffsetTooBig, kNoTCPFromServer, + kNoTCPSynAck, + kTCPLearnModeBadFlow, kPktNotSupp, kPktProcessFail, - kCapFileErr + kCapFileErr, + kPlugInWithLearn, + kIPOptionNotAllowed, + kTCPIpgTooLow }; bool Create(); @@ -3435,7 +3468,7 @@ public: /* update flow info */ void update_info(); - bool is_valid_template_load_time(std::string & err); + enum CCapFileFlowInfo::load_cap_file_err is_valid_template_load_time(); void save_to_erf(std::string cap_file_name,int pcap); @@ -3930,13 +3963,15 @@ public: public: int load_from_yaml(std::string csv_file,uint32_t num_threads); - int load_from_mac_file(std::string csv_file); + int load_client_config_file(std::string file_name); + public: void Dump(FILE *fd); void DumpCsv(FILE *fd); void DumpPktSize(); void UpdateFast(); double GetCpuUtil(); + double GetCpuUtilRaw(); public: double get_total_kcps(); @@ -3944,12 +3979,12 @@ public: double get_total_tx_bps(); uint32_t get_total_repeat_flows(); double get_delta_flow_is_sec(); - bool get_is_mac_conf() { return m_mac_info.is_configured();} + public: - std::vector<CFlowGeneratorRec *> m_cap_gen; /* global info */ - CFlowsYamlInfo m_yaml_info; /* global yaml*/ - std::vector<CFlowGenListPerThread *> m_threads_info; - CFlowGenListMac m_mac_info; + std::vector<CFlowGeneratorRec *> m_cap_gen; /* global info */ + CFlowsYamlInfo m_yaml_info; /* global yaml*/ + std::vector<CFlowGenListPerThread *> m_threads_info; + ClientCfgDB m_client_config_info; }; @@ -3989,9 +4024,8 @@ inline void CCapFileFlowInfo::generate_flow(CTupleTemplateGeneratorSmart * tup node->m_src_idx = tuple.getClientId(); node->m_dest_idx = tuple.getServerId(); node->m_src_port = tuple.getClientPort(); - memcpy(&node->m_src_mac, - tuple.getClientMac(), - sizeof(mac_addr_align_t)); + node->m_client_cfg = tuple.getClientCfg(); + node->m_plugin_info =(void *)0; if ( unlikely( CGlobalInfo::is_learn_mode() ) ){ diff --git a/src/common/Network/Packet/TcpHeader.h b/src/common/Network/Packet/TcpHeader.h index c19cd262..97575a60 100755 --- a/src/common/Network/Packet/TcpHeader.h +++ b/src/common/Network/Packet/TcpHeader.h @@ -23,6 +23,11 @@ class TCPHeader { public: + enum TCPHeader_enum_t + { + TCP_INVALID_PORT = 0 + }; + TCPHeader(){} TCPHeader(uint16_t argSourcePort, diff --git a/src/flow_stat.cpp b/src/flow_stat.cpp index 5503434f..8c2f2566 100644 --- a/src/flow_stat.cpp +++ b/src/flow_stat.cpp @@ -796,6 +796,9 @@ int CFlowStatRuleMgr::start_stream(TrexStream * stream) { if (m_num_started_streams == 0) { send_start_stop_msg_to_rx(true); // First transmitting stream. Rx core should start reading packets; + //also good time to zero global counters + memset(m_rx_cant_count_err, 0, sizeof(m_rx_cant_count_err)); + memset(m_tx_cant_count_err, 0, sizeof(m_tx_cant_count_err)); // wait to make sure that message is acknowledged. RX core might be in deep sleep mode, and we want to // start transmitting packets only after it is working, otherwise, packets will get lost. @@ -910,7 +913,7 @@ int CFlowStatRuleMgr::stop_stream(TrexStream * stream) { m_num_started_streams--; assert (m_num_started_streams >= 0); if (m_num_started_streams == 0) { - send_start_stop_msg_to_rx(false); // No more transmittig streams. Rx core shoulde get into idle loop. + send_start_stop_msg_to_rx(false); // No more transmittig streams. Rx core should get into idle loop. } return 0; } @@ -947,6 +950,7 @@ bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, boo tx_per_flow_t tx_stats[MAX_FLOW_STATS]; tx_per_flow_t tx_stats_payload[MAX_FLOW_STATS_PAYLOAD]; rfc2544_info_t rfc2544_info[MAX_FLOW_STATS_PAYLOAD]; + CRxCoreErrCntrs rx_err_cntrs; Json::FastWriter writer; Json::Value s_root; Json::Value l_root; @@ -973,6 +977,7 @@ bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, boo } m_api->get_rfc2544_info(rfc2544_info, 0, m_max_hw_id_payload, false); + m_api->get_rx_err_cntrs(&rx_err_cntrs); // read hw counters, and update for (uint8_t port = 0; port < m_num_ports; port++) { @@ -1046,10 +1051,21 @@ bool CFlowStatRuleMgr::dump_json(std::string & s_json, std::string & l_json, boo // general per port data for (uint8_t port = 0; port < m_num_ports; port++) { std::string str_port = static_cast<std::ostringstream*>( &(std::ostringstream() << int(port) ) )->str(); - if (m_rx_cant_count_err[port] != 0) - s_data_section["port_data"][str_port]["rx_err"] = m_rx_cant_count_err[port]; - if (m_tx_cant_count_err[port] != 0) - s_data_section["port_data"][str_port]["tx_err"] = m_tx_cant_count_err[port]; + if ((m_rx_cant_count_err[port] != 0) || baseline) + s_data_section["global"]["rx_err"][str_port] = m_rx_cant_count_err[port]; + if ((m_tx_cant_count_err[port] != 0) || baseline) + s_data_section["global"]["tx_err"][str_port] = m_tx_cant_count_err[port]; + } + + // payload rules rx errors + uint64_t tmp_cnt; + tmp_cnt = rx_err_cntrs.get_bad_header(); + if (tmp_cnt || baseline) { + l_data_section["global"]["bad_hdr"] = Json::Value::UInt64(tmp_cnt); + } + tmp_cnt = rx_err_cntrs.get_old_flow(); + if (tmp_cnt || baseline) { + l_data_section["global"]["old_flow"] = Json::Value::UInt64(tmp_cnt); } flow_stat_user_id_map_it_t it; diff --git a/src/global_io_mode.cpp b/src/global_io_mode.cpp index 2457599e..289863c9 100755 --- a/src/global_io_mode.cpp +++ b/src/global_io_mode.cpp @@ -103,6 +103,14 @@ bool CTrexGlobalIoMode::handle_io_modes(void){ m_g_mode=gNORMAL; } break; + case ccNat: + m_g_mode=gNAT; + m_nat_mode++; + if (m_nat_mode==natLAST) { + m_nat_mode = natDISABLE; + m_g_mode = gNORMAL; + } + break; } @@ -121,17 +129,18 @@ void CTrexGlobalIoMode::Dump(FILE *fd){ } void CTrexGlobalIoMode::DumpHelp(FILE *fd){ - fprintf(fd,"Help for Interactive Commands - Trex \n" ); - fprintf(fd," d : Toggle, Disable all -> Noraml \n"); - fprintf(fd," n : Default mode all in Normal mode \n"); - fprintf(fd," h : Toggle, Help->Normal \n"); - fprintf(fd,"\n"); - fprintf(fd," p : Per ports Toggle mode, disable -> table -> normal \n"); - fprintf(fd," a : Global ports Toggle mode, disable -> enable \n"); - fprintf(fd," l : Latency Toggle mode, disable -> enable -> enhanced \n"); - fprintf(fd," r : Rx check Toggle mode, disable -> enable -> enhanced \n"); - fprintf(fd," m : memory stats , disable -> enable \n"); - fprintf(fd," Press h or 1 to go back to Normal mode \n"); + fprintf(fd, "Help for Interactive Commands\n" ); + fprintf(fd, " %c : Toggle, Disable all/Default \n", ccGDISABLE); + fprintf(fd, " %c : Go back to default mode \n", ccGNORAML); + fprintf(fd, " %c : Toggle, Help/Default \n", ccHELP); + fprintf(fd, "\n"); + fprintf(fd, " %c : Per ports toggle disable -> table -> normal \n", ccGPP); + fprintf(fd, " %c : Global ports toggle disable/enable \n", ccGAP); + fprintf(fd, " %c : Latency toggle disable -> enable -> enhanced \n", ccGL); + fprintf(fd, " %c : Rx check toggle disable -> enable -> enhanced \n", ccGRC); + fprintf(fd, " %c : Memory stats toggle disable/enable \n", ccMem); + fprintf(fd, " %c : NAT pending flows toggle disable/enable \n", ccNat); + fprintf(fd, " Press %c or %c to go back to Normal mode \n", ccHELP, ccGNORAML); } diff --git a/src/global_io_mode.h b/src/global_io_mode.h index 84b402b7..44fa4be5 100755 --- a/src/global_io_mode.h +++ b/src/global_io_mode.h @@ -48,12 +48,13 @@ public: enum Chars{ ccHELP='h', ccGDISABLE='d', - ccGNORAML='n', + ccGNORAML='0', ccGPP='p', ccGAP='a', ccGL='l', ccGRC='r', - ccMem='m' + ccMem='m', + ccNat='n' }; enum CliDumpMode { @@ -67,7 +68,8 @@ public: gDISABLE=0, // no print at all gHELP=1, // help gNORMAL=2, // normal - gMem=3 + gMem=3, + gNAT }; @@ -104,12 +106,20 @@ public: }; typedef uint8_t RxCheckMode_t; + enum NatMode { + natDISABLE = 0, + natENABLE = 1, + natLAST = 2 + }; + typedef uint8_t NatMode_t; + Global_t m_g_mode; bool m_g_disable_first; PerPortCountersMode_t m_pp_mode; AllPortCountersMode_t m_ap_mode; LatecnyMode_t m_l_mode; RxCheckMode_t m_rc_mode; + NatMode_t m_nat_mode; public: void set_mode(CliDumpMode mode); diff --git a/src/gtest/tuple_gen_test.cpp b/src/gtest/tuple_gen_test.cpp index f3b9fa1e..fa760c6d 100755 --- a/src/gtest/tuple_gen_test.cpp +++ b/src/gtest/tuple_gen_test.cpp @@ -25,6 +25,22 @@ limitations under the License. /* TEST case for CClientInfo*/ +class CClientInfo : public CSimpleClientInfo<CIpInfo> { +public: + CClientInfo() : CSimpleClientInfo<CIpInfo>(0) { + + } +}; + +class CClientInfoL : public CSimpleClientInfo<CIpInfoL> { +public: + CClientInfoL() : CSimpleClientInfo<CIpInfoL>(0) { + + } +}; + +static ClientCfgDB g_dummy; + class CClientInfoUT { public: CClientInfoUT(CClientInfo *a) { @@ -157,7 +173,7 @@ TEST(CClientInfoLTest, get_new_free_port) { TEST(tuple_gen,clientPoolL) { CClientPool gen; gen.Create(cdSEQ_DIST, - 0x10000001, 0x10000f01, 64000,1,NULL,false, + 0x10000001, 0x10000f01, 64000,1, g_dummy, 0,0); CTupleBase result; uint32_t result_src; @@ -181,7 +197,7 @@ TEST(tuple_gen,clientPoolL) { TEST(tuple_gen,clientPool) { CClientPool gen; gen.Create(cdSEQ_DIST, - 0x10000001, 0x10000021, 64000,1000,NULL,false, + 0x10000001, 0x10000021, 64000,1000, g_dummy, 0,0); CTupleBase result; uint32_t result_src; @@ -273,7 +289,7 @@ TEST(tuple_gen,GenerateTuple2) { CClientPool c_gen; CClientPool c_gen_2; c_gen.Create(cdSEQ_DIST, - 0x10000001, 0x1000000f, 64000,4,NULL,false, + 0x10000001, 0x1000000f, 64000,4, g_dummy, 0,0); CServerPool s_gen; CServerPool s_gen_2; @@ -302,7 +318,7 @@ TEST(tuple_gen,GenerateTuple2) { c_gen.Delete(); // EXPECT_EQ((size_t)0, gen.m_clients.size()); c_gen.Create(cdSEQ_DIST, - 0x10000001, 0x1000000f, 64000,400,NULL,false, + 0x10000001, 0x1000000f, 64000,400, g_dummy, 0,0); s_gen.Create(cdSEQ_DIST, 0x30000001, 0x30000001, 64000,10); @@ -325,39 +341,6 @@ TEST(tuple_gen,GenerateTuple2) { } -TEST(tuple_gen,GenerateTupleMac) { - CFlowGenList fl; - fl.Create(); - fl.load_from_mac_file("avl/mac_uit.yaml"); - - CClientPool gen; - gen.Create(cdSEQ_DIST, - 0x10000001, 0x1000000f, 64000,2, &fl.m_mac_info,true,0,0); - - CTupleBase result; - uint32_t result_src; - uint16_t result_port; - mac_addr_align_t* result_mac; - for(int i=0;i<10;i++) { - gen.GenerateTuple(result); - printf(" C:%x P:%d \n",result.getClient(),result.getClientPort()); - - result_src = result.getClient(); - result_port = result.getClientPort(); - result_mac = result.getClientMac(); - EXPECT_EQ(result_src, (uint32_t)(0x10000001+i%2)); - EXPECT_EQ(result_port, 1024+i/2); - printf("i:%d,mac:%d\n",i,result_mac->mac[3]); - if (i%2==0) - EXPECT_EQ(result_mac->mac[3], 5); - else - EXPECT_EQ(result_mac->mac[3], 1); - } - - gen.Delete(); -// EXPECT_EQ((size_t)0, gen.m_clients.size()); -} - TEST(tuple_gen,split1) { CIpPortion portion; @@ -421,7 +404,7 @@ TEST(tuple_gen,split2) { TEST(tuple_gen,template1) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4, g_dummy, 0, 0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -446,7 +429,7 @@ TEST(tuple_gen,template1) { TEST(tuple_gen,template2) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -475,7 +458,7 @@ TEST(tuple_gen,template2) { TEST(tuple_gen,no_free) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x10000001,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x10000001,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x400000ff,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -497,7 +480,7 @@ TEST(tuple_gen,no_free) { TEST(tuple_gen,try_to_free) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x10000001,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x10000001,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x400000ff,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -524,7 +507,7 @@ TEST(tuple_gen,try_to_free) { TEST(tuple_gen_2,GenerateTuple) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x10000f01,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x10000f01,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -552,7 +535,7 @@ TEST(tuple_gen_2,GenerateTuple) { TEST(tuple_gen_2,GenerateTuple2) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -576,7 +559,7 @@ TEST(tuple_gen_2,GenerateTuple2) { gen.Delete(); // EXPECT_EQ((size_t)0, gen.m_clients.size()); gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); template_1.Create(&gen,0,0); for(int i=0;i<200;i++) { @@ -599,7 +582,7 @@ TEST(tuple_gen_2,GenerateTuple2) { TEST(tuple_gen_2,template1) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); @@ -626,7 +609,7 @@ TEST(tuple_gen_2,template1) { TEST(tuple_gen_2,template2) { CTupleGeneratorSmart gen; gen.Create(1, 1); - gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,NULL,0,0); + gen.add_client_pool(cdSEQ_DIST,0x10000001,0x1000000f,64000,4,g_dummy,0,0); gen.add_server_pool(cdSEQ_DIST,0x30000001,0x40000001,64000,4,false); CTupleTemplateGeneratorSmart template_1; template_1.Create(&gen,0,0); diff --git a/src/internal_api/trex_platform_api.h b/src/internal_api/trex_platform_api.h index a52f9e60..7037584b 100644 --- a/src/internal_api/trex_platform_api.h +++ b/src/internal_api/trex_platform_api.h @@ -28,6 +28,7 @@ limitations under the License. #include <string.h> #include "flow_stat_parser.h" #include "trex_defs.h" +#include <json/json.h> /** * Global stats @@ -148,6 +149,7 @@ public: virtual int get_flow_stats(uint8_t port_id, void *stats, void *tx_stats, int min, int max, bool reset , TrexPlatformApi::driver_stat_cap_e type) const = 0; virtual int get_rfc2544_info(void *rfc2544_info, int min, int max, bool reset) const = 0; + virtual int get_rx_err_cntrs(void *rx_err_cntrs) const = 0; virtual int reset_hw_flow_stats(uint8_t port_id) const = 0; virtual void get_port_num(uint8_t &port_num) const = 0; virtual int add_rx_flow_stat_rule(uint8_t port_id, uint8_t type, uint16_t proto, uint16_t id) const = 0; @@ -156,6 +158,8 @@ public: virtual bool get_promiscuous(uint8_t port_id) const = 0; virtual void flush_dp_messages() const = 0; virtual int get_active_pgids(flow_stat_active_t &result) const = 0; + virtual int get_cpu_util_full(cpu_util_full_t &result) const = 0; + virtual int get_mbuf_util(Json::Value &result) const = 0; virtual CFlowStatParser *get_flow_stat_parser() const = 0; virtual ~TrexPlatformApi() {} }; @@ -180,6 +184,7 @@ public: int get_flow_stats(uint8_t port_id, void *stats, void *tx_stats, int min, int max, bool reset , TrexPlatformApi::driver_stat_cap_e type) const; int get_rfc2544_info(void *rfc2544_info, int min, int max, bool reset) const; + int get_rx_err_cntrs(void *rx_err_cntrs) const; int reset_hw_flow_stats(uint8_t port_id) const; void get_port_num(uint8_t &port_num) const; int add_rx_flow_stat_rule(uint8_t port_id, uint8_t type, uint16_t proto, uint16_t id) const; @@ -188,6 +193,8 @@ public: bool get_promiscuous(uint8_t port_id) const; void flush_dp_messages() const; int get_active_pgids(flow_stat_active_t &result) const; + int get_cpu_util_full(cpu_util_full_t &result) const; + int get_mbuf_util(Json::Value &result) const; CFlowStatParser *get_flow_stat_parser() const; }; @@ -237,6 +244,7 @@ public: int get_flow_stats(uint8_t port_id, void *stats, void *tx_stats, int min, int max, bool reset , TrexPlatformApi::driver_stat_cap_e type) const {return 0;}; virtual int get_rfc2544_info(void *rfc2544_info, int min, int max, bool reset) const {return 0;}; + virtual int get_rx_err_cntrs(void *rx_err_cntrs) const {return 0;}; virtual int reset_hw_flow_stats(uint8_t port_id) const {return 0;}; virtual void get_port_num(uint8_t &port_num) const {port_num = 2;}; virtual int add_rx_flow_stat_rule(uint8_t port_id, uint8_t type, uint16_t proto, uint16_t id) const {return 0;} @@ -252,6 +260,8 @@ public: void flush_dp_messages() const { } int get_active_pgids(flow_stat_active_t &result) const {return 0;} + int get_cpu_util_full(cpu_util_full_t &result) const {return 0;} + int get_mbuf_util(Json::Value &result) const {return 0;} CFlowStatParser *get_flow_stat_parser() const {return new CFlowStatParser();} private: diff --git a/src/latency.cpp b/src/latency.cpp index 841913cf..768e161b 100644 --- a/src/latency.cpp +++ b/src/latency.cpp @@ -436,7 +436,7 @@ bool CCPortLatency::check_packet(rte_mbuf_t * m,CRx_check_header * & rx_p) { m_no_ipv4_option++; return (false); } - m_parent->get_nat_manager()->handle_packet_ipv4(lp,parser.m_ipv4); + m_parent->get_nat_manager()->handle_packet_ipv4(lp, parser.m_ipv4, true); opt_len -= CNatOption::noOPTION_LEN; opt_ptr += CNatOption::noOPTION_LEN; break; @@ -445,10 +445,11 @@ bool CCPortLatency::check_packet(rte_mbuf_t * m,CRx_check_header * & rx_p) { return (false); } // End of switch } // End of while - if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK) - && parser.IsNatInfoPkt()) { - m_parent->get_nat_manager()->handle_packet_ipv4(NULL, parser.m_ipv4); - } + + bool first; + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP) && parser.IsNatInfoPkt(first)) { + m_parent->get_nat_manager()->handle_packet_ipv4(NULL, parser.m_ipv4, first); + } return (true); } // End of check for non-latency packet @@ -910,6 +911,10 @@ void CLatencyManager::DumpShortRxCheck(FILE *fd){ } } +void CLatencyManager::dump_nat_flow_table(FILE *fd) { + m_nat_check_manager.Dump(fd); +} + void CLatencyManager::rx_check_dump_json(std::string & json){ if ( get_is_rx_check_mode() ) { m_rx_check_manager.dump_json(json ); diff --git a/src/latency.h b/src/latency.h index 724621f0..63e50337 100644 --- a/src/latency.h +++ b/src/latency.h @@ -107,19 +107,27 @@ public: } // Check if this packet contains NAT info in TCP ack - inline bool IsNatInfoPkt() { - if (!m_ipv4 || (m_protocol != IPPROTO_TCP)) { - return false; - } - if (! m_l4 || (m_l4 - rte_pktmbuf_mtod(m_m, uint8_t*) + TCP_HEADER_LEN) > m_m->data_len) { - return false; - } - // If we are here, relevant fields from tcp header are guaranteed to be in first mbuf - TCPHeader *tcp = (TCPHeader *)m_l4; - if (!tcp->getSynFlag() || (tcp->getAckNumber() == 0)) { - return false; - } - return true; + // first - set to true if this is the first packet of the flow. false otherwise. + // relevant only if return value is true + inline bool IsNatInfoPkt(bool &first) { + if (!m_ipv4 || (m_protocol != IPPROTO_TCP)) { + return false; + } + if (! m_l4 || (m_l4 - rte_pktmbuf_mtod(m_m, uint8_t*) + TCP_HEADER_LEN) > m_m->data_len) { + return false; + } + // If we are here, relevant fields from tcp header are guaranteed to be in first mbuf + // We want to handle SYN and SYN+ACK packets + TCPHeader *tcp = (TCPHeader *)m_l4; + if (! tcp->getSynFlag()) + return false; + + if (! tcp->getAckFlag()) { + first = true; + } else { + first = false; + } + return true; } public: @@ -354,6 +362,7 @@ public: void DumpRxCheck(FILE *fd); // dump all void DumpShortRxCheck(FILE *fd); // dump short histogram of latency + void dump_nat_flow_table(FILE *fd); void rx_check_dump_json(std::string & json); uint16_t get_latency_header_offset(){ return ( m_pkt_gen.get_payload_offset() ); diff --git a/src/mac_mapping.h b/src/mac_mapping.h deleted file mode 100644 index 84151e8c..00000000 --- a/src/mac_mapping.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef MAC_MAPPING_H_ -#define MAC_MAPPING_H_ - -#define INUSED 0 -#define UNUSED 1 -typedef struct mac_addr_align_ { -public: - uint8_t mac[6]; - uint8_t inused; - uint8_t pad; -} mac_addr_align_t; - -typedef struct mac_mapping_ { - mac_addr_align_t mac; - uint32_t ip; -} mac_mapping_t; - -class CFlowGenListMac { -public: - CFlowGenListMac() { - set_configured(false); - } - - std::map<uint32_t, mac_addr_align_t> & - get_mac_info () { - return m_mac_info; - } - - bool is_configured() { - return is_mac_info_configured; - } - - void set_configured(bool is_conf) { - is_mac_info_configured = is_conf; - } - - void clear() { - set_configured(false); - m_mac_info.clear(); - } - - uint32_t is_mac_exist(uint32_t ip) { - if (is_configured()) { - return m_mac_info.count(ip); - } else { - return 0; - } - } - mac_addr_align_t* get_mac_addr_by_ip(uint32_t ip) { - if (is_mac_exist(ip)!=0) { - return &(m_mac_info[ip]); - } - return NULL; - } -private: - bool is_mac_info_configured; - std::map<uint32_t, mac_addr_align_t> m_mac_info; /* global mac info loaded form mac_file*/ -}; - -#endif //MAC_MAPPING_H_ diff --git a/src/main.cpp b/src/main.cpp index 62eee880..de6cef45 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ limitations under the License. #include "bp_sim.h" #include "os_time.h" +#include "trex_client_config.h" #include <unordered_map> #include <string> @@ -35,7 +36,7 @@ using namespace std; // An enum for all the option types enum { OPT_HELP, OPT_CFG, OPT_NODE_DUMP, OP_STATS, - OPT_FILE_OUT, OPT_UT, OPT_PCAP, OPT_IPV6, OPT_MAC_FILE, + OPT_FILE_OUT, OPT_UT, OPT_PCAP, OPT_IPV6, OPT_CLIENT_CFG_FILE, OPT_SL, OPT_DP_CORE_COUNT, OPT_DP_CORE_INDEX, OPT_LIMIT, OPT_DRY_RUN}; @@ -61,22 +62,22 @@ typedef enum { */ static CSimpleOpt::SOption parser_options[] = { - { OPT_HELP, "-?", SO_NONE }, - { OPT_HELP, "-h", SO_NONE }, - { OPT_HELP, "--help", SO_NONE }, - { OPT_UT, "--ut", SO_NONE }, - { OP_STATS, "-s", SO_NONE }, - { OPT_CFG, "-f", SO_REQ_SEP }, - { OPT_MAC_FILE, "--mac", SO_REQ_SEP }, - { OPT_FILE_OUT , "-o", SO_REQ_SEP }, - { OPT_NODE_DUMP , "-v", SO_REQ_SEP }, - { OPT_PCAP, "--pcap", SO_NONE }, - { OPT_IPV6, "--ipv6", SO_NONE }, - { OPT_SL, "--sl", SO_NONE }, - { OPT_DP_CORE_COUNT, "--cores", SO_REQ_SEP }, - { OPT_DP_CORE_INDEX, "--core_index", SO_REQ_SEP }, - { OPT_LIMIT, "--limit", SO_REQ_SEP }, - { OPT_DRY_RUN, "--dry", SO_NONE }, + { OPT_HELP, "-?", SO_NONE }, + { OPT_HELP, "-h", SO_NONE }, + { OPT_HELP, "--help", SO_NONE }, + { OPT_UT, "--ut", SO_NONE }, + { OP_STATS, "-s", SO_NONE }, + { OPT_CFG, "-f", SO_REQ_SEP }, + { OPT_CLIENT_CFG_FILE, "--client_cfg", SO_REQ_SEP }, + { OPT_FILE_OUT , "-o", SO_REQ_SEP }, + { OPT_NODE_DUMP , "-v", SO_REQ_SEP }, + { OPT_PCAP, "--pcap", SO_NONE }, + { OPT_IPV6, "--ipv6", SO_NONE }, + { OPT_SL, "--sl", SO_NONE }, + { OPT_DP_CORE_COUNT, "--cores", SO_REQ_SEP }, + { OPT_DP_CORE_INDEX, "--core_index", SO_REQ_SEP }, + { OPT_LIMIT, "--limit", SO_REQ_SEP }, + { OPT_DRY_RUN, "--dry", SO_NONE }, SO_END_OF_OPTIONS @@ -159,8 +160,8 @@ static int parse_options(int argc, po->cfg_file = args.OptionArg(); break; - case OPT_MAC_FILE: - po->mac_file = args.OptionArg(); + case OPT_CLIENT_CFG_FILE: + po->client_cfg_file = args.OptionArg(); break; case OPT_FILE_OUT: diff --git a/src/main_dpdk.cpp b/src/main_dpdk.cpp index 820371ab..7bff8253 100644 --- a/src/main_dpdk.cpp +++ b/src/main_dpdk.cpp @@ -141,8 +141,8 @@ public: virtual int configure_drop_queue(CPhyEthIF * _if); virtual void get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats)=0; virtual void clear_extended_stats(CPhyEthIF * _if)=0; - virtual int wait_for_stable_link()=0; - virtual void wait_after_link_up(){}; + virtual int wait_for_stable_link(); + virtual void wait_after_link_up(); virtual bool flow_control_disable_supported(){return true;} virtual bool hw_rx_stat_supported(){return false;} virtual int get_rx_stats(CPhyEthIF * _if, uint32_t *pkts, uint32_t *prev_pkts, uint32_t *bytes, uint32_t *prev_bytes @@ -538,6 +538,7 @@ enum { OPT_HELP, OPT_ONLY_LATENCY, OPT_1G_MODE, OPT_LATENCY_PREVIEW , + OPT_WAIT_BEFORE_TRAFFIC, OPT_PCAP, OPT_RX_CHECK, OPT_IO_MODE, @@ -548,12 +549,10 @@ enum { OPT_HELP, OPT_L_PKT_MODE, OPT_NO_FLOW_CONTROL, OPT_RX_CHECK_HOPS, - OPT_MAC_FILE, + OPT_CLIENT_CFG_FILE, OPT_NO_KEYBOARD_INPUT, - OPT_VLAN, OPT_VIRT_ONE_TX_RX_QUEUE, OPT_PREFIX, - OPT_MAC_SPLIT, OPT_SEND_DEBUG_PKT, OPT_NO_WATCHDOG, OPT_ALLOW_COREDUMP, @@ -602,6 +601,7 @@ static CSimpleOpt::SOption parser_options[] = { OPT_1G_MODE, "-1g", SO_NONE }, { OPT_LATENCY_PREVIEW , "-k", SO_REQ_SEP }, + { OPT_WAIT_BEFORE_TRAFFIC , "-w", SO_REQ_SEP }, { OPT_PCAP, "--pcap", SO_NONE }, { OPT_RX_CHECK, "--rx-check", SO_REQ_SEP }, { OPT_IO_MODE, "--iom", SO_REQ_SEP }, @@ -612,12 +612,10 @@ static CSimpleOpt::SOption parser_options[] = { OPT_LEARN_VERIFY, "--learn-verify", SO_NONE }, { OPT_L_PKT_MODE, "--l-pkt-mode", SO_REQ_SEP }, { OPT_NO_FLOW_CONTROL, "--no-flow-control-change", SO_NONE }, - { OPT_VLAN, "--vlan", SO_NONE }, - { OPT_MAC_FILE, "--mac", SO_REQ_SEP }, + { OPT_CLIENT_CFG_FILE, "--client_cfg", SO_REQ_SEP }, { OPT_NO_KEYBOARD_INPUT ,"--no-key", SO_NONE }, { OPT_VIRT_ONE_TX_RX_QUEUE, "--vm-sim", SO_NONE }, { OPT_PREFIX, "--prefix", SO_REQ_SEP }, - { OPT_MAC_SPLIT, "--mac-spread", SO_REQ_SEP }, { OPT_SEND_DEBUG_PKT, "--send-debug-pkt", SO_REQ_SEP }, { OPT_MBUF_FACTOR , "--mbuf-factor", SO_REQ_SEP }, { OPT_NO_WATCHDOG , "--no-watchdog", SO_NONE }, @@ -644,7 +642,7 @@ static int usage(){ printf(" options \n\n"); - printf(" --mac [file] : YAML file with <client ip, mac addr> configuration \n"); + printf(" --client_cfg [file] : YAML file which describes clients configuration\n"); printf(" \n\n"); printf(" -c [number of threads] : default is 1. number of threads to allocate for each dual ports. \n"); printf(" \n"); @@ -682,6 +680,8 @@ static int usage(){ printf(" \n"); printf(" -k [sec] : run latency test before starting the test. it will wait for x sec sending packet and x sec after that \n"); printf(" \n"); + printf(" -w [sec] : wait between init of interfaces and sending traffic, default is 1\n"); + printf(" \n"); printf(" --cfg [platform_yaml] : load and configure platform using this file see example in cfg/cfg_examplexx.yaml file \n"); printf(" this file is used to configure/mask interfaces cores affinity and mac addr \n"); @@ -690,9 +690,10 @@ static int usage(){ printf(" --ipv6 : work in ipv6 mode\n"); printf(" --learn (deprecated). Replaced by --learn-mode. To get older behaviour, use --learn-mode 2\n"); - printf(" --learn-mode [1-2] : Work in NAT environments, learn the dynamic NAT translation and ALG \n"); - printf(" 1 Use TCP ACK in first SYN to pass NAT translation information. Will work only for TCP streams. Initial SYN packet must be present in stream.\n"); + printf(" --learn-mode [1-3] : Work in NAT environments, learn the dynamic NAT translation and ALG \n"); + printf(" 1 Use TCP ACK in first SYN to pass NAT translation information. Will work only for TCP streams. Initial SYN packet must be first packet in stream.\n"); printf(" 2 Add special IP option to pass NAT translation information. Will not work on certain firewalls if they drop packets with IP options\n"); + printf(" 3 Like 1, but without support for sequence number randomization in server->clien direction. Performance (flow/second) better than 1\n"); printf(" --learn-verify : Learn the translation, but intended for verification of the mechanism in cases that NAT does not exist \n"); printf(" \n"); printf(" --l-pkt-mode [0-3] : Set mode for sending latency packets.\n"); @@ -719,8 +720,6 @@ static int usage(){ printf(" --no-key : daemon mode, don't get input from keyboard \n"); printf(" --no-flow-control-change : By default TRex disables flow-control. If this option is given, it does not touch it\n"); printf(" --prefix : for multi trex, each instance should have a different name \n"); - printf(" --mac-spread : Spread the destination mac-order by this factor. e.g 2 will generate the traffic to 2 devices DEST-MAC ,DEST-MAC+1 \n"); - printf(" maximum is up to 128 devices \n"); printf(" --mbuf-factor : factor for packet memory \n"); printf(" \n"); printf(" --no-watchdog : disable watchdog \n"); @@ -745,7 +744,7 @@ static int usage(){ printf("\n"); printf("\n"); - printf(" Copyright (c) 2015-2015 Cisco Systems, Inc. \n"); + printf(" Copyright (c) 2015-2016 Cisco Systems, Inc. \n"); printf(" \n"); printf(" Licensed under the Apache License, Version 2.0 (the 'License') \n"); printf(" you may not use this file except in compliance with the License. \n"); @@ -830,8 +829,8 @@ static int parse_options(int argc, char *argv[], CParserOption* po, bool first_t po->preview.set_no_keyboard(true); break; - case OPT_MAC_FILE : - po->mac_file = args.OptionArg(); + case OPT_CLIENT_CFG_FILE : + po->client_cfg_file = args.OptionArg(); break; case OPT_PLAT_CFG_FILE : @@ -846,9 +845,6 @@ static int parse_options(int argc, char *argv[], CParserOption* po, bool first_t po->preview.set_ipv6_mode_enable(true); break; - case OPT_VLAN: - po->preview.set_vlan_mode_enable(true); - break; case OPT_LEARN : po->m_learn_mode = CParserOption::LEARN_MODE_IP_OPTION; @@ -954,6 +950,10 @@ static int parse_options(int argc, char *argv[], CParserOption* po, bool first_t sscanf(args.OptionArg(),"%d", &po->m_latency_prev); break; + case OPT_WAIT_BEFORE_TRAFFIC : + sscanf(args.OptionArg(),"%d", &po->m_wait_before_traffic); + break; + case OPT_PCAP: po->preview.set_pcap_mode_enable(true); break; @@ -980,13 +980,6 @@ static int parse_options(int argc, char *argv[], CParserOption* po, bool first_t po->prefix = args.OptionArg(); break; - case OPT_MAC_SPLIT: - sscanf(args.OptionArg(),"%d", &tmp_data); - po->m_mac_splitter = (uint8_t)tmp_data; - po->preview.set_mac_ip_features_enable(true); - po->preview.setDestMacSplit(true); - break; - case OPT_SEND_DEBUG_PKT: sscanf(args.OptionArg(),"%d", &tmp_data); po->m_debug_pkt_proto = (uint8_t)tmp_data; @@ -1014,17 +1007,9 @@ static int parse_options(int argc, char *argv[], CParserOption* po, bool first_t parse_err("Please provide single run mode (e.g. batch or interactive)"); } - if ( po->m_mac_splitter > 128 ){ - std::stringstream ss; - ss << "maximum mac spreading is 128 you set it to: " << po->m_mac_splitter; - parse_err(ss.str()); - } - - if ( CGlobalInfo::is_learn_mode() ){ - if ( po->preview.get_ipv6_mode_enable() ){ - parse_err("--learn mode is not supported with --ipv6, beacuse there is not such thing NAT66 ( ipv6-ipv6) \n" \ - "if you think it is important,open a defect \n"); - } + if (CGlobalInfo::is_learn_mode() && po->preview.get_ipv6_mode_enable()) { + parse_err("--learn mode is not supported with --ipv6, beacuse there is not such thing NAT66 ( ipv6-ipv6) \n" \ + "if you think it is important,open a defect \n"); } if (po->preview.get_is_rx_check_enable() || po->is_latency_enabled() || CGlobalInfo::is_learn_mode()) { @@ -1777,7 +1762,9 @@ public: virtual void flush_dp_rx_queue(void); virtual int flush_tx_queue(void); __attribute__ ((noinline)) void flush_rx_queue(); - __attribute__ ((noinline)) void update_mac_addr(CGenNode * node,uint8_t *p); + __attribute__ ((noinline)) void handle_slowpath_features(CGenNode *node, rte_mbuf_t *m, uint8_t *p, pkt_dir_t dir); + + void apply_client_cfg(const ClientCfg *cfg, rte_mbuf_t *m, pkt_dir_t dir, uint8_t *p); bool process_rx_pkt(pkt_dir_t dir,rte_mbuf_t * m); @@ -1810,6 +1797,8 @@ protected: rte_mbuf_t *m, CVirtualIFPerSideStats * lp_stats); + void add_vlan(rte_mbuf_t *m, uint16_t vlan_id); + protected: uint8_t m_core_id; uint16_t m_mbuf_cache; @@ -2034,27 +2023,6 @@ void CCoreEthIF::send_one_pkt(pkt_dir_t dir, lp_port->m_len = 0; } - -void CCoreEthIF::update_mac_addr(CGenNode * node,uint8_t *p){ - - if ( CGlobalInfo::m_options.preview.getDestMacSplit() ) { - p[5]+= (node->m_src_ip % CGlobalInfo::m_options.m_mac_splitter); - } - - if ( unlikely( CGlobalInfo::m_options.preview.get_mac_ip_mapping_enable() ) ) { - /* mac mapping file is configured - */ - if ( node->is_initiator_pkt() && (node->m_src_mac.inused==INUSED)) { - memcpy(p+6, &node->m_src_mac.mac, 6); - } - } else if ( unlikely( CGlobalInfo::m_options.preview.get_mac_ip_overide_enable() ) ){ - /* client side */ - if ( node->is_initiator_pkt() ){ - *((uint32_t*)(p+6))=PKT_NTOHL(node->m_src_ip); - } - } -} - int CCoreEthIFStateless::send_node_flow_stat(rte_mbuf *m, CGenNodeStateless * node_sl, CCorePerPort * lp_port , CVirtualIFPerSideStats * lp_stats, bool is_const) { // Defining this makes 10% percent packet loss. 1% packet reorder. @@ -2180,8 +2148,59 @@ int CCoreEthIFStateless::handle_slow_path_node(CGenNode * no) { return (-1); } +void CCoreEthIF::apply_client_cfg(const ClientCfg *cfg, rte_mbuf_t *m, pkt_dir_t dir, uint8_t *p) { + + assert(cfg); + + /* take the right direction config */ + const ClientCfgDir &cfg_dir = ( (dir == CLIENT_SIDE) ? cfg->m_initiator : cfg->m_responder); + + /* dst mac */ + if (cfg_dir.has_dst_mac_addr()) { + memcpy(p, cfg_dir.get_dst_mac_addr(), 6); + } + + /* src mac */ + if (cfg_dir.has_src_mac_addr()) { + memcpy(p + 6, cfg_dir.get_src_mac_addr(), 6); + } + + /* VLAN */ + if (cfg_dir.has_vlan()) { + add_vlan(m, cfg_dir.get_vlan()); + } +} + + +void CCoreEthIF::add_vlan(rte_mbuf_t *m, uint16_t vlan_id) { + m->ol_flags = PKT_TX_VLAN_PKT; + m->l2_len = 14; + m->vlan_tci = vlan_id; +} + +/** + * slow path features goes here (avoid multiple IFs) + * + */ +void CCoreEthIF::handle_slowpath_features(CGenNode *node, rte_mbuf_t *m, uint8_t *p, pkt_dir_t dir) { + + + /* MAC ovverride */ + if ( unlikely( CGlobalInfo::m_options.preview.get_mac_ip_overide_enable() ) ) { + /* client side */ + if ( node->is_initiator_pkt() ) { + *((uint32_t*)(p+6)) = PKT_NTOHL(node->m_src_ip); + } + } + + /* flag is faster than checking the node pointer (another cacheline) */ + if ( unlikely(CGlobalInfo::m_options.preview.get_is_client_cfg_enable() ) ) { + apply_client_cfg(node->m_client_cfg, m, dir, p); + } + +} -int CCoreEthIF::send_node(CGenNode * node){ +int CCoreEthIF::send_node(CGenNode * node) { if ( unlikely( node->get_cache_mbuf() !=NULL ) ) { pkt_dir_t dir; @@ -2199,33 +2218,30 @@ int CCoreEthIF::send_node(CGenNode * node){ rte_mbuf_t * m=lp->generate_new_mbuf(node); pkt_dir_t dir; - bool single_port; + bool single_port; - dir = node->cur_interface_dir(); + dir = node->cur_interface_dir(); single_port = node->get_is_all_flow_from_same_dir() ; + if ( unlikely( CGlobalInfo::m_options.preview.get_vlan_mode_enable() ) ){ /* which vlan to choose 0 or 1*/ uint8_t vlan_port = (node->m_src_ip &1); - - /* set the vlan */ - m->ol_flags = PKT_TX_VLAN_PKT; - m->l2_len =14; - uint16_t vlan_id = CGlobalInfo::m_options.m_vlan_port[vlan_port]; - + uint16_t vlan_id = CGlobalInfo::m_options.m_vlan_port[vlan_port]; if (likely( vlan_id >0 ) ) { - m->vlan_tci = vlan_id; dir = dir ^ vlan_port; }else{ /* both from the same dir but with VLAN0 */ - m->vlan_tci = CGlobalInfo::m_options.m_vlan_port[0]; + vlan_id = CGlobalInfo::m_options.m_vlan_port[0]; dir = dir ^ 0; } + + add_vlan(m, vlan_id); } - CCorePerPort * lp_port=&m_ports[dir]; - CVirtualIFPerSideStats * lp_stats = &m_stats[dir]; + CCorePerPort *lp_port = &m_ports[dir]; + CVirtualIFPerSideStats *lp_stats = &m_stats[dir]; if (unlikely(m==0)) { lp_stats->m_tx_alloc_error++; @@ -2233,19 +2249,17 @@ int CCoreEthIF::send_node(CGenNode * node){ } /* update mac addr dest/src 12 bytes */ - uint8_t *p=rte_pktmbuf_mtod(m, uint8_t*); - uint8_t p_id=lp_port->m_port->get_port_id(); - - + uint8_t *p = rte_pktmbuf_mtod(m, uint8_t*); + uint8_t p_id = lp_port->m_port->get_port_id(); + memcpy(p,CGlobalInfo::m_options.get_dst_src_mac_addr(p_id),12); - /* if customer enables both mac_file and get_mac_ip_overide, - * we will apply mac_file. - */ - if ( unlikely(CGlobalInfo::m_options.preview.get_mac_ip_features_enable() ) ) { - update_mac_addr(node,p); + /* when slowpath features are on */ + if ( unlikely( CGlobalInfo::m_options.preview.get_is_slowpath_features_on() ) ) { + handle_slowpath_features(node, m, p, dir); } + if ( unlikely( node->is_rx_check_enabled() ) ) { lp_stats->m_tx_rx_check_pkt++; lp->do_generate_new_mbuf_rxcheck(m, node, single_port); @@ -2446,8 +2460,10 @@ public: uint64_t m_active_sockets; uint64_t m_total_nat_time_out; + uint64_t m_total_nat_time_out_wait_ack; uint64_t m_total_nat_no_fid ; uint64_t m_total_nat_active ; + uint64_t m_total_nat_syn_wait; uint64_t m_total_nat_open ; uint64_t m_total_nat_learn_error ; @@ -2468,6 +2484,7 @@ public: float m_active_flows; float m_open_flows; float m_cpu_util; + float m_cpu_util_raw; float m_rx_cpu_util; float m_bw_per_core; uint8_t m_threads; @@ -2477,7 +2494,7 @@ public: public: void Dump(FILE *fd,DumpFormat mode); void DumpAllPorts(FILE *fd); - void dump_json(std::string & json, bool baseline,uint32_t stats_tick); + void dump_json(std::string & json, bool baseline); private: std::string get_field(std::string name,float &f); std::string get_field(std::string name,uint64_t &f); @@ -2517,7 +2534,7 @@ std::string CGlobalStats::get_field_port(int port,std::string name,uint64_t &f){ } -void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_tick){ +void CGlobalStats::dump_json(std::string & json, bool baseline){ /* refactor this to JSON */ json="{\"name\":\"trex-global\",\"type\":0,"; @@ -2535,6 +2552,7 @@ void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_ti #define GET_FIELD_PORT(p,f) get_field_port(p,std::string(#f),lp->f) json+=GET_FIELD(m_cpu_util); + json+=GET_FIELD(m_cpu_util_raw); json+=GET_FIELD(m_bw_per_core); json+=GET_FIELD(m_rx_cpu_util); json+=GET_FIELD(m_platform_factor); @@ -2564,8 +2582,10 @@ void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_ti json+=GET_FIELD(m_socket_util); json+=GET_FIELD(m_total_nat_time_out); + json+=GET_FIELD(m_total_nat_time_out_wait_ack); json+=GET_FIELD(m_total_nat_no_fid ); json+=GET_FIELD(m_total_nat_active ); + json+=GET_FIELD(m_total_nat_syn_wait); json+=GET_FIELD(m_total_nat_open ); json+=GET_FIELD(m_total_nat_learn_error); @@ -2584,10 +2604,6 @@ void CGlobalStats::dump_json(std::string & json, bool baseline,uint32_t stats_ti json+=GET_FIELD_PORT(i,m_total_rx_pps); } json+=m_template.dump_as_json("template"); - if ( stats_tick %4==0){ - json+=CGlobalInfo::dump_pool_as_json(); /* no need a feq update beacuse it trash the cores D cache, once in 2 sec */ - } - json+="\"unknown\":0}}" ; } @@ -2605,7 +2621,12 @@ void CGlobalStats::DumpAllPorts(FILE *fd){ fprintf (fd," Platform_factor : %2.1f \n",m_platform_factor); fprintf (fd," Total-Tx : %s ",double_to_human_str(m_tx_bps,"bps",KBYE_1000).c_str()); if ( CGlobalInfo::is_learn_mode() ) { - fprintf (fd," Nat_time_out : %8llu \n", (unsigned long long)m_total_nat_time_out); + fprintf (fd," NAT time out : %8llu", (unsigned long long)m_total_nat_time_out); + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + fprintf (fd," (%llu in wait for syn+ack)\n", (unsigned long long)m_total_nat_time_out_wait_ack); + } else { + fprintf (fd, "\n"); + } }else{ fprintf (fd,"\n"); } @@ -2613,28 +2634,33 @@ void CGlobalStats::DumpAllPorts(FILE *fd){ fprintf (fd," Total-Rx : %s ",double_to_human_str(m_rx_bps,"bps",KBYE_1000).c_str()); if ( CGlobalInfo::is_learn_mode() ) { - fprintf (fd," Nat_no_fid : %8llu \n", (unsigned long long)m_total_nat_no_fid); + fprintf (fd," NAT aged flow id: %8llu \n", (unsigned long long)m_total_nat_no_fid); }else{ fprintf (fd,"\n"); } fprintf (fd," Total-PPS : %s ",double_to_human_str(m_tx_pps,"pps",KBYE_1000).c_str()); if ( CGlobalInfo::is_learn_mode() ) { - fprintf (fd," Total_nat_active: %8llu \n", (unsigned long long)m_total_nat_active); + fprintf (fd," Total NAT active: %8llu", (unsigned long long)m_total_nat_active); + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + fprintf (fd," (%llu waiting for syn)\n", (unsigned long long)m_total_nat_syn_wait); + } else { + fprintf (fd, "\n"); + } }else{ fprintf (fd,"\n"); } fprintf (fd," Total-CPS : %s ",double_to_human_str(m_tx_cps,"cps",KBYE_1000).c_str()); if ( CGlobalInfo::is_learn_mode() ) { - fprintf (fd," Total_nat_open : %8llu \n", (unsigned long long)m_total_nat_open); + fprintf (fd," Total NAT opened: %8llu \n", (unsigned long long)m_total_nat_open); }else{ fprintf (fd,"\n"); } fprintf (fd,"\n"); fprintf (fd," Expected-PPS : %s ",double_to_human_str(m_tx_expected_pps,"pps",KBYE_1000).c_str()); if ( CGlobalInfo::is_learn_verify_mode() ) { - fprintf (fd," Nat_learn_errors: %8llu \n", (unsigned long long)m_total_nat_learn_error); + fprintf (fd," NAT learn errors: %8llu \n", (unsigned long long)m_total_nat_learn_error); }else{ fprintf (fd,"\n"); } @@ -3381,7 +3407,9 @@ int CGlobalTRex::ixgbe_prob_init(void){ if ( !CTRexExtendedDriverDb::Ins()->is_driver_exists(dev_info.driver_name) ){ - printf(" ERROR driver name %s is not supported \n",dev_info.driver_name); + printf(" Error: driver %s is not supported. Please consult the documentation for a list of supported drivers\n" + ,dev_info.driver_name); + exit(1); } int i; @@ -3604,6 +3632,7 @@ void CGlobalTRex::get_stats(CGlobalStats & stats){ stats.m_num_of_ports = m_max_ports; stats.m_cpu_util = m_fl.GetCpuUtil(); + stats.m_cpu_util_raw = m_fl.GetCpuUtilRaw(); if (get_is_stateless()) { stats.m_rx_cpu_util = m_rx_sl.get_cpu_util(); } @@ -3655,8 +3684,10 @@ void CGlobalTRex::get_stats(CGlobalStats & stats){ uint64_t total_nat_time_out =0; + uint64_t total_nat_time_out_wait_ack =0; uint64_t total_nat_no_fid =0; uint64_t total_nat_active =0; + uint64_t total_nat_syn_wait = 0; uint64_t total_nat_open =0; uint64_t total_nat_learn_error=0; @@ -3685,8 +3716,10 @@ void CGlobalTRex::get_stats(CGlobalStats & stats){ total_sockets += lpt->m_smart_gen.MaxSockets(); total_nat_time_out +=lpt->m_stats.m_nat_flow_timeout; + total_nat_time_out_wait_ack += lpt->m_stats.m_nat_flow_timeout_wait_ack; total_nat_no_fid +=lpt->m_stats.m_nat_lookup_no_flow_id ; total_nat_active +=lpt->m_stats.m_nat_lookup_add_flow_id - lpt->m_stats.m_nat_lookup_remove_flow_id; + total_nat_syn_wait += lpt->m_stats.m_nat_lookup_add_flow_id - lpt->m_stats.m_nat_lookup_wait_ack_state; total_nat_open +=lpt->m_stats.m_nat_lookup_add_flow_id; total_nat_learn_error +=lpt->m_stats.m_nat_flow_learn_error; uint8_t port0 = lpt->getDualPortId() *2; @@ -3708,8 +3741,10 @@ void CGlobalTRex::get_stats(CGlobalStats & stats){ } stats.m_total_nat_time_out = total_nat_time_out; + stats.m_total_nat_time_out_wait_ack = total_nat_time_out_wait_ack; stats.m_total_nat_no_fid = total_nat_no_fid; stats.m_total_nat_active = total_nat_active; + stats.m_total_nat_syn_wait = total_nat_syn_wait; stats.m_total_nat_open = total_nat_open; stats.m_total_nat_learn_error = total_nat_learn_error; @@ -3835,7 +3870,7 @@ CGlobalTRex::publish_async_data(bool sync_now, bool baseline) { get_stats(m_stats); } - m_stats.dump_json(json, baseline,m_stats_cnt); + m_stats.dump_json(json, baseline); m_zmq_publisher.publish_json(json); /* generator json , all cores are the same just sample the first one */ @@ -3929,7 +3964,7 @@ CGlobalTRex::handle_slow_path(bool &was_stopped) { if (m_io_modes.m_g_mode == CTrexGlobalIoMode::gMem) { if ( m_stats_cnt%4==0) { - fprintf (stdout," %s \n",CGlobalInfo::dump_pool_as_json().c_str()); + fprintf (stdout," %s \n",CGlobalInfo::dump_pool_as_json_str().c_str()); } } @@ -3967,9 +4002,17 @@ CGlobalTRex::handle_slow_path(bool &was_stopped) { m_mg.DumpRxCheck(stdout); break; } - } - + } + } + if ( m_io_modes.m_g_mode == CTrexGlobalIoMode::gNAT ) { + if ( m_io_modes.m_nat_mode == CTrexGlobalIoMode::natENABLE ) { + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + fprintf(stdout, "NAT flow table info\n"); + m_mg.dump_nat_flow_table(stdout); + } else { + fprintf(stdout, "\nThis is only relevant in --learn-mode %d\n", CParserOption::LEARN_MODE_TCP_ACK); + } } } @@ -4221,12 +4264,24 @@ int CGlobalTRex::start_master_statefull() { m_fl.Create(); m_fl.load_from_yaml(CGlobalInfo::m_options.cfg_file,get_cores_tx()); - if (CGlobalInfo::m_options.mac_file != "") { - CGlobalInfo::m_options.preview.set_mac_ip_mapping_enable(true); - m_fl.load_from_mac_file(CGlobalInfo::m_options.mac_file); - m_fl.m_mac_info.set_configured(true); - } else { - m_fl.m_mac_info.set_configured(false); + + /* client config */ + if (CGlobalInfo::m_options.client_cfg_file != "") { + try { + m_fl.load_client_config_file(CGlobalInfo::m_options.client_cfg_file); + } catch (const std::runtime_error &e) { + std::cout << "\n*** " << e.what() << "\n\n"; + exit(-1); + } + CGlobalInfo::m_options.preview.set_client_cfg_enable(true); + } + + /* verify options */ + try { + CGlobalInfo::m_options.verify(); + } catch (const std::runtime_error &e) { + std::cout << "\n*** " << e.what() << "\n\n"; + exit(-1); } m_expected_pps = m_fl.get_total_pps(); @@ -4860,6 +4915,18 @@ int main_test(int argc , char * argv[]){ return (0); } +void wait_x_sec(int sec) { + int i; + printf(" wait %d sec ", sec); + fflush(stdout); + for (i=0; i<sec; i++) { + delay(1000); + printf("."); + fflush(stdout); + } + printf("\n"); + fflush(stdout); +} ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// @@ -4870,32 +4937,28 @@ int CTRexExtendedDriverBase::configure_drop_queue(CPhyEthIF * _if) { return (rte_eth_dev_rx_queue_stop(port_id, 0)); } +int CTRexExtendedDriverBase::wait_for_stable_link() { + wait_x_sec(CGlobalInfo::m_options.m_wait_before_traffic); + return 0; +} + +void CTRexExtendedDriverBase::wait_after_link_up() { + wait_x_sec(CGlobalInfo::m_options.m_wait_before_traffic); +} + CFlowStatParser *CTRexExtendedDriverBase::get_flow_stat_parser() { CFlowStatParser *parser = new CFlowStatParser(); assert (parser); return parser; } -void wait_x_sec(int sec) { - int i; - printf(" wait %d sec ", sec); - fflush(stdout); - for (i=0; i<sec; i++) { - delay(1000); - printf("."); - fflush(stdout); - } - printf("\n"); - fflush(stdout); -} - // in 1G we need to wait if links became ready to soon void CTRexExtendedDriverBase1G::wait_after_link_up(){ - wait_x_sec(7); + wait_x_sec(6 + CGlobalInfo::m_options.m_wait_before_traffic); } int CTRexExtendedDriverBase1G::wait_for_stable_link(){ - wait_x_sec(10); + wait_x_sec(9 + CGlobalInfo::m_options.m_wait_before_traffic); return(0); } @@ -5277,7 +5340,7 @@ void CTRexExtendedDriverBase10G::get_extended_stats(CPhyEthIF * _if,CPhyEthIFSta } int CTRexExtendedDriverBase10G::wait_for_stable_link(){ - delay(2000); + wait_x_sec(1 + CGlobalInfo::m_options.m_wait_before_traffic); return (0); } @@ -5531,11 +5594,10 @@ void CTRexExtendedDriverBase40G::get_extended_stats(CPhyEthIF * _if,CPhyEthIFSta stats->f_ibytes = 0; - stats->ierrors = stats1.ierrors + stats1.imissed + stats1.ibadcrc + + stats->ierrors = stats1.imissed + stats1.ibadcrc + stats1.ibadlen + stats1.ierrors + stats1.oerrors + - stats1.imcasts + stats1.rx_nombuf + stats1.tx_pause_xon + stats1.rx_pause_xon + @@ -5550,7 +5612,7 @@ void CTRexExtendedDriverBase40G::get_extended_stats(CPhyEthIF * _if,CPhyEthIFSta } int CTRexExtendedDriverBase40G::wait_for_stable_link(){ - delay(2000); + wait_x_sec(1 + CGlobalInfo::m_options.m_wait_before_traffic); return (0); } @@ -5605,11 +5667,10 @@ void CTRexExtendedDriverBase1GVm::get_extended_stats(CPhyEthIF * _if,CPhyEthIFSt stats->f_ibytes = 0; - stats->ierrors = stats1.ierrors + stats1.imissed + stats1.ibadcrc + + stats->ierrors = stats1.imissed + stats1.ibadcrc + stats1.ibadlen + stats1.ierrors + stats1.oerrors + - stats1.imcasts + stats1.rx_nombuf + stats1.tx_pause_xon + stats1.rx_pause_xon + @@ -5624,7 +5685,7 @@ void CTRexExtendedDriverBase1GVm::get_extended_stats(CPhyEthIF * _if,CPhyEthIFSt } int CTRexExtendedDriverBase1GVm::wait_for_stable_link(){ - delay(10); + wait_x_sec(CGlobalInfo::m_options.m_wait_before_traffic); return (0); } @@ -5782,6 +5843,10 @@ int TrexDpdkPlatformApi::get_rfc2544_info(void *rfc2544_info, int min, int max, return g_trex.m_rx_sl.get_rfc2544_info((rfc2544_info_t *)rfc2544_info, min, max, reset); } +int TrexDpdkPlatformApi::get_rx_err_cntrs(void *rx_err_cntrs) const { + return g_trex.m_rx_sl.get_rx_err_cntrs((CRxCoreErrCntrs *)rx_err_cntrs); +} + int TrexDpdkPlatformApi::reset_hw_flow_stats(uint8_t port_id) const { return g_trex.m_ports[port_id].reset_hw_flow_stats(); } @@ -5812,6 +5877,20 @@ int TrexDpdkPlatformApi::get_active_pgids(flow_stat_active_t &result) const { return g_trex.m_trex_stateless->m_rx_flow_stat.get_active_pgids(result); } +int TrexDpdkPlatformApi::get_cpu_util_full(cpu_util_full_t &cpu_util_full) const { + cpu_util_full.resize((int)g_trex.m_fl.m_threads_info.size()); + for (int thread_id=0; thread_id<(int)g_trex.m_fl.m_threads_info.size(); thread_id++) { + CFlowGenListPerThread * lp=g_trex.m_fl.m_threads_info[thread_id]; + lp->m_cpu_cp_u.GetHistory(cpu_util_full[thread_id]); + } + return 0; +} + +int TrexDpdkPlatformApi::get_mbuf_util(Json::Value &mbuf_pool) const { + CGlobalInfo::dump_pool_as_json(mbuf_pool); + return 0; +} + CFlowStatParser *TrexDpdkPlatformApi::get_flow_stat_parser() const { return CTRexExtendedDriverDb::Ins()->get_drv()->get_flow_stat_parser(); } diff --git a/src/nat_check.cpp b/src/nat_check.cpp index 7e224430..f3dd93d1 100755 --- a/src/nat_check.cpp +++ b/src/nat_check.cpp @@ -41,7 +41,8 @@ void CGenNodeNatInfo::dump(FILE *fd){ int i; for (i=0; i<m_cnt; i++) { CNatFlowInfo * lp=&m_data[i]; - fprintf (fd," id:%d , external ip:%08x:%x , ex_port: %04x , fid: %d \n",i,lp->m_external_ip,lp->m_external_ip_server,lp->m_external_port,lp->m_fid); + fprintf (fd," id:%d , external ip:%08x , ex_port: %04x , TCP seq:%x fid: %d \n" + , i, lp->m_external_ip, lp->m_external_port, lp->m_tcp_seq, lp->m_fid); } } @@ -51,7 +52,6 @@ void CGenNodeNatInfo::init(){ m_cnt=0; } - void CNatStats::reset(){ m_total_rx=0; m_total_msg=0; @@ -147,14 +147,17 @@ void CNatRxManager::get_info_from_tcp_ack(uint32_t tcp_ack, uint32_t &fid, uint8 * option - pointer to our proprietary NAT info IP option. * If it is NULL, the NAT info is in the TCP ACK number * ipv4 - pointer to ipv4 header to extract info from. + * is_first - Is this the first packet of the flow or second. To handle firewalls that do + * TCP seq randomization on the server->client side, we also look at the second + * packet of the flow (SYN+ACK), and extract its seq num. */ -void CNatRxManager::handle_packet_ipv4(CNatOption *option, IPHeader *ipv4) { +void CNatRxManager::handle_packet_ipv4(CNatOption *option, IPHeader *ipv4, bool is_first) { CNatPerThreadInfo * thread_info; uint32_t fid=0; + uint32_t tcp_seq; /* Extract info from the packet ! */ uint32_t ext_ip = ipv4->getSourceIp(); - uint32_t ext_ip_server = ipv4->getDestIp(); uint8_t proto = ipv4->getProtocol(); /* must be TCP/UDP this is the only supported proto */ if (!( (proto==6) || (proto==17) )){ @@ -165,21 +168,48 @@ void CNatRxManager::handle_packet_ipv4(CNatOption *option, IPHeader *ipv4) { TCPHeader *tcp = (TCPHeader *) (((char *)ipv4)+ ipv4->getHeaderLength()); uint16_t ext_port = tcp->getSourcePort(); + tcp_seq = tcp->getSeqNumber(); + if (option) { thread_info = get_thread_info(option->get_thread_id()); fid = option->get_fid(); } else { - uint8_t thread_id; - get_info_from_tcp_ack(tcp->getAckNumber(), fid, thread_id); - thread_info = get_thread_info(thread_id); + uint8_t thread_id; + + if (is_first) { + uint32_t tcp_ack = tcp->getAckNumber(); + get_info_from_tcp_ack(tcp_ack, fid, thread_id); + thread_info = get_thread_info(thread_id); + if (CGlobalInfo::is_learn_mode(CParserOption::LEARN_MODE_TCP_ACK)) { + uint32_t dst_ip = ipv4->getDestIp(); + uint16_t dst_port = tcp->getDestPort(); + uint64_t map_key = (dst_ip << 16) + dst_port; + double time_stamp = now_sec(); + m_ft.insert(map_key, tcp_ack, time_stamp); + m_ft.clear_old(time_stamp - 1); + } + } else { + uint32_t val; + // server->client packet. IP/port reversed in regard to first SYN packet + uint64_t map_key = (ext_ip << 16) + ext_port; + if (m_ft.erase(map_key, val)) { + get_info_from_tcp_ack(val, fid, thread_id); + thread_info = get_thread_info(thread_id); + } else { + // flow was not found in the table + thread_info = 0; + } + } } + if (unlikely(!thread_info)) { return; } #ifdef NAT_TRACE_ - printf("rx msg ext ip : %08x:%08x ext port : %04x flow_id : %d \n",ext_ip,ext_ip_server,ext_port, fid); + printf("rx msg ext ip: %08x ext port: %04x TCP Seq: %08x flow_id : %d (%s) \n", ext_ip, ext_port, tcp_seq, fid + , is_first ? "first":"second"); #endif CGenNodeNatInfo * node=thread_info->m_cur_nat_msg; @@ -194,9 +224,13 @@ void CNatRxManager::handle_packet_ipv4(CNatOption *option, IPHeader *ipv4) { CNatFlowInfo * msg=node->get_next_msg(); /* fill the message */ - msg->m_external_ip = ext_ip; - msg->m_external_ip_server = ext_ip_server; - msg->m_external_port = ext_port; + if (is_first) { + msg->m_external_ip = ext_ip; + msg->m_external_port = ext_port; + } else { + msg->m_external_port = TCPHeader::TCP_INVALID_PORT; + } + msg->m_tcp_seq = tcp_seq; msg->m_fid = fid; msg->m_pad = 0xee; @@ -221,7 +255,8 @@ void CNatStats::Dump(FILE *fd){ void CNatRxManager::Dump(FILE *fd){ - m_stats.Dump(stdout); + m_stats.Dump(fd); + m_ft.dump(fd); } void CNatRxManager::DumpShort(FILE *fd){ diff --git a/src/nat_check.h b/src/nat_check.h index 133501ae..18add5e0 100755 --- a/src/nat_check.h +++ b/src/nat_check.h @@ -21,6 +21,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include <map> #include "msg_manager.h" #include <common/Network/Packet/TcpHeader.h> #include <common/Network/Packet/UdpHeader.h> @@ -28,6 +29,7 @@ limitations under the License. #include <common/Network/Packet/IPv6Header.h> #include <common/Network/Packet/EthernetHeader.h> #include "os_time.h" +#include "nat_check_flow_table.h" // 2msec timeout #define MAX_TIME_MSG_IN_QUEUE_SEC ( 0.002 ) @@ -121,7 +123,7 @@ private: struct CNatFlowInfo { uint32_t m_external_ip; - uint32_t m_external_ip_server; + uint32_t m_tcp_seq; uint32_t m_fid; uint16_t m_external_port; uint16_t m_pad; @@ -210,13 +212,12 @@ public: void Dump(FILE *fd); }; - class CNatRxManager { public: bool Create(); void Delete(); - void handle_packet_ipv4(CNatOption * option, IPHeader * ipv4); + void handle_packet_ipv4(CNatOption * option, IPHeader * ipv4, bool is_first); void handle_aging(); void Dump(FILE *fd); void DumpShort(FILE *fd); @@ -232,6 +233,7 @@ private: uint8_t m_max_threads; CNatPerThreadInfo * m_per_thread; CNatStats m_stats; + CNatCheckFlowTable m_ft; }; diff --git a/src/nat_check_flow_table.cpp b/src/nat_check_flow_table.cpp new file mode 100644 index 00000000..18ba8142 --- /dev/null +++ b/src/nat_check_flow_table.cpp @@ -0,0 +1,314 @@ +/* + Ido Barnea + Cisco Systems, Inc. +*/ + +/* + Copyright (c) 2016-2016 Cisco Systems, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include <assert.h> +#include <stdint.h> +#include <string> +#include <iostream> +#include <new> +#include "nat_check_flow_table.h" +/* + classes in this file: + CNatCheckFlowTableList - List implementation + CNatCheckFlowTableMap - Map implementation + CNatData - element of data that exists in the map and the list. + CNatCheckFlowTable - Wrapper class which is the interface to outside. + New element is always inserted to the list and map. + If element is removed, it is always removed from list and map together. + Map is used to lookup elemnt by key. + List is used to clean up old elements by timestamp. We guarantee the list is always sorted by timestamp, + since we always insert at the end, with increasing timestamp. +*/ + +std::ostream& operator<<(std::ostream& os, const CNatData &cn) { + os << "(" << &cn << ")" << "data:" << cn.m_data << " time:" << cn.m_timestamp; + os << " prev:" << cn.m_prev << " next:" << cn.m_next; + return os; +} + +// map implementation +CNatData *CNatCheckFlowTableMap::erase(uint64_t key) { + nat_check_flow_map_iter_no_const_t it = m_map.find(key); + + if (it != m_map.end()) { + CNatData *val = it->second; + m_map.erase(it); + return val; + } + + return NULL; +} + +bool CNatCheckFlowTableMap::find(uint64_t key, uint32_t &val) { + nat_check_flow_map_iter_t it = m_map.find(key); + + if (it != m_map.end()) { + CNatData *data = it->second; + val = data->get_data(); + return true; + } + + return false; +} + +// for testing +bool CNatCheckFlowTableMap::verify(uint32_t *arr, int size) { + uint32_t val = -1; + int real_size = 0; + + for (int i = 0; i < size; i++) { + if (arr[i] == -1) + continue; + real_size++; + find(i, val); + if (val != arr[i]) + return false; + } + + if (m_map.size() != real_size) { + std::cout << "Error: Wrong map size " << m_map.size() << ". Should be " << real_size << std::endl; + return false; + } + + return true; +} + +CNatData * CNatCheckFlowTableMap::insert(uint64_t key, uint32_t val, double time) { + CNatData *elem = new CNatData; + assert(elem); + elem->set_data(val); + elem->set_key(key); + elem->set_timestamp(time); + std::pair<uint64_t, CNatData *> pair = std::pair<uint64_t, CNatData *>(key, elem); + + if (m_map.insert(pair).second == false) { + delete elem; + return NULL; + } else { + return elem; + } +} + +std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTableMap& cf) { + nat_check_flow_map_iter_t it; + + os << "NAT check flow table map:\n"; + for (it = cf.m_map.begin(); it != cf.m_map.end(); it++) { + CNatData *data = it->second; + uint32_t key = it->first; + os << " " << key << ":" << *data << std::endl; + } + return os; +} + +void CNatCheckFlowTableList::dump_short(FILE *fd) { + fprintf(fd, "list:\n"); + // this is not fully safe, since we call it from CP core, and rx core changes the list. + // It is usefull as a debug function + if (m_head) { + fprintf(fd, " head: time:%f key:%x data:%x\n", m_head->get_timestamp(), m_head->get_key(), m_head->get_data()); + fprintf(fd, " tail: time:%f key:%x data:%x\n", m_tail->get_timestamp(), m_tail->get_key(), m_tail->get_data()); + } +} + +// list implementation +// The list is always sorted by timestamp, since we always insert at the end, with increasing timestamp +void CNatCheckFlowTableList::erase(CNatData *data) { + if (m_head == data) { + m_head = data->m_next; + } + if (m_tail == data) { + m_tail = data->m_prev; + } + if (data->m_prev) { + data->m_prev->m_next = data->m_next; + } + if (data->m_next) { + data->m_next->m_prev = data->m_prev; + } +} + +// insert as last element in list +void CNatCheckFlowTableList::insert(CNatData *data) { + data->m_prev = m_tail; + data->m_next = NULL; + + if (m_tail != NULL) { + m_tail->m_next = data; + } else { + // if m_tail is NULL m_head is also NULL + m_head = data; + } + m_tail = data; +} + +bool CNatCheckFlowTableList::verify(uint32_t *arr, int size) { + int index = -1; + CNatData *elem = m_head; + int count = 0; + + while (index < size - 1) { + index++; + if (arr[index] == -1) { + continue; + } + count++; + + if (elem->get_data() != arr[index]) { + return false; + } + + if (elem == NULL) { + std::cout << "Too few items in list. Only " << count << std::endl; + return false; + } + elem = elem->m_next; + } + + // We expect number of items in list to be like num of val!=-1 in arr + if (elem != NULL) { + std::cout << "Too many items in list" << std::endl; + return false; + } + + return true; +} + +std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTableList& cf) { + CNatData *it = cf.m_head; + + os << "NAT check flow table list:\n"; + os << " head:" << cf.m_head << " tail:" << cf.m_tail << std::endl; + while (it != NULL) { + os << " " << *it << std::endl; + it = it->m_next; + } + + return os; +} + +// flow table +std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTable& cf) { + os << "========= Flow table start =========" << std::endl; + os << cf.m_map; + os << cf.m_list; + os << "========= Flow table end =========" << std::endl; + + return os; +} + +void CNatCheckFlowTable::clear_old(double time) { + CNatData *data = m_list.get_head(); + uint32_t val; + + while (data) { + if (data->get_timestamp() < time) { + erase(data->get_key(), val); + data = m_list.get_head(); + } else { + break; + } + } +} + +void CNatCheckFlowTable::dump(FILE *fd) { + fprintf(fd, "map size:%lu\n", m_map.size()); + m_list.dump_short(fd); +} + +bool CNatCheckFlowTable::erase(uint64_t key, uint32_t &val) { + CNatData *data = m_map.erase(key); + + if (!data) + return false; + + val = data->get_data(); + m_list.erase(data); + delete data; + + return true; +} + +bool CNatCheckFlowTable::insert(uint64_t key, uint32_t val, double time) { + CNatData *res; + + res = m_map.insert(key, val, time); + if (!res) { + return false; + } else { + m_list.insert(res); + } + return true; +} + +CNatCheckFlowTable::~CNatCheckFlowTable() { + clear_old(UINT64_MAX); +} + +bool CNatCheckFlowTable::test() { + uint32_t size = 100; + uint32_t arr[size]; + int i; + uint32_t val; + + for (i = 0; i < size; i++) { + arr[i] = i+200; + } + + // insert some elements + for (i = 0; i < size; i++) { + val = arr[i]; + assert(insert(i, val, i) == true); + } + + // insert same elements. should fail + for (i = 0; i < size; i++) { + val = arr[i]; + assert(insert(i, val, val) == false); + } + + // remove element we did not insert + assert(erase(size, val) == false); + + assert (m_map.verify(arr, size) == true); + assert (m_list.verify(arr, size) == true); + + // remove even keys + for (i = 0; i < size; i += 2) { + assert(erase(i, val) == true); + assert (val == arr[i]); + arr[i] = -1; + } + + assert (m_map.verify(arr, size) == true); + assert (m_list.verify(arr, size) == true); + + // clear half of the old values (We removed the even already, so 1/4 should be left) + clear_old(size/2); + for (i = 0; i < size/2; i++) { + arr [i] = -1; + } + + assert (m_map.verify(arr, size) == true); + assert (m_list.verify(arr, size) == true); + + return true; +} diff --git a/src/nat_check_flow_table.h b/src/nat_check_flow_table.h new file mode 100644 index 00000000..dddd45b6 --- /dev/null +++ b/src/nat_check_flow_table.h @@ -0,0 +1,111 @@ +/* + Ido Barnea + Cisco Systems, Inc. +*/ + +/* + Copyright (c) 2016-2016 Cisco Systems, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#ifndef NAT_CHECK_FLOW_TABLE_H +#define NAT_CHECK_FLOW_TABLE_H +#include <map> + +class CNatData; +class CNatCheckFlowTableList; + +typedef std::map<uint64_t, CNatData *, std::less<uint64_t> > nat_check_flow_map_t; +typedef nat_check_flow_map_t::const_iterator nat_check_flow_map_iter_t; +typedef nat_check_flow_map_t::iterator nat_check_flow_map_iter_no_const_t; + +// One element of list and map +class CNatData { + friend class CNatCheckFlowTableList; // access m_next and m_prev + friend std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTableList& cf); + friend std::ostream& operator<<(std::ostream& os, const CNatData &cn); + + public: + uint32_t get_data() {return m_data;} + void set_data(uint32_t val) {m_data = val;} + uint32_t get_key() {return m_key;} + void set_key(uint32_t val) {m_key = val;} + double get_timestamp() {return m_timestamp;} + void set_timestamp(double val) {m_timestamp = val;} + CNatData() { + m_next = NULL; + m_prev = NULL; + } + + private: + double m_timestamp; + uint64_t m_key; + CNatData *m_prev; + CNatData *m_next; + uint32_t m_data; +}; + +class CNatCheckFlowTableMap { + friend class CNatCheckFlowTable; + friend std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTableMap& cf); + + private: + void clear(void) {m_map.clear();} + CNatData *erase(uint64_t key); + bool find(uint64_t key, uint32_t &val); + CNatData *insert(uint64_t key, uint32_t val, double time); + bool verify(uint32_t *arr, int size); + uint64_t size(void) {return m_map.size();} + + private: + nat_check_flow_map_t m_map; +}; + +class CNatCheckFlowTableList { + friend class CNatCheckFlowTable; + friend std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTableList& cf); + + private: + void dump_short(FILE *fd); + void erase(CNatData *data); + CNatData *get_head() {return m_head;} + void insert(CNatData *data); + bool verify(uint32_t *arr, int size); + + CNatCheckFlowTableList() { + m_head = NULL; + m_tail = NULL; + } + + private: + CNatData *m_head; + CNatData *m_tail; +}; + +class CNatCheckFlowTable { + friend std::ostream& operator<<(std::ostream& os, const CNatCheckFlowTable& cf); + + public: + ~CNatCheckFlowTable(); + void clear_old(double time); + void dump(FILE *fd); + bool erase(uint64_t key, uint32_t &val); + bool insert(uint64_t key, uint32_t val, double time); + bool test(); + + private: + CNatCheckFlowTableMap m_map; + CNatCheckFlowTableList m_list; +}; + +#endif diff --git a/src/rpc-server/commands/trex_rpc_cmd_general.cpp b/src/rpc-server/commands/trex_rpc_cmd_general.cpp index 68ea2587..27010e0e 100644 --- a/src/rpc-server/commands/trex_rpc_cmd_general.cpp +++ b/src/rpc-server/commands/trex_rpc_cmd_general.cpp @@ -157,6 +157,29 @@ TrexRpcCmdGetActivePGIds::_run(const Json::Value ¶ms, Json::Value &result) { return (TREX_RPC_CMD_OK); } +// get utilization of CPU per thread with up to 20 latest values + mbufs per socket +trex_rpc_cmd_rc_e +TrexRpcCmdGetUtilization::_run(const Json::Value ¶ms, Json::Value &result) { + cpu_util_full_t cpu_util_full; + + Json::Value §ion = result["result"]; + + if (get_stateless_obj()->get_platform_api()->get_mbuf_util(section) != 0) { + return TREX_RPC_CMD_INTERNAL_ERR; + } + + if (get_stateless_obj()->get_platform_api()->get_cpu_util_full(cpu_util_full) != 0) { + return TREX_RPC_CMD_INTERNAL_ERR; + } + + for (int thread_id = 0; thread_id < cpu_util_full.size(); thread_id++) { + for (int history_id = 0; history_id < cpu_util_full[thread_id].size(); history_id++) { + section["cpu"][thread_id].append(cpu_util_full[thread_id][history_id]); + } + } + return (TREX_RPC_CMD_OK); +} + /** * get the CPU model * diff --git a/src/rpc-server/commands/trex_rpc_cmds.h b/src/rpc-server/commands/trex_rpc_cmds.h index affa65c1..2776727d 100644 --- a/src/rpc-server/commands/trex_rpc_cmds.h +++ b/src/rpc-server/commands/trex_rpc_cmds.h @@ -67,6 +67,7 @@ TREX_RPC_CMD_DEFINE(TrexRpcPublishNow, "publish_now", 2, false, TREX_RPC_CMD_DEFINE(TrexRpcCmdGetCmds, "get_supported_cmds", 0, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetVersion, "get_version", 0, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetActivePGIds, "get_active_pgids", 0, false, APIClass::API_CLASS_TYPE_CORE); +TREX_RPC_CMD_DEFINE(TrexRpcCmdGetUtilization, "get_utilization", 0, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE_EXTENDED(TrexRpcCmdGetSysInfo, "get_system_info", 0, false, APIClass::API_CLASS_TYPE_CORE, diff --git a/src/rpc-server/trex_rpc_cmds_table.cpp b/src/rpc-server/trex_rpc_cmds_table.cpp index 7104792e..6144d265 100644 --- a/src/rpc-server/trex_rpc_cmds_table.cpp +++ b/src/rpc-server/trex_rpc_cmds_table.cpp @@ -39,6 +39,7 @@ TrexRpcCommandsTable::TrexRpcCommandsTable() { register_command(new TrexRpcCmdGetCmds()); register_command(new TrexRpcCmdGetVersion()); register_command(new TrexRpcCmdGetActivePGIds()); + register_command(new TrexRpcCmdGetUtilization()); register_command(new TrexRpcCmdGetSysInfo()); register_command(new TrexRpcCmdGetOwner()); register_command(new TrexRpcCmdAcquire()); diff --git a/src/sim/trex_sim_stateful.cpp b/src/sim/trex_sim_stateful.cpp index 88698cd1..7546644d 100644 --- a/src/sim/trex_sim_stateful.cpp +++ b/src/sim/trex_sim_stateful.cpp @@ -165,6 +165,24 @@ int load_list_of_cap_files(CParserOption * op){ CFlowGenList fl; fl.Create(); fl.load_from_yaml(op->cfg_file,1); + + if (op->client_cfg_file != "") { + try { + fl.load_client_config_file(op->client_cfg_file); + } catch (const std::runtime_error &e) { + std::cout << "\n*** " << e.what() << "\n\n"; + exit(-1); + } + CGlobalInfo::m_options.preview.set_client_cfg_enable(true); + } + + try { + CGlobalInfo::m_options.verify(); + } catch (const std::runtime_error &e) { + std::cout << "\n*** " << e.what() << "\n\n"; + exit(-1); + } + if ( op->preview.getVMode() >0 ) { fl.DumpCsv(stdout); } @@ -596,5 +614,10 @@ int merge_2_cap_files_sip() { int SimStateful::run() { assert( CMsgIns::Ins()->Create(4) ); - return load_list_of_cap_files(&CGlobalInfo::m_options); + try { + return load_list_of_cap_files(&CGlobalInfo::m_options); + } catch (const std::runtime_error &e) { + std::cout << "\n*** " << e.what() << "\n\n"; + exit(-1); + } } diff --git a/src/stateless/cp/trex_stateless.cpp b/src/stateless/cp/trex_stateless.cpp index 698ede90..8633897e 100644 --- a/src/stateless/cp/trex_stateless.cpp +++ b/src/stateless/cp/trex_stateless.cpp @@ -54,7 +54,11 @@ TrexStateless::TrexStateless(const TrexStatelessCfg &cfg) { m_publisher = cfg.m_publisher; /* API core version */ - m_api_classes[APIClass::API_CLASS_TYPE_CORE].init(APIClass::API_CLASS_TYPE_CORE, 1, 2); + const int API_VER_MAJOR = 1; + const int API_VER_MINOR = 3; + m_api_classes[APIClass::API_CLASS_TYPE_CORE].init(APIClass::API_CLASS_TYPE_CORE, + API_VER_MAJOR, + API_VER_MINOR); } /** diff --git a/src/stateless/rx/trex_stateless_rx_core.cpp b/src/stateless/rx/trex_stateless_rx_core.cpp index 0bd601b6..853fc868 100644 --- a/src/stateless/rx/trex_stateless_rx_core.cpp +++ b/src/stateless/rx/trex_stateless_rx_core.cpp @@ -206,6 +206,7 @@ void CRxCoreStateless::handle_rx_pkt(CLatencyManagerPerPortStl *lp, rte_mbuf_t * if (unlikely(fsp_head->magic != FLOW_STAT_PAYLOAD_MAGIC) || hw_id >= MAX_FLOW_STATS_PAYLOAD) { good_packet = false; + m_err_cntrs.m_bad_header++; } else { curr_rfc2544 = &m_rfc2544[hw_id]; @@ -216,6 +217,7 @@ void CRxCoreStateless::handle_rx_pkt(CLatencyManagerPerPortStl *lp, rte_mbuf_t * if (fsp_head->flow_seq == curr_rfc2544->get_prev_flow_seq()) { // packet from previous flow using this hw_id that arrived late good_packet = false; + m_err_cntrs.m_old_flow++; } else { if (curr_rfc2544->no_flow_seq()) { // first packet we see from this flow @@ -224,6 +226,7 @@ void CRxCoreStateless::handle_rx_pkt(CLatencyManagerPerPortStl *lp, rte_mbuf_t * } else { // garbage packet good_packet = false; + m_err_cntrs.m_bad_header++; } } } @@ -278,9 +281,7 @@ void CRxCoreStateless::handle_rx_pkt(CLatencyManagerPerPortStl *lp, rte_mbuf_t * } } else { hw_id = get_hw_id(ip_id); - if (hw_id >= MAX_FLOW_STATS) { - // increase some error counter - } else { + if (hw_id < MAX_FLOW_STATS) { lp->m_port.m_rx_pg_stat[hw_id].add_pkts(1); lp->m_port.m_rx_pg_stat[hw_id].add_bytes(m->pkt_len + 4); // +4 for ethernet CRC } @@ -444,6 +445,11 @@ int CRxCoreStateless::get_rfc2544_info(rfc2544_info_t *rfc2544_info, int min, in return 0; } +int CRxCoreStateless::get_rx_err_cntrs(CRxCoreErrCntrs *rx_err) { + *rx_err = m_err_cntrs; + return 0; +} + void CRxCoreStateless::set_working_msg_ack(bool val) { sanb_smp_memory_barrier(); m_ack_start_work_msg = val; diff --git a/src/stateless/rx/trex_stateless_rx_core.h b/src/stateless/rx/trex_stateless_rx_core.h index 209dc29f..fc66704e 100644 --- a/src/stateless/rx/trex_stateless_rx_core.h +++ b/src/stateless/rx/trex_stateless_rx_core.h @@ -95,6 +95,25 @@ class CRFC2544Info { uint16_t m_prev_flow_seq; }; +class CRxCoreErrCntrs { + friend CRxCoreStateless; + + public: + uint64_t get_bad_header() {return m_bad_header;} + uint64_t get_old_flow() {return m_old_flow;} + CRxCoreErrCntrs() { + reset(); + } + void reset() { + m_bad_header = 0; + m_old_flow = 0; + } + + private: + uint64_t m_bad_header; + uint64_t m_old_flow; +}; + class CRxCoreStateless { enum state_e { STATE_IDLE, @@ -109,7 +128,11 @@ class CRxCoreStateless { int get_rx_stats(uint8_t port_id, rx_per_flow_t *rx_stats, int min, int max, bool reset , TrexPlatformApi::driver_stat_cap_e type); int get_rfc2544_info(rfc2544_info_t *rfc2544_info, int min, int max, bool reset); - void work() {m_state = STATE_WORKING;} + int get_rx_err_cntrs(CRxCoreErrCntrs *rx_err); + void work() { + m_state = STATE_WORKING; + m_err_cntrs.reset(); // When starting to work, reset global counters + } void idle() {m_state = STATE_IDLE;} void quit() {m_state = STATE_QUIT;} bool is_working() const {return (m_ack_start_work_msg == true);} @@ -146,6 +169,7 @@ class CRxCoreStateless { CCpuUtlCp m_cpu_cp_u; // Used for acking "work" (go out of idle) messages from cp volatile bool m_ack_start_work_msg __rte_cache_aligned; + CRxCoreErrCntrs m_err_cntrs; CRFC2544Info m_rfc2544[MAX_FLOW_STATS_PAYLOAD]; }; #endif diff --git a/src/trex_client_config.cpp b/src/trex_client_config.cpp new file mode 100644 index 00000000..b56037ea --- /dev/null +++ b/src/trex_client_config.cpp @@ -0,0 +1,235 @@ +/* + Itay Marom + Cisco Systems, Inc. +*/ + +/* +Copyright (c) 2016 Cisco Systems, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include <stdexcept> +#include <sstream> +#include <fstream> + +#include <yaml-cpp/yaml.h> + +#include "utl_yaml.h" +#include "trex_client_config.h" +#include "common/basic_utils.h" +#include "bp_sim.h" + +void +ClientCfgEntry::dump() const { + + std::cout << "IP start: " << ip_to_str(m_ip_start) << "\n"; + std::cout << "IP end: " << ip_to_str(m_ip_end) << "\n"; + + //m_cfg.dump(); + + #if 0 + std::cout << "Init. MAC addr: "; + for (int i = 0; i < 6; i++) { + printf("%lx:", ( (m_initiator.m_dst_mac >> ( (6-i) * 8)) & 0xFF ) ); + } + std::cout << "\n"; + + std::cout << "Init. VLAN: " << m_initiator.m_vlan << "\n"; + + std::cout << "Res. MAC addr: "; + for (int i = 0; i < 6; i++) { + printf("%lx:", ( (m_responder.m_dst_mac >> ( (6-i) * 8)) & 0xFF ) ); + } + std::cout << "\n"; + + std::cout << "Res. VLAN: " << m_responder.m_vlan << "\n"; + #endif +} + + +/** + * loads a YAML file containing + * the client groups configuration + * + */ +void +ClientCfgDB::load_yaml_file(const std::string &filename) { + std::stringstream ss; + + m_groups.clear(); + m_cache_group = NULL; + + /* wrapper parser */ + YAML::Node root; + YAMLParserWrapper parser(filename); + parser.load(root); + + /* parse globals */ + m_under_vlan = parser.parse_bool(root, "vlan"); + + const YAML::Node &groups = parser.parse_list(root, "groups"); + + /* parse each group */ + for (int i = 0; i < groups.size(); i++) { + parse_single_group(parser, groups[i]); + } + + verify(parser); + + m_is_empty = false; + +} + +/** + * reads a single group of clients from YAML + * + */ +void +ClientCfgDB::parse_single_group(YAMLParserWrapper &parser, const YAML::Node &node) { + ClientCfgEntry group; + + /* ip_start */ + group.m_ip_start = parser.parse_ip(node, "ip_start"); + + /* ip_end */ + group.m_ip_end = parser.parse_ip(node, "ip_end"); + + /* sanity check */ + if (group.m_ip_end < group.m_ip_start) { + parser.parse_err("ip_end must be >= ip_start", node); + } + + const YAML::Node &init = parser.parse_map(node, "initiator"); + const YAML::Node &resp = parser.parse_map(node, "responder"); + + parse_dir(parser, init, group.m_cfg.m_initiator); + parse_dir(parser, resp, group.m_cfg.m_responder); + + + group.m_count = parser.parse_uint(node, "count", 0, UINT64_MAX, 1); + + /* add to map with copying */ + m_groups[group.m_ip_start] = group; + +} + +void +ClientCfgDB::parse_dir(YAMLParserWrapper &parser, const YAML::Node &node, ClientCfgDir &dir) { + if (node.FindValue("src_mac")) { + dir.set_dst_mac_addr(parser.parse_mac_addr(node, "src_mac")); + } + + if (node.FindValue("dst_mac")) { + dir.set_dst_mac_addr(parser.parse_mac_addr(node, "dst_mac")); + } + + if (m_under_vlan) { + dir.set_vlan(parser.parse_uint(node, "vlan", 0, 0xfff)); + } else { + if (node.FindValue("vlan")) { + parser.parse_err("VLAN config was disabled", node["vlan"]); + } + } +} + +/** + * sanity checks + * + * @author imarom (28-Jun-16) + */ +void +ClientCfgDB::verify(const YAMLParserWrapper &parser) const { + std::stringstream ss; + uint32_t monotonic = 0; + + /* check that no interval overlaps */ + + /* all intervals do not overloap iff when sorted each start/end dots are strong monotonic */ + for (const auto &p : m_groups) { + const ClientCfgEntry &group = p.second; + + if ( (monotonic > 0 ) && (group.m_ip_start <= monotonic) ) { + ss << "IP '" << ip_to_str(group.m_ip_start) << "' - '" << ip_to_str(group.m_ip_end) << "' overlaps with other groups"; + parser.parse_err(ss.str()); + } + + monotonic = group.m_ip_end; + } +} + +/** + * lookup function + * should be fast + * + */ +ClientCfgEntry * +ClientCfgDB::lookup(uint32_t ip) { + + /* a cache to avoid constant search (usually its a range of IPs) */ + if ( (m_cache_group) && (m_cache_group->contains(ip)) ) { + return m_cache_group; + } + + /* clear the cache pointer */ + m_cache_group = NULL; + + std::map<uint32_t ,ClientCfgEntry>::iterator it; + + /* upper bound fetchs the first greater element */ + it = m_groups.upper_bound(ip); + + /* if the first element in the map is bigger - its not in the map */ + if (it == m_groups.begin()) { + return NULL; + } + + /* go one back - we know it's not on begin so we have at least one back */ + it--; + + ClientCfgEntry &group = (*it).second; + + /* because this is a non overlapping intervals + if IP exists it must be in this group + because if it exists in some other previous group + its end_range >= ip so start_range > ip + and so start_range is the upper_bound + */ + if (group.contains(ip)) { + /* save as cache */ + m_cache_group = &group; + return &group; + } else { + return NULL; + } + +} + +/** + * for convenience - search by IP as string + * + * @author imarom (28-Jun-16) + * + * @param ip + * + * @return ClientCfgEntry* + */ +ClientCfgEntry * +ClientCfgDB::lookup(const std::string &ip) { + uint32_t addr = (uint32_t)inet_addr(ip.c_str()); + addr = PKT_NTOHL(addr); + + return lookup(addr); +} + + diff --git a/src/trex_client_config.h b/src/trex_client_config.h new file mode 100644 index 00000000..a5bb83b3 --- /dev/null +++ b/src/trex_client_config.h @@ -0,0 +1,265 @@ +/* + Itay Marom + Cisco Systems, Inc. +*/ + +/* +Copyright (c) 2016 Cisco Systems, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#ifndef __TREX_CLIENT_CONFIG_H__ +#define __TREX_CLIENT_CONFIG_H__ + +#include <stdint.h> +#include <string> +#include <map> + +class YAMLParserWrapper; + + +/** + * client configuration per direction + * + * @author imarom (29-Jun-16) + */ +class ClientCfgDir { + +private: + enum { + HAS_SRC_MAC = 0x1, + HAS_DST_MAC = 0x2, + HAS_VLAN = 0x4, + }; + + uint8_t m_src_mac[6]; + uint8_t m_dst_mac[6]; + uint16_t m_vlan; + uint8_t m_bitfield; + + +public: + ClientCfgDir() { + m_bitfield = 0; + } + + bool has_src_mac_addr() const { + return (m_bitfield & HAS_SRC_MAC); + } + + bool has_dst_mac_addr() const { + return (m_bitfield & HAS_DST_MAC); + } + bool has_vlan() const { + return (m_bitfield & HAS_VLAN); + } + + void set_src_mac_addr(uint64_t mac_addr) { + for (int i = 0; i < 6; i++) { + m_src_mac[i] = ( mac_addr >> ((5 - i) * 8) ) & 0xFF; + } + m_bitfield |= HAS_SRC_MAC; + } + + void set_dst_mac_addr(uint64_t mac_addr) { + for (int i = 0; i < 6; i++) { + m_dst_mac[i] = ( mac_addr >> ((5 - i) * 8) ) & 0xFF; + } + m_bitfield |= HAS_DST_MAC; + } + + void set_vlan(uint16_t vlan_id) { + m_vlan = vlan_id; + m_bitfield |= HAS_VLAN; + } + + /* updates a configuration with a group index member */ + + void update(uint32_t index) { + if (has_src_mac_addr()) { + mac_add(m_src_mac, index); + } + + if (has_dst_mac_addr()) { + mac_add(m_dst_mac, index); + } + } + + const uint8_t *get_src_mac_addr() const { + assert(has_src_mac_addr()); + return m_src_mac; + } + + const uint8_t *get_dst_mac_addr() const { + assert(has_dst_mac_addr()); + return m_dst_mac; + } + + uint16_t get_vlan() const { + assert(has_vlan()); + return m_vlan; + } + +private: + /** + * transform MAC address to uint64_t + * performs add and return to MAC format + * + */ + void mac_add(uint8_t *mac, uint32_t i) { + uint64_t tmp = 0; + + for (int i = 0; i < 6; i++) { + tmp <<= 8; + tmp |= mac[i]; + } + + tmp += i; + + for (int i = 0; i < 6; i++) { + mac[i] = ( tmp >> ((5 - i) * 8) ) & 0xFF; + } + + } +}; + +/** + * single client config + * + */ +class ClientCfg { + +public: + + void update(uint32_t index) { + m_initiator.update(index); + m_responder.update(index); + } + + ClientCfgDir m_initiator; + ClientCfgDir m_responder; +}; + +/******************************** internal section ********************************/ + +/** + * describes a single client config + * entry loaded from the config file + * + */ +class ClientCfgEntry { + +public: + + ClientCfgEntry() { + reset(); + } + + + void dump() const; + + bool contains(uint32_t ip) const { + return ( (ip >= m_ip_start) && (ip <= m_ip_end) ); + } + + void reset() { + m_iterator = 0; + } + + + /** + * assings a client config from the group + * it will advance MAC addresses andf etc. + * + * @author imarom (27-Jun-16) + * + * @param info + */ + void assign(ClientCfg &info) { + info = m_cfg; + info.update(m_iterator); + + /* advance for the next assign */ + m_iterator = (m_iterator + 1) % m_count; + } + +public: + uint32_t m_ip_start; + uint32_t m_ip_end; + + ClientCfg m_cfg; + + uint32_t m_count; + +private: + uint32_t m_iterator; +}; + +/** + * holds all the configured clients groups + * + */ +class ClientCfgDB { +public: + + ClientCfgDB() { + m_is_empty = true; + m_cache_group = NULL; + m_under_vlan = false; + } + + /** + * if no config file was loaded + * this should return true + * + */ + bool is_empty() { + return m_is_empty; + } + + /** + * loads a YAML file + * configuration will be built + * according to the YAML config + * + */ + void load_yaml_file(const std::string &filename); + + /** + * lookup for a specific IP address for + * a group that contains this IP + * + */ + ClientCfgEntry * lookup(uint32_t ip); + ClientCfgEntry * lookup(const std::string &ip); + +private: + void parse_single_group(YAMLParserWrapper &parser, const YAML::Node &node); + void parse_dir(YAMLParserWrapper &parser, const YAML::Node &node, ClientCfgDir &dir); + + /** + * verify the YAML file loaded in valid + * + */ + void verify(const YAMLParserWrapper &parser) const; + + /* maps the IP start value to client groups */ + std::map<uint32_t, ClientCfgEntry> m_groups; + bool m_under_vlan; + + ClientCfgEntry *m_cache_group; + bool m_is_empty; +}; + +#endif /* __TREX_CLIENT_CONFIG_H__ */ + diff --git a/src/trex_defs.h b/src/trex_defs.h index 665c1edc..9abb38f5 100644 --- a/src/trex_defs.h +++ b/src/trex_defs.h @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ #include <set> +#include <queue> +#include <vector> #ifndef __TREX_DEFS_H__ #define __TREX_DEFS_H__ @@ -33,8 +35,13 @@ limitations under the License. #define UINT16_MAX 0xFFFF #endif +#ifndef UINT64_MAX + #define UINT64_MAX 0xFFFFFFFFFFFFFFFF +#endif typedef std::set<uint32_t> flow_stat_active_t; typedef std::set<uint32_t>::iterator flow_stat_active_it_t; +typedef std::vector<std::vector<uint8_t>> cpu_util_full_t; +typedef std::vector<uint8_t> cpu_vct_t; #endif diff --git a/src/trex_watchdog.cpp b/src/trex_watchdog.cpp index d099933b..88711b7a 100644 --- a/src/trex_watchdog.cpp +++ b/src/trex_watchdog.cpp @@ -92,7 +92,7 @@ std::string Backtrace(int skip = 1) /* add the addr2line info */ std::stringstream addr2line; - addr2line << "/usr/bin/addr2line -e " << get_exe_name() << " "; + addr2line << "/usr/bin/addr2line -s -e " << get_exe_name() << " "; for (int i = skip; i < nFrames; i++) { addr2line << callstack[i] << " "; } diff --git a/src/trex_watchdog.h b/src/trex_watchdog.h index 1c948d56..af9a3993 100644 --- a/src/trex_watchdog.h +++ b/src/trex_watchdog.h @@ -117,15 +117,12 @@ private: /* write fields are first */ volatile bool m_active; volatile bool m_tickled; - dsec_t m_ts; - int m_handle; + dsec_t m_ts; double m_timeout_sec; pthread_t m_tid; std::string m_name; - /* for for a full cacheline */ - uint8_t pad[15]; } __rte_cache_aligned; @@ -203,6 +200,5 @@ private: static bool g_signal_init; }; -static_assert(sizeof(TrexMonitor) == RTE_CACHE_LINE_SIZE, "sizeof(TrexMonitor) != RTE_CACHE_LINE_SIZE" ); #endif /* __TREX_WATCHDOG_H__ */ diff --git a/src/tuple_gen.cpp b/src/tuple_gen.cpp index d221a4d9..6861b73f 100755 --- a/src/tuple_gen.cpp +++ b/src/tuple_gen.cpp @@ -25,6 +25,7 @@ limitations under the License. #include "tuple_gen.h" #include <string.h> #include "utl_yaml.h" +#include "bp_sim.h" void CServerPool::Create(IP_DIST_t dist_value, uint32_t min_ip, @@ -52,97 +53,117 @@ void CServerPool::Create(IP_DIST_t dist_value, -void CClientPool::Create(IP_DIST_t dist_value, - uint32_t min_ip, - uint32_t max_ip, - double l_flow, - double t_cps, - CFlowGenListMac* mac_info, - bool has_mac_map, - uint16_t tcp_aging, - uint16_t udp_aging) { - assert(max_ip>=min_ip); +void CClientPool::Create(IP_DIST_t dist_value, + uint32_t min_ip, + uint32_t max_ip, + double l_flow, + double t_cps, + ClientCfgDB &client_info, + uint16_t tcp_aging, + uint16_t udp_aging) { + + assert(max_ip >= min_ip); + set_dist(dist_value); - uint32_t total_ip = max_ip - min_ip +1; - uint32_t avail_ip = total_ip; - if (has_mac_map && (mac_info!=NULL)) { - for(int idx=0;idx<total_ip;idx++){ - mac_addr_align_t *mac_adr = NULL; - mac_adr = mac_info->get_mac_addr_by_ip(min_ip+idx); - if (mac_adr == NULL) { - avail_ip--; - } - } - } - if (avail_ip!=0) { - m_ip_info.resize(avail_ip); + + uint32_t total_ip = max_ip - min_ip +1; + bool is_long_range = total_ip > (l_flow * t_cps / MAX_PORT); + + m_ip_info.resize(total_ip); + + /* if client info is empty - flat allocation o.w use configured clients */ + if (client_info.is_empty()) { + allocate_simple_clients(min_ip, total_ip, is_long_range); } else { - printf("\n Error, invalid mac file is configured.\n"); - assert(0); + allocate_configured_clients(min_ip, total_ip, is_long_range, client_info); } - int skip_cnt=0; - if (total_ip > ((l_flow*t_cps/MAX_PORT))) { - if (has_mac_map) { - skip_cnt=0; - for(int idx=0;idx<total_ip;idx++){ - mac_addr_align_t *mac_adr = NULL; - mac_adr = mac_info->get_mac_addr_by_ip( min_ip+idx); - if (mac_adr != NULL) { - m_ip_info[idx-skip_cnt] = new CClientInfoL(has_mac_map); - m_ip_info[idx-skip_cnt]->set_ip(min_ip+idx); - m_ip_info[idx-skip_cnt]->set_mac(mac_adr); - } else { - skip_cnt++; - } - } + m_tcp_aging = tcp_aging; + m_udp_aging = udp_aging; + + CreateBase(); +} + +/** + * simple allocation of a client - no configuration was provided + * + * @author imarom (27-Jun-16) + * + * @param ip + * @param index + * @param is_long_range + */ +void CClientPool::allocate_simple_clients(uint32_t min_ip, + uint32_t total_ip, + bool is_long_range) { + + /* simple creation of clients - no extended info */ + for (uint32_t i = 0; i < total_ip; i++) { + uint32_t ip = min_ip + i; + if (is_long_range) { + m_ip_info[i] = new CSimpleClientInfo<CIpInfoL>(ip); } else { - for(int idx=0;idx<total_ip;idx++){ - m_ip_info[idx] = new CClientInfoL(has_mac_map); - m_ip_info[idx]->set_ip(min_ip+idx); - } - } - } else { - if (has_mac_map) { - skip_cnt=0; - for(int idx=0;idx<total_ip;idx++){ - mac_addr_align_t *mac_adr = NULL; - mac_adr = mac_info->get_mac_addr_by_ip(min_ip+idx); - if (mac_adr != NULL) { - m_ip_info[idx-skip_cnt] = new CClientInfo(has_mac_map); - m_ip_info[idx-skip_cnt]->set_ip(min_ip+idx); - m_ip_info[idx-skip_cnt]->set_mac(mac_adr); - } else { - skip_cnt++; - } - } + m_ip_info[i] = new CSimpleClientInfo<CIpInfo>(ip); + } + } + +} + +/** + * simple allocation of a client - no configuration was provided + * + * @author imarom (27-Jun-16) + * + * @param ip + * @param index + * @param is_long_range + */ +void CClientPool::allocate_configured_clients(uint32_t min_ip, + uint32_t total_ip, + bool is_long_range, + ClientCfgDB &client_info) { + + for (uint32_t i = 0; i < total_ip; i++) { + uint32_t ip = min_ip + i; + + /* lookup for the right group of clients */ + ClientCfgEntry *group = client_info.lookup(ip); + if (!group) { + std::stringstream ss; + ss << "client configuration error: could not map IP '" << ip_to_str(ip) << "' to a group\n"; + throw std::runtime_error(ss.str()); + } + + ClientCfg info; + group->assign(info); + + if (is_long_range) { + m_ip_info[i] = new CConfiguredClientInfo<CIpInfoL>(ip, info); } else { - for(int idx=0;idx<total_ip;idx++){ - m_ip_info[idx] = new CClientInfo(has_mac_map); - m_ip_info[idx]->set_ip(min_ip+idx); - } - } - + m_ip_info[i] = new CConfiguredClientInfo<CIpInfo>(ip, info); + } } - m_tcp_aging = tcp_aging; - m_udp_aging = udp_aging; - CreateBase(); } -bool CTupleGeneratorSmart::add_client_pool(IP_DIST_t client_dist, - uint32_t min_client, - uint32_t max_client, - double l_flow, - double t_cps, - CFlowGenListMac* mac_info, - uint16_t tcp_aging, - uint16_t udp_aging){ +bool CTupleGeneratorSmart::add_client_pool(IP_DIST_t client_dist, + uint32_t min_client, + uint32_t max_client, + double l_flow, + double t_cps, + ClientCfgDB &client_info, + uint16_t tcp_aging, + uint16_t udp_aging) { assert(max_client>=min_client); CClientPool* pool = new CClientPool(); - pool->Create(client_dist, min_client, max_client, - l_flow, t_cps, mac_info, m_has_mac_mapping, - tcp_aging, udp_aging); + pool->Create(client_dist, + min_client, + max_client, + l_flow, + t_cps, + client_info, + tcp_aging, + udp_aging); m_client_pool.push_back(pool); return(true); @@ -170,19 +191,17 @@ bool CTupleGeneratorSmart::add_server_pool(IP_DIST_t server_dist, bool CTupleGeneratorSmart::Create(uint32_t _id, - uint32_t thread_id, - bool has_mac) + uint32_t thread_id) + { m_thread_id = thread_id; m_id = _id; m_was_init=true; - m_has_mac_mapping = has_mac; return(true); } void CTupleGeneratorSmart::Delete(){ m_was_init=false; - m_has_mac_mapping = false; for (int idx=0;idx<m_client_pool.size();idx++) { m_client_pool[idx]->Delete(); diff --git a/src/tuple_gen.h b/src/tuple_gen.h index b2f6e34a..2491f489 100755 --- a/src/tuple_gen.h +++ b/src/tuple_gen.h @@ -37,15 +37,17 @@ limitations under the License. #include "common/c_common.h" #include <bitset> #include <yaml-cpp/yaml.h> -#include <mac_mapping.h> +#include "trex_client_config.h" #include <random> class CTupleBase { public: + CTupleBase() { - m_client_mac.inused = UNUSED; + m_client_cfg = NULL; } + uint32_t getClient() { return m_client_ip; } @@ -83,22 +85,20 @@ public: void setClientPort(uint16_t port) { m_client_port = port; } - mac_addr_align_t* getClientMac() { - return &m_client_mac; + void setClientCfg(ClientCfg *cfg) { + m_client_cfg = cfg; } - void setClientMac(mac_addr_align_t* mac_info) { - if (mac_info != NULL) { - memcpy(&m_client_mac, mac_info, sizeof(mac_addr_align_t)); - m_client_mac.inused = INUSED; - } else { - m_client_mac.inused = UNUSED; - } + ClientCfg *getClientCfg() { + return m_client_cfg; } - void setClientTuple(uint32_t ip,mac_addr_align_t*mac,uint16_t port) { + + + void setClientTuple(uint32_t ip, ClientCfg *cfg, uint16_t port) { setClient(ip); - setClientMac(mac); setClientPort(port); + setClientCfg(cfg); } + void setClientAll2(uint32_t id, uint32_t ip,uint16_t port) { setClientId(id); setClient(ip); @@ -121,9 +121,12 @@ public: private: uint32_t m_client_ip; uint32_t m_client_idx; + uint32_t m_server_ip; uint32_t m_server_idx; - mac_addr_align_t m_client_mac; + + ClientCfg *m_client_cfg; + uint16_t m_client_port; uint16_t m_server_port; }; @@ -171,8 +174,6 @@ typedef enum { class CIpInfoBase { public: - virtual mac_addr_align_t* get_mac() { return NULL;} - virtual void set_mac(mac_addr_align_t*){;} virtual uint16_t get_new_free_port() = 0; virtual void return_port(uint16_t a) = 0; virtual void generate_tuple(CTupleBase & tuple) = 0; @@ -303,76 +304,54 @@ class CIpInfo : public CIpInfoBase { } }; -class CClientInfo : public CIpInfo { + +/** + * a flat client info (no configuration) + * + * using template to avoid duplicating the code for CIpInfo and + * CIpInfoL + * + * @author imarom (27-Jun-16) + */ +template <typename T> +class CSimpleClientInfo : public T { + public: - CClientInfo (bool has_mac) { - if (has_mac==true) { - m_mac = new mac_addr_align_t(); - } else { - m_mac = NULL; - } - } - CClientInfo () { - m_mac = NULL; - } + CSimpleClientInfo(uint32_t ip) { + T::set_ip(ip); + } - mac_addr_align_t* get_mac() { - return m_mac; - } - void set_mac(mac_addr_align_t *mac) { - memcpy(m_mac, mac, sizeof(mac_addr_align_t)); - } - ~CClientInfo() { - if (m_mac!=NULL){ - delete m_mac; - m_mac=NULL; - } - } - - void generate_tuple(CTupleBase & tuple) { - tuple.setClientTuple(m_ip, m_mac, - get_new_free_port()); + void generate_tuple(CTupleBase &tuple) { + tuple.setClientTuple(T::m_ip, + NULL, + T::get_new_free_port()); } -private: - mac_addr_align_t *m_mac; }; -class CClientInfoL : public CIpInfoL { -public: - CClientInfoL (bool has_mac) { - if (has_mac==true) { - m_mac = new mac_addr_align_t(); - } else { - m_mac = NULL; - } - } - CClientInfoL () { - m_mac = NULL; - } +/** + * a configured client object + * + * @author imarom (26-Jun-16) + */ +template <typename T> +class CConfiguredClientInfo : public T { - mac_addr_align_t* get_mac() { - return m_mac; +public: + CConfiguredClientInfo(uint32_t ip, const ClientCfg &cfg) : m_cfg(cfg) { + T::set_ip(ip); } - void set_mac(mac_addr_align_t *mac) { - memcpy(m_mac, mac, sizeof(mac_addr_align_t)); + void generate_tuple(CTupleBase &tuple) { + tuple.setClientTuple(T::m_ip, + &m_cfg, + T::get_new_free_port()); } - ~CClientInfoL() { - if (m_mac!=NULL){ - delete m_mac; - m_mac=NULL; - } - } - - void generate_tuple(CTupleBase & tuple) { - tuple.setClientTuple(m_ip, m_mac, - get_new_free_port()); - } private: - mac_addr_align_t *m_mac; + ClientCfg m_cfg; }; + class CServerInfo : public CIpInfo { void generate_tuple(CTupleBase & tuple) { tuple.setServer(m_ip); @@ -475,12 +454,6 @@ class CIpPool { client->return_port(port); } - mac_addr_align_t * get_curr_mac() { - return m_ip_info[m_cur_idx]->get_mac(); - } - mac_addr_align_t *get_mac(uint32_t idx) { - return m_ip_info[idx]->get_mac(); - } public: std::vector<CIpInfoBase*> m_ip_info; @@ -536,19 +509,30 @@ public: uint16_t get_udp_aging() { return m_udp_aging; } - void Create(IP_DIST_t dist_value, - uint32_t min_ip, - uint32_t max_ip, - double l_flow, - double t_cps, - CFlowGenListMac* mac_info, - bool has_mac_map, - uint16_t tcp_aging, - uint16_t udp_aging); + + void Create(IP_DIST_t dist_value, + uint32_t min_ip, + uint32_t max_ip, + double l_flow, + double t_cps, + ClientCfgDB &client_info, + uint16_t tcp_aging, + uint16_t udp_aging); public: uint16_t m_tcp_aging; uint16_t m_udp_aging; + +private: + void allocate_simple_clients(uint32_t min_ip, + uint32_t total_ip, + bool is_long_range); + + void allocate_configured_clients(uint32_t min_ip, + uint32_t total_ip, + bool is_long_range, + ClientCfgDB &client_info); + }; class CServerPoolBase { @@ -685,10 +669,9 @@ public: public: CTupleGeneratorSmart(){ m_was_init=false; - m_has_mac_mapping = false; } - bool Create(uint32_t _id, - uint32_t thread_id, bool has_mac=false); + + bool Create(uint32_t _id, uint32_t thread_id); void Delete(); @@ -704,20 +687,22 @@ public: return (total_alloc_error); } - bool add_client_pool(IP_DIST_t client_dist, - uint32_t min_client, - uint32_t max_client, - double l_flow, - double t_cps, - CFlowGenListMac* mac_info, - uint16_t tcp_aging, - uint16_t udp_aging); + bool add_client_pool(IP_DIST_t client_dist, + uint32_t min_client, + uint32_t max_client, + double l_flow, + double t_cps, + ClientCfgDB &client_info, + uint16_t tcp_aging, + uint16_t udp_aging); + bool add_server_pool(IP_DIST_t server_dist, - uint32_t min_server, - uint32_t max_server, - double l_flow, - double t_cps, - bool is_bundling); + uint32_t min_server, + uint32_t max_server, + double l_flow, + double t_cps, + bool is_bundling); + CClientPool* get_client_pool(uint8_t idx) { return m_client_pool[idx]; } @@ -736,7 +721,6 @@ private: std::vector<CClientPool*> m_client_pool; std::vector<CServerPoolBase*> m_server_pool; bool m_was_init; - bool m_has_mac_mapping; }; class CTupleTemplateGeneratorSmart { diff --git a/src/utl_cpuu.cpp b/src/utl_cpuu.cpp index 4d75cf6f..7786356e 100755 --- a/src/utl_cpuu.cpp +++ b/src/utl_cpuu.cpp @@ -25,7 +25,9 @@ limitations under the License. void CCpuUtlCp::Create(CCpuUtlDp * cdp){ m_dpcpu=cdp; - m_cpu_util=0.0; + memset(m_cpu_util, 0, sizeof(m_cpu_util)); + m_history_latest_index = 0; + m_cpu_util_lpf=0.0; m_ticks=0; m_work=0; } @@ -41,17 +43,34 @@ void CCpuUtlCp::Update(){ m_work++; } if (m_ticks==100) { - double window_cpu_u = ((double)m_work/(double)m_ticks); /* LPF*/ - m_cpu_util = (m_cpu_util*0.75)+(window_cpu_u*0.25); + m_cpu_util_lpf = (m_cpu_util_lpf*0.75)+((double)m_work*0.25); + AppendHistory(m_work); m_ticks=0; m_work=0; - } } -/* return cpu % */ +/* return cpu % Smoothed */ double CCpuUtlCp::GetVal(){ - return (m_cpu_util*100); + return (m_cpu_util_lpf); +} + +/* return cpu % Raw */ +uint8_t CCpuUtlCp::GetValRaw(){ + return (m_cpu_util[m_history_latest_index]); } +/* get cpu % utilization history */ +void CCpuUtlCp::GetHistory(cpu_vct_t &cpu_vct){ + cpu_vct.clear(); + for (int i = m_history_latest_index + m_history_size; i > m_history_latest_index; i--) { + cpu_vct.push_back(m_cpu_util[i % m_history_size]); + } +} + +/* save last CPU % util in history */ +void CCpuUtlCp::AppendHistory(uint8_t val){ + m_history_latest_index = (m_history_latest_index + 1) % m_history_size; + m_cpu_util[m_history_latest_index] = val; +} diff --git a/src/utl_cpuu.h b/src/utl_cpuu.h index e5305783..109fff4f 100755 --- a/src/utl_cpuu.h +++ b/src/utl_cpuu.h @@ -22,6 +22,8 @@ limitations under the License. */ #include <stdint.h> +#include <cstring> +#include "trex_defs.h" #include "os_time.h" #include "mbuf.h" @@ -56,13 +58,18 @@ public: void Update(); /* return cpu % */ double GetVal(); - + uint8_t GetValRaw(); + void GetHistory(cpu_vct_t &cpu_vct); private: - CCpuUtlDp * m_dpcpu; - uint16_t m_ticks; - uint16_t m_work; + void AppendHistory(uint8_t); + CCpuUtlDp * m_dpcpu; + uint8_t m_ticks; + uint8_t m_work; - double m_cpu_util; + static const int m_history_size=20; + uint8_t m_cpu_util[m_history_size]; // history as cyclic array + uint8_t m_history_latest_index; + double m_cpu_util_lpf; }; #endif diff --git a/src/utl_yaml.cpp b/src/utl_yaml.cpp index 828817e4..8352e887 100755 --- a/src/utl_yaml.cpp +++ b/src/utl_yaml.cpp @@ -21,7 +21,9 @@ See the License for the specific language governing permissions and limitations under the License. */ - +#include <istream> +#include <fstream> +#include "common/basic_utils.h" #define INADDRSZ 4 @@ -65,8 +67,8 @@ static int my_inet_pton4(const char *src, unsigned char *dst) bool utl_yaml_read_ip_addr(const YAML::Node& node, - std::string name, - uint32_t & val){ + const std::string &name, + uint32_t & val){ std::string tmp; uint32_t ip; bool res=false; @@ -83,8 +85,10 @@ bool utl_yaml_read_ip_addr(const YAML::Node& node, return (res); } + + bool utl_yaml_read_uint32(const YAML::Node& node, - std::string name, + const std::string &name, uint32_t & val){ bool res=false; if ( node.FindValue(name) ) { @@ -95,8 +99,8 @@ bool utl_yaml_read_uint32(const YAML::Node& node, } bool utl_yaml_read_uint16(const YAML::Node& node, - std::string name, - uint16_t & val){ + const std::string &name, + uint16_t & val){ uint32_t val_tmp; bool res=false; if ( node.FindValue(name) ) { @@ -108,15 +112,250 @@ bool utl_yaml_read_uint16(const YAML::Node& node, return (res); } -bool utl_yaml_read_bool(const YAML::Node& node, - std::string name, - bool & val){ - bool res=false; - if ( node.FindValue(name) ) { - node[name] >> val ; - res=true; +static void +split_str_by_delimiter(std::string str, char delim, std::vector<std::string> &tokens) { + size_t pos = 0; + std::string token; + + while ((pos = str.find(delim)) != std::string::npos) { + token = str.substr(0, pos); + tokens.push_back(token); + str.erase(0, pos + 1); + } + + if (str.size() > 0) { + tokens.push_back(str); + } +} + +static bool mac2uint64(const std::string &mac_str, uint64_t &mac_num) { + std::vector<std::string> tokens; + uint64_t val; + + split_str_by_delimiter(mac_str, ':', tokens); + if (tokens.size() != 6) { + return false; + } + + val = 0; + + for (int i = 0; i < 6 ; i++) { + char *endptr = NULL; + unsigned long octet = strtoul(tokens[i].c_str(), &endptr, 16); + + if ( (*endptr != 0) || (octet > 0xff) ) { + return false; + } + + val = (val << 8) + octet; } - return( res); + + mac_num = val; + + return true; } +/************************ + * YAML Parser Wrapper + * + ***********************/ +void +YAMLParserWrapper::load(YAML::Node &root) { + std::stringstream ss; + + /* first check file exists */ + if (!utl_is_file_exists(m_filename)){ + ss << "file '" << m_filename << "' does not exists"; + throw std::runtime_error(ss.str()); + } + + std::ifstream fin(m_filename); + + try { + YAML::Parser base_parser(fin); + base_parser.GetNextDocument(root); + + } catch (const YAML::Exception &e) { + parse_err(e.what()); + } +} + +bool +YAMLParserWrapper::parse_bool(const YAML::Node &node, const std::string &name, bool def) { + if (!node.FindValue(name)) { + return def; + } + + return parse_bool(node, name); +} + +bool +YAMLParserWrapper::parse_bool(const YAML::Node &node, const std::string &name) { + try { + bool val; + node[name] >> val; + return (val); + + } catch (const YAML::InvalidScalar &ex) { + parse_err("expecting true/false for field '" + name + "'", node[name]); + + } catch (const YAML::KeyNotFound &ex) { + parse_err("cannot locate mandatory field '" + name + "'", node); + } + + assert(0); +} + +const YAML::Node & +YAMLParserWrapper::parse_list(const YAML::Node &node, const std::string &name) { + + try { + const YAML::Node &val = node[name]; + if (val.Type() != YAML::NodeType::Sequence) { + throw YAML::InvalidScalar(node[name].GetMark()); + } + return val; + + } catch (const YAML::InvalidScalar &ex) { + parse_err("expecting sequence/list for field '" + name + "'", node[name]); + + } catch (const YAML::KeyNotFound &ex) { + parse_err("cannot locate mandatory field '" + name + "'", node); + } + + assert(0); +} + +const YAML::Node & +YAMLParserWrapper::parse_map(const YAML::Node &node, const std::string &name) { + + try { + const YAML::Node &val = node[name]; + if (val.Type() != YAML::NodeType::Map) { + throw YAML::InvalidScalar(node[name].GetMark()); + } + return val; + + } catch (const YAML::InvalidScalar &ex) { + parse_err("expecting map for field '" + name + "'", node[name]); + } catch (const YAML::KeyNotFound &ex) { + parse_err("cannot locate mandatory field '" + name + "'", node); + } + + assert(0); +} + +uint32_t +YAMLParserWrapper::parse_ip(const YAML::Node &node, const std::string &name) { + + + try { + std::string ip_str; + uint32_t ip_num; + + node[name] >> ip_str; + int rc = my_inet_pton4((char *)ip_str.c_str(), (unsigned char *)&ip_num); + if (!rc) { + parse_err("invalid IP address: " + ip_str, node[name]); + } + + return PKT_NTOHL(ip_num); + + } catch (const YAML::InvalidScalar &ex) { + parse_err("expecting valid IP address for field '" + name + "'", node[name]); + + } catch (const YAML::KeyNotFound &ex) { + parse_err("cannot locate mandatory field '" + name + "'", node); + } + + assert(0); +} + +uint64_t +YAMLParserWrapper::parse_mac_addr(const YAML::Node &node, const std::string &name, uint64_t def) { + if (!node.FindValue(name)) { + return def; + } + + return parse_mac_addr(node, name); +} + +uint64_t +YAMLParserWrapper::parse_mac_addr(const YAML::Node &node, const std::string &name) { + + std::string mac_str; + uint64_t mac_num; + + try { + + node[name] >> mac_str; + bool rc = mac2uint64(mac_str, mac_num); + if (!rc) { + parse_err("invalid MAC address: " + mac_str, node[name]); + } + return mac_num; + + } catch (const YAML::InvalidScalar &ex) { + parse_err("expecting true/false for field '" + name + "'", node[name]); + + } catch (const YAML::KeyNotFound &ex) { + parse_err("cannot locate mandatory field '" + name + "'", node); + } + + assert(0); +} + +uint64_t +YAMLParserWrapper::parse_uint(const YAML::Node &node, const std::string &name, uint64_t low, uint64_t high, uint64_t def) { + if (!node.FindValue(name)) { + return def; + } + + return parse_uint(node, name, low, high); +} + +uint64_t +YAMLParserWrapper::parse_uint(const YAML::Node &node, const std::string &name, uint64_t low, uint64_t high) { + + try { + + uint64_t val; + node[name] >> val; + + if ( (val < low) || (val > high) ) { + std::stringstream ss; + ss << "valid range for field '" << name << "' is: [" << low << " - " << high << "]"; + parse_err(ss.str(), node[name]); + } + + return (val); + + } catch (const YAML::InvalidScalar &ex) { + parse_err("expecting true/false for field '" + name + "'", node[name]); + + } catch (const YAML::KeyNotFound &ex) { + parse_err("cannot locate mandatory field '" + name + "'", node); + } + + assert(0); +} + +void +YAMLParserWrapper::parse_err(const std::string &err, const YAML::Node &node) const { + std::stringstream ss; + + ss << "'" << m_filename << "' - YAML parsing error at line " << node.GetMark().line << ": "; + ss << err; + + throw std::runtime_error(ss.str()); +} + +void +YAMLParserWrapper::parse_err(const std::string &err) const { + std::stringstream ss; + + ss << "'" << m_filename << "' - YAML parsing error: " << err; + + throw std::runtime_error(ss.str()); +} diff --git a/src/utl_yaml.h b/src/utl_yaml.h index 71655488..59104b21 100755 --- a/src/utl_yaml.h +++ b/src/utl_yaml.h @@ -24,23 +24,56 @@ limitations under the License. #include <stdint.h> #include <yaml-cpp/yaml.h> - + +/* static methods - please prefer the wrapper over those */ bool utl_yaml_read_ip_addr(const YAML::Node& node, - std::string name, - uint32_t & val - ); + const std::string &name, + uint32_t & val); bool utl_yaml_read_uint32(const YAML::Node& node, - std::string name, - uint32_t & val); + const std::string &name, + uint32_t & val); + bool utl_yaml_read_uint16(const YAML::Node& node, - std::string name, - uint16_t & val); + const std::string &name, + uint16_t & val); + +/* a thin wrapper to customize errors */ +class YAMLParserWrapper { +public: + /* a header that will start every error message */ + YAMLParserWrapper(const std::string &filename) : m_filename(filename) { + } + + /** + * loads the file (while parsing it) + * + */ + void load(YAML::Node &root); + + /* bool */ + bool parse_bool(const YAML::Node &node, const std::string &name); + bool parse_bool(const YAML::Node &node, const std::string &name, bool def); -bool utl_yaml_read_bool(const YAML::Node& node, - std::string name, - bool & val); + const YAML::Node & parse_list(const YAML::Node &node, const std::string &name); + const YAML::Node & parse_map(const YAML::Node &node, const std::string &name); + uint32_t parse_ip(const YAML::Node &node, const std::string &name); + uint64_t parse_mac_addr(const YAML::Node &node, const std::string &name); + uint64_t parse_mac_addr(const YAML::Node &node, const std::string &name, uint64_t def); + + uint64_t parse_uint(const YAML::Node &node, const std::string &name, uint64_t low = 0, uint64_t high = UINT64_MAX); + uint64_t parse_uint(const YAML::Node &node, const std::string &name, uint64_t low, uint64_t high, uint64_t def); + + +public: + void parse_err(const std::string &err, const YAML::Node &node) const; + void parse_err(const std::string &err) const; + + +private: + std::string m_filename; +}; #endif |