diff options
74 files changed, 4738 insertions, 985 deletions
diff --git a/doc/AnalyticsConnect.py b/doc/AnalyticsConnect.py index e1210772..10619532 100755 --- a/doc/AnalyticsConnect.py +++ b/doc/AnalyticsConnect.py @@ -38,7 +38,7 @@ def initialize_analyticsreporting(): return analytics -def get_report(analytics,start_date='2016-11-06',end_date='2016-11-27'): +def get_report(analytics,start_date='2016-11-27',end_date='2016-11-27'): # Use the Analytics Service Object to query the Analytics Reporting API V4. return analytics.reports().batchGet( body={ @@ -80,31 +80,9 @@ def print_response(response): print metricHeader.get('name') + ': ' + value -def export_to_dict(response): - df = {'Test_name':[],'State':[],'Setup':[],'Test_type':[],'MPPS':[],'MPPS-Golden min':[],'MPPS-Golden max':[]} - for report in response.get('reports', []): - rows = report.get('data', {}).get('rows', []) - for row in rows: - dimensions = row.get('dimensions', []) - # print 'this is dimensions' - # print dimensions - df['Test_name'].append(dimensions[1]) - df['State'].append(dimensions[2]) - df['Setup'].append(dimensions[3]) - df['Test_type'].append(dimensions[4]) - dateRangeValues = row.get('metrics', []) - value = dateRangeValues[0].get('values',[])[0] - golden_min = dateRangeValues[0].get('values',[])[1] - golden_max = dateRangeValues[0].get('values',[])[2] - # print value - df['MPPS'].append(value) - df['MPPS-Golden min'].append(golden_min) - df['MPPS-Golden max'].append(golden_max) - return df - def export_to_tuples(response): - counter = 0 + # counter = 0 setups = set() df = {} for report in response.get('reports', []): @@ -125,7 +103,6 @@ def export_to_tuples(response): data.append(value) data.append(golden_min) data.append(golden_max) - counter+=1 if dimensions[3] in setups: if dimensions[1] in df[dimensions[3]]: df[dimensions[3]][dimensions[1]].append(tuple(data)) @@ -135,7 +112,7 @@ def export_to_tuples(response): df[dimensions[3]] = {} df[dimensions[3]][dimensions[1]] = [tuple(data)] setups.add(dimensions[3]) - print 'counter is: %d' % counter + # print 'counter is: %d' % counter return df, setups @@ -143,9 +120,72 @@ def main(): analytics = initialize_analyticsreporting() response = get_report(analytics) df, setups = export_to_tuples(response) + # pprint(df) + return df,setups - - #pprint(response) if __name__ == '__main__': - main() + main() + + +""" +response structure (when fetched with "export to tuples"): + +{ 'setup1': {'test_name1': [(test_res1),(test_res2),...], + 'test_name2': [(test_res1),(test_res2),...] + }, + 'setup2': {'test_name1': [(test_res1),(test_res2),...], + 'test_name2': [(test_res1),(test_res2),...] + }, + . + . + . + . +} + +{u'kiwi02': {u'VM - 64 bytes, multi CPU, cache size 1024': [(u'VM - 64 bytes, multi CPU, cache size 1024', + u'stl', + u'performance', + u'19.711146', + u'19.0', + u'22.0'), + (u'VM - 64 bytes, multi CPU, cache size 1024', + u'stl', + u'performance', + u'19.581567', + u'19.0', + u'22.0')], + u'VM - 64 bytes, multi CPUs': [(u'VM - 64 bytes, multi CPUs', + u'stl', + u'performance', + u'10.398847', + u'9.7', + u'12.5'), + (u'VM - 64 bytes, multi CPUs', + u'stl', + u'performance', + u'10.925308', + u'9.7', + u'12.5') + ] + } + u'trex07': {u'VM - 64 bytes, multi CPU, cache size 1024': [(u'VM - 64 bytes, multi CPU, cache size 1024', + u'stl', + u'performance', + u'25.078212', + u'9.0', + u'15.0') + ] + u'VM - 64 bytes, multi CPUs': [(u'VM - 64 bytes, multi CPUs', + u'stl', + u'performance', + u'9.469138', + u'8.5', + u'12.0') + ] + } +} + + + +""" diff --git a/doc/AnalyticsWebReport.py b/doc/AnalyticsWebReport.py index 182d8367..bd4a9a2b 100755 --- a/doc/AnalyticsWebReport.py +++ b/doc/AnalyticsWebReport.py @@ -3,6 +3,7 @@ import sys import AnalyticsConnect as ac
import TRexDataAnalysis as tr
import time
+import datetime
def main(verbose = False):
@@ -10,12 +11,14 @@ def main(verbose = False): print('Retrieving data from Google Analytics')
analytics = ac.initialize_analyticsreporting()
current_date = time.strftime("%Y-%m-%d")
- response = ac.get_report(analytics, '2016-11-06', current_date)
+ k_days_ago = datetime.datetime.now() - datetime.timedelta(days=15)
+ start_date = str(k_days_ago.date())
+ response = ac.get_report(analytics, start_date, current_date)
ga_all_data_dict, setups = ac.export_to_tuples(response)
dest_path = os.path.join(os.getcwd(), 'build', 'images')
if verbose:
print('Saving data to %s' % dest_path)
- tr.create_all_data(ga_all_data_dict, setups, '2016-11-06', current_date, save_path = dest_path,
+ tr.create_all_data(ga_all_data_dict, setups, start_date, current_date, save_path = dest_path,
add_stats='yes')
if verbose:
print('Done without errors.')
diff --git a/doc/TRexDataAnalysis.py b/doc/TRexDataAnalysis.py index f8cefc19..fb855a16 100755 --- a/doc/TRexDataAnalysis.py +++ b/doc/TRexDataAnalysis.py @@ -42,7 +42,7 @@ def generate_dframe_arr_and_stats_of_tests_per_setup(date, setup_name, setup_dic def create_plot_for_dframe_arr(dframe_arr, setup_name, start_date, end_date, show='no', save_path='',
- file_name='trend_graph'):
+ file_name='_trend_graph'):
dframe_all = pd.concat(dframe_arr, axis=1)
dframe_all = dframe_all.astype(float)
dframe_all.plot()
diff --git a/doc/trex-analytics-howto.asciidoc b/doc/trex-analytics-howto.asciidoc new file mode 100755 index 00000000..6ad6c216 --- /dev/null +++ b/doc/trex-analytics-howto.asciidoc @@ -0,0 +1,58 @@ +TRex Analytics How-To Guide +=========================== +:email: trex.tgen@gmail.com +:quotes.++: +:numbered: +:web_server_url: https://trex-tgn.cisco.com/trex +:local_web_server_url: csi-wiki-01:8181/trex +:toclevels: 6 +:tabledef-default.subs: normal,callouts +include::trex_ga.asciidoc[] +// PDF version - image width variable +ifdef::backend-docbook[] +:p_width: 450 +endif::backend-docbook[] += Requirements +Google Analytics Integration: Google API Python client library: link:https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/installed-py[here] + +Data Analysis: using NumPy, MatPlotLib and Pandas from Anaconda 4.2.0 link:https://www.continuum.io/downloads[here] + += Setup fetch-analysis-publishing routine +== Build an Analytic Environment +1. Create a Google Analytics account and property using this link:https://support.google.com/analytics/answer/1008015?hl=en[link] +2. Using the Google Analytics tracking guide, send test results to your property. link:https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide[here] +3. Set up your account to properly fetch the data from Google Analytics, using this guide: link:https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/installed-py[here] + +== Fetch and organize +1. Fetch the data into dictionary of this structure: + +[source,python] +---- +{ 'setup1': {'test_name1': [(test_res1),(test_res2),...], + 'test_name2': [(test_res1),(test_res2),...] + }, + 'setup2': {'test_name1': [(test_res1),(test_res2),...], + 'test_name2': [(test_res1),(test_res2),...] + }, + . + . + . + . +} +---- +test_res should maintain this structure: +[source,python] +---- +(test_name,state,test_type,MPPS,goldenMin,goldenMax) + +Example: +('VM - 64 bytes, multi CPU, cache size 1024','stl','performance','19.711146','19.0','22.0') +---- +== Analize and generate plots and data tables +Use the script TRexDataAnalysis.py to create the plots and data tables for your test results: + +1. run create_all_data with the entire data dictionary that was fetched, to create plots and tables for each setup provided in the dictionary. + +provide a "save_path" to save the graphs and plots to your desired location + + + +== Build the Document + + + diff --git a/doc/trex_analytics.asciidoc b/doc/trex_analytics.asciidoc index 72d4876e..35c3a3e4 100755 --- a/doc/trex_analytics.asciidoc +++ b/doc/trex_analytics.asciidoc @@ -42,7 +42,7 @@ include::build/images/trex07_latest_test_runs_stats.csv[] === Trend: Analysis Over Time -image:images/trex07trend_graph.png[title="trex07trend_graph",align="left",width={p_width}, link="images/trex07trend_graph.png"] +image:images/trex07_trend_graph.png[title="trex07_trend_graph",align="left",width={p_width}, link="images/trex07_trend_graph.png"] [format="csv", options="header",halign='center'] |=== @@ -68,7 +68,7 @@ include::build/images/trex08_latest_test_runs_stats.csv[] === Trend: Analysis Over Time -image:images/trex08trend_graph.png[title="trex08trend_graph",align="left",width={p_width}, link="images/trex08trend_graph.png"] +image:images/trex08_trend_graph.png[title="trex08_trend_graph",align="left",width={p_width}, link="images/trex08_trend_graph.png"] [format="csv", options="header",halign='center'] |=== @@ -96,7 +96,7 @@ include::build/images/trex09_latest_test_runs_stats.csv[] === Trend: Analysis Over Time -image:images/trex09trend_graph.png[title="trex09trend_graph",align="left",width={p_width}, link="images/trex09trend_graph.png"] +image:images/trex09_trend_graph.png[title="trex09_trend_graph",align="left",width={p_width}, link="images/trex09_trend_graph.png"] [format="csv", options="header",halign='center'] |=== @@ -120,7 +120,7 @@ include::build/images/trex11_latest_test_runs_stats.csv[] === Trend: Analysis Over Time -image:images/trex11trend_graph.png[title="trex11trend_graph",align="left",width={p_width}, link="images/trex11trend_graph.png"] +image:images/trex11_trend_graph.png[title="trex11_trend_graph",align="left",width={p_width}, link="images/trex11_trend_graph.png"] [format="csv", options="header",halign='center'] |=== @@ -148,7 +148,7 @@ include::build/images/kiwi02_latest_test_runs_stats.csv[] === Trend: Analysis Over Time -image:images/kiwi02trend_graph.png[title="kiwi02trend_graph",align="left",width={p_width}, link="images/kiwi02trend_graph.png"] +image:images/kiwi02_trend_graph.png[title="kiwi02_trend_graph",align="left",width={p_width}, link="images/kiwi02_trend_graph.png"] [format="csv", options="header",halign='center'] |=== diff --git a/doc/trex_book.asciidoc b/doc/trex_book.asciidoc index ec661908..ad924360 100755 --- a/doc/trex_book.asciidoc +++ b/doc/trex_book.asciidoc @@ -869,30 +869,28 @@ asr1k(config)#ipv6 route 5000::/64 3001::2 === Client clustering configuration -TRex supports testing complex topologies, using a feature called "client clustering". -This feature allows more detailed clustering of clients. +TRex supports testing complex topologies, with more than one DUT, using a feature called "client clustering". +This feature allows specifying the distribution of clients TRex emulates. Let's look at the following topology: .Topology Example image:images/topology.png[title="Client Clustering",width=850] +We have two clusters of DUTs. +Using config file, you can partition TRex emulated clients to groups, and define +how they will be spread between the DUT clusters. -We would like to configure two clusters and direct traffic to them. - -Using config file, you can instruct TRex to generate clients -with specific configuration per cluster. - -Cluster configuration includes: +Group configuration includes: * IP start range. * IP end range. -* Initiator side configuration. -* Responder side configuration. +* Initiator side configuration. - These are the parameters affecting packets sent from client side. +* Responder side configuration. - These are the parameters affecting packets sent from server side. [NOTE] It is important to understand that this is *complimentary* to the client generator -configured per profile - it only defines how the generator will be clustered. +configured per profile - it only defines how the clients will be spread between clusters. Let's look at an example. @@ -908,11 +906,7 @@ $cat cap2/dns.yaml clients_end : "16.0.0.255" servers_start : "48.0.0.1" servers_end : "48.0.0.255" - clients_per_gb : 201 - min_clients : 101 dual_port_mask : "1.0.0.0" - tcp_aging : 1 - udp_aging : 1 cap_info : - name: cap2/dns.pcap cps : 1.0 @@ -921,8 +915,10 @@ $cat cap2/dns.yaml w : 1 ---- -We want to create two clusters with 4 devices each. -We also want to divide *80%* of the traffic to the upper cluster and *20%* to the lower cluster. +We want to create two clusters with 4 and 3 devices respectively. +We also want to send *80%* of the traffic to the upper cluster and *20%* to the lower cluster. +We can specify to which DUT the packet will be sent by MAC address or IP. We will present a MAC +based example, and then see how to change to be IP based. We will create the following cluster configuration file. @@ -938,13 +934,11 @@ We will create the following cluster configuration file. # # 'groups' - each client group must contain range of IPs # and initiator and responder section -# 'count' represents the number of different MACs -# addresses in the group. -# -# initiator and responder can contain 'vlan', 'src_mac', 'dst_mac' +# 'count' represents the number of different DUTs +# in the group. # -# each group contains a double way VLAN configuration +# 'true' means each group must contain VLAN configuration. 'false' means no VLAN config allowed. vlan: true groups: @@ -956,7 +950,7 @@ groups: dst_mac : "00:00:00:01:00:00" responder : vlan : 200 - dst_mac : "00:00:00:01:00:00" + dst_mac : "00:00:00:02:00:00" count : 4 @@ -964,43 +958,74 @@ groups: ip_end : 16.0.0.255 initiator : vlan : 101 - dst_mac : "01:00:00:00:01:01" + dst_mac : "00:00:01:00:00:00" responder: vlan : 201 - dst_mac : "01:00:00:00:02:01" + dst_mac : "00:00:02:00:00:00" - count : 4 + count : 3 ---- -The above configuration will divide the generator range of 255 clients to two clusters, -each with 4 devices and VLAN in both directions. +The above configuration will divide the generator range of 255 clients to two clusters. The range +of IPs in all groups in the client config file together, must cover the entire range of client IPs +from the traffic profile file. -MACs will be allocated incrementaly, with a wrap around. +MACs will be allocated incrementally, with a wrap around after count addresses. e.g. -*Initiator side:* +*Initiator side: (packets with source in 16.x.x.x net)* -* 16.0.0.1 --> dst_mac: 00:00:00:01:00:00 valn: 100 -* 16.0.0.2 --> dst_mac: 00:00:00:01:00:01 valn: 100 -* 16.0.0.3 --> dst_mac: 00:00:00:01:00:02 valn: 100 -* 16.0.0.4 --> dst_mac: 00:00:00:01:00:03 valn: 100 -* 16.0.0.5 --> dst_mac: 00:00:00:01:00:00 valn: 100 -* 16.0.0.6 --> dst_mac: 00:00:00:01:00:01 valn: 100 +* 16.0.0.1 -> 48.x.x.x - dst_mac: 00:00:00:01:00:00 vlan: 100 +* 16.0.0.2 -> 48.x.x.x - dst_mac: 00:00:00:01:00:01 vlan: 100 +* 16.0.0.3 -> 48.x.x.x - dst_mac: 00:00:00:01:00:02 vlan: 100 +* 16.0.0.4 -> 48.x.x.x - dst_mac: 00:00:00:01:00:03 vlan: 100 +* 16.0.0.5 -> 48.x.x.x - dst_mac: 00:00:00:01:00:00 vlan: 100 +* 16.0.0.6 -> 48.x.x.x - dst_mac: 00:00:00:01:00:01 vlan: 100 -*responder side:* +*responder side: (packets with source in 48.x.x.x net)* +* 48.x.x.x -> 16.0.0.1 - dst_mac(from responder) : "00:00:00:02:00:00" , vlan:200 +* 48.x.x.x -> 16.0.0.2 - dst_mac(from responder) : "00:00:00:02:00:01" , vlan:200 +and so on. + +This means that the DUT MACs of each cluster has to be changed to be sequential. Other option is to +specify instead of ``dst_mac'', ip address, using ``next_hop''. + +For example, config file first group will look like: -* server ->16.0.0.1 dst_mac(from responder) : "01:00:00:00:02:01" , valn:201 -* server ->16.0.0.2 dst_mac(from responder) : "01:00:00:00:02:02" , valn:201 +---- +- ip_start : 16.0.0.1 + ip_end : 16.0.0.204 + initiator : + vlan : 100 + next_hop : 1.1.1.1 + src_ip : 1.1.1.100 + responder : + vlan : 200 + next_hop : 2.2.2.1 + src_ip : 2.2.2.100 -and so on. + count : 4 +---- + +In this case, TRex will try to resolve using ARP requests the addresses +1.1.1.1, 1.1.1.2, 1.1.1.3, 1.1.1.4 (and the range 2.2.2.1-2.2.2.4). If not all IPs are resolved, +TRex will exit with an error message. ``src_ip'' will be used for sending gratitues ARP, and +for filling relevant fields in ARP request. If no ``src_ip'' given, TRex will look for source +IP in the relevant port section in the platform config file (/etc/trex_cfg.yaml). If none is found, TRex +will exit with an error message. + +If client config file is given, the ``dest_mac'' and ``default_gw'' parameters from the platform config +file are ignored. -With this model every client (e.g. 16.0.0.1) will always have the same path, e.g. -c->s side will always have initiator VLAN and init-destination MAC and in the response side (s->c) alway responder-VLAN and responder-MAC + +[NOTE] +It is important to understand that the ip to MAC coupling (both with MAC based config or IP based) +is done at the beginning and never changes. Meaning, for example, for the MAC case, packets +with source IP 16.0.0.2 will always have VLAN 100 and dst MAC 00:00:00:01:00:01. +Packets with destination IP 16.0.0.2 will always have VLAN 200 and dst MAC "00:00:00:02:00:01. +This way, you can predict exactly which packet (and how many packets) will go to each DUT. *Usage:* diff --git a/doc/ws_main.py b/doc/ws_main.py index 58f6e98f..9ba1d295 100755 --- a/doc/ws_main.py +++ b/doc/ws_main.py @@ -987,6 +987,9 @@ def build(bld): bld(rule=convert_to_html_toc_book, source='trex_scapy_rpc_server.asciidoc waf.css', target='trex_scapy_rpc_server.html',scan=ascii_doc_scan); + + bld(rule=convert_to_html_toc_book, + source='trex-analytics-howto.asciidoc waf.css', target='trex-analytics-howto.html',scan=ascii_doc_scan); bld(rule='${ASCIIDOC} -a stylesheet=${SRC[1].abspath()} -a icons=true -a toc2 -a max-width=55em -o ${TGT} ${SRC[0].abspath()}', source='vm_doc.asciidoc waf.css', target='vm_doc.html', scan=ascii_doc_scan) @@ -1073,16 +1076,51 @@ def release(bld): os.system('cp -rv build/release_notes.* '+ release_dir) +def rsync_int(bld, src, dst): + cmd = 'rsync -av --del --rsh=ssh build/{src} {host}:{dir}/{dst}'.format( + src = src, + host = Env().get_local_web_server(), + dir = Env().get_remote_release_path() + '../doc', + dst = dst) + ret = os.system(cmd) + if ret: + bld.fatal("cmd '%s' exited with return status" % (cmd, ret)) + + +def rsync_ext(bld, src, dst): + cmd = 'rsync -avz --del -e "ssh -i {key}" --rsync-path=/usr/bin/rsync build/{src} {user}@{host}:{dir}/doc/{dst}'.format( + key = Env().get_trex_ex_web_key(), + src = src, + user = Env().get_trex_ex_web_user(), + host = Env().get_trex_ex_web_srv(), + dir = Env().get_trex_ex_web_path(), + dst = dst) + ret = os.system(cmd) + if ret: + bld.fatal("cmd '%s' exited with return status" % (cmd, ret)) + + def publish(bld): - # copy all the files to our web server - remote_dir = "%s:%s" % ( Env().get_local_web_server(), Env().get_remote_release_path ()+'../doc/') - os.system('rsync -av --del --rsh=ssh build/ %s' % (remote_dir)) + # copy all the files to internal web server + rsync_int(bld, '', '') def publish_ext(bld): - from_ = 'build/' - os.system('rsync -avz --del -e "ssh -i %s" --rsync-path=/usr/bin/rsync %s %s@%s:%s/doc/' % (Env().get_trex_ex_web_key(),from_, Env().get_trex_ex_web_user(),Env().get_trex_ex_web_srv(),Env().get_trex_ex_web_path() ) ) - + # copy all the files to external web server + rsync_ext(bld, '', '') + + +def publish_perf(bld): + # copy performance files to internal and external servers + rsync_int(bld, 'trex_analytics.html', '') + rsync_ext(bld, 'trex_analytics.html', '') + rsync_int(bld, 'images/*_latest_test_*', 'images/') + rsync_ext(bld, 'images/*_latest_test_*', 'images/') + rsync_int(bld, 'images/*_trend_graph.*', 'images/') + rsync_ext(bld, 'images/*_trend_graph.*', 'images/') + rsync_int(bld, 'images/*_trend_stats.*', 'images/') + rsync_ext(bld, 'images/*_trend_stats.*', 'images/') + def publish_test(bld): # copy all the files to our web server diff --git a/doc/wscript b/doc/wscript index 187a5528..f574e69b 100755 --- a/doc/wscript +++ b/doc/wscript @@ -52,6 +52,9 @@ def publish_ext(bld): def publish_web(bld): ws_main.publish_web(bld) +def publish_perf(bld): + ws_main.publish_perf(bld) + def sync(bld): ws_main.sync(bld) diff --git a/linux/ws_main.py b/linux/ws_main.py index c989bb50..31d6b979 100755 --- a/linux/ws_main.py +++ b/linux/ws_main.py @@ -121,6 +121,7 @@ main_src = SrcGroup(dir='src', 'utl_cpuu.cpp', 'utl_ip.cpp', 'msg_manager.cpp', + 'trex_port_attr.cpp', 'publisher/trex_publisher.cpp', 'stateful_rx_core.cpp', 'flow_stat.cpp', @@ -169,6 +170,8 @@ stateless_src = SrcGroup(dir='src/stateless/', 'cp/trex_dp_port_events.cpp', 'dp/trex_stateless_dp_core.cpp', 'messaging/trex_stateless_messaging.cpp', + 'rx/trex_stateless_rx_core.cpp', + 'rx/trex_stateless_rx_port_mngr.cpp' ]) # RPC code rpc_server_src = SrcGroup(dir='src/rpc-server/', diff --git a/linux_dpdk/ws_main.py b/linux_dpdk/ws_main.py index 28a8f4c1..fa427fee 100755 --- a/linux_dpdk/ws_main.py +++ b/linux_dpdk/ws_main.py @@ -13,6 +13,7 @@ import re import uuid import subprocess import platform +from waflib import Logs # these variables are mandatory ('/' are converted automatically) top = '../' @@ -108,8 +109,12 @@ def check_ibverbs_deps(bld): if ret or not out: bld.fatal("Command of checking libraries '%s' failed.\nReturn status: %s\nOutput: %s" % (cmd, ret, out)) if '=> not found' in out: + Logs.pprint('YELLOW', 'Could not find dependency libraries of libibverbs.so:') + for line in out.splitlines(): + if '=> not found' in line: + Logs.pprint('YELLOW', line) dumy_libs_path = os.path.abspath(top + 'scripts/dumy_libs') - print('Adding rpath %s' % dumy_libs_path) + Logs.pprint('YELLOW', 'Adding rpath of %s' % dumy_libs_path) rpath_linkage.append(dumy_libs_path) @@ -134,6 +139,10 @@ def configure(conf): conf.load('gcc') conf.find_program('ldd') conf.check_cxx(lib = 'z', errmsg = missing_pkg_msg(fedora = 'zlib-devel', ubuntu = 'zlib1g-dev')) + try: + conf.check_cxx(lib = 'ibverbs', errmsg = 'Could not find library ibverbs, will try internal version.') + except: + pass def getstatusoutput(cmd): @@ -177,6 +186,7 @@ main_src = SrcGroup(dir='src', 'nat_check.cpp', 'nat_check_flow_table.cpp', 'msg_manager.cpp', + 'trex_port_attr.cpp', 'publisher/trex_publisher.cpp', 'pal/linux_dpdk/pal_utl.cpp', 'pal/linux_dpdk/mbuf.cpp', @@ -247,7 +257,8 @@ stateless_src = SrcGroup(dir='src/stateless/', 'cp/trex_dp_port_events.cpp', 'dp/trex_stateless_dp_core.cpp', 'messaging/trex_stateless_messaging.cpp', - 'rx/trex_stateless_rx_core.cpp' + 'rx/trex_stateless_rx_core.cpp', + 'rx/trex_stateless_rx_port_mngr.cpp' ]) # JSON package json_src = SrcGroup(dir='external_libs/json', @@ -832,12 +843,15 @@ def post_build(bld): def build(bld): bld.add_pre_fun(pre_build) bld.add_post_fun(post_build); - check_ibverbs_deps(bld) zmq_lib_path='external_libs/zmq/' bld.read_shlib( name='zmq' , paths=[top+zmq_lib_path] ) - ibverbs_lib_path='external_libs/ibverbs/' - bld.read_shlib( name='ibverbs' , paths=[top+ibverbs_lib_path] ) + if bld.env['LIB_IBVERBS']: + bld.read_shlib(name='ibverbs') + else: + ibverbs_lib_path='external_libs/ibverbs/' + bld.read_shlib( name='ibverbs' , paths=[top+ibverbs_lib_path] ) + check_ibverbs_deps(bld) for obj in build_types: build_type(bld,obj); diff --git a/scripts/automation/regression/cfg/client_cfg.yaml b/scripts/automation/regression/cfg/client_cfg.yaml new file mode 100644 index 00000000..5c3d19ef --- /dev/null +++ b/scripts/automation/regression/cfg/client_cfg.yaml @@ -0,0 +1,48 @@ +#vlan: true +vlan: false + +groups: + +- ip_start : 16.0.0.1 + ip_end : 16.0.1.255 + initiator : + next_hop: 1.1.1.1 + src_ip : 1.1.1.2 + responder : + next_hop: 1.1.2.1 + src_ip : 1.1.2.2 + + count : 1 + +- ip_start : 17.0.0.1 + ip_end : 17.0.1.255 + initiator : + next_hop: 1.1.3.1 + src_ip : 1.1.3.2 + responder : + next_hop: 1.1.4.1 + src_ip : 1.1.4.2 + + count : 1 + +- ip_start : 18.0.0.1 + ip_end : 18.0.1.255 + initiator : + next_hop: 1.1.5.1 + src_ip : 1.1.5.2 + responder : + next_hop: 1.1.6.1 + src_ip : 1.1.6.2 + + count : 1 + +- ip_start : 19.0.0.1 + ip_end : 19.0.1.255 + initiator : + next_hop: 1.1.7.1 + src_ip : 1.1.7.2 + responder : + next_hop: 1.1.8.1 + src_ip : 1.1.8.2 + + count : 1 diff --git a/scripts/automation/regression/setups/trex15/benchmark.yaml b/scripts/automation/regression/setups/trex03/benchmark.yaml index b366b3fb..b366b3fb 100644 --- a/scripts/automation/regression/setups/trex15/benchmark.yaml +++ b/scripts/automation/regression/setups/trex03/benchmark.yaml diff --git a/scripts/automation/regression/setups/trex15/config.yaml b/scripts/automation/regression/setups/trex03/config.yaml index c5fc3b22..17c4c91e 100644 --- a/scripts/automation/regression/setups/trex15/config.yaml +++ b/scripts/automation/regression/setups/trex03/config.yaml @@ -34,6 +34,6 @@ # expected_bw - the "golden" bandwidth (in Gbps) results planned on receiving from the test trex: - hostname : csi-trex-15 + hostname : csi-trex-03 cores : 1 modes : [loopback, virt_nics, VM] diff --git a/scripts/automation/regression/setups/trex17/benchmark.yaml b/scripts/automation/regression/setups/trex06/benchmark.yaml index 8bc9d29c..2f51a3fd 100644 --- a/scripts/automation/regression/setups/trex17/benchmark.yaml +++ b/scripts/automation/regression/setups/trex06/benchmark.yaml @@ -140,7 +140,7 @@ test_CPU_benchmark: bw_per_core : 1 - name : stl/pcap.py - kwargs : {ipg_usec: 2, loop_count: 0} + kwargs : {ipg_usec: 3, loop_count: 0} cpu_util : 1 bw_per_core : 1 diff --git a/scripts/automation/regression/setups/trex17/config.yaml b/scripts/automation/regression/setups/trex06/config.yaml index 7ad6a20a..da0eb2dd 100644 --- a/scripts/automation/regression/setups/trex17/config.yaml +++ b/scripts/automation/regression/setups/trex06/config.yaml @@ -34,6 +34,6 @@ # expected_bw - the "golden" bandwidth (in Gbps) results planned on receiving from the test trex: - hostname : csi-trex-17 + hostname : csi-trex-06 cores : 1 modes : [loopback, virt_nics, VM] diff --git a/scripts/automation/regression/setups/trex11/backup/benchmark.yaml b/scripts/automation/regression/setups/trex11/backup/benchmark.yaml new file mode 100644 index 00000000..b366b3fb --- /dev/null +++ b/scripts/automation/regression/setups/trex11/backup/benchmark.yaml @@ -0,0 +1,155 @@ +################################################################ +#### TRex benchmark configuration file #### +################################################################ + +### stateful ### + +test_jumbo: + multiplier : 2.8 + cores : 1 + bw_per_core : 106.652 + + +test_routing_imix: + multiplier : 0.5 + cores : 1 + bw_per_core : 11.577 + + +test_routing_imix_64: + multiplier : 28 + cores : 1 + bw_per_core : 2.030 + + +test_static_routing_imix_asymmetric: + multiplier : 0.8 + cores : 1 + bw_per_core : 13.742 + + + +### 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: 4, 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 + + diff --git a/scripts/automation/regression/setups/trex11/backup/config.yaml b/scripts/automation/regression/setups/trex11/backup/config.yaml new file mode 100644 index 00000000..782b7542 --- /dev/null +++ b/scripts/automation/regression/setups/trex11/backup/config.yaml @@ -0,0 +1,38 @@ +################################################################ +#### TRex nightly test configuration file #### +################################################################ + + +### TRex configuration: +# hostname - can be DNS name or IP for the TRex machine for ssh to the box +# password - root password for TRex machine +# is_dual - should the TRex inject with -p ? +# version_path - path to the TRex version and executable +# cores - how many cores should be used +# latency - rate of latency packets injected by the TRex +# modes - list of modes (tagging) of this setup (loopback, virtual etc.) +# * loopback - Trex works via loopback. Router and TFTP configurations may be skipped. +# * virtual - virtual OS (accept low CPU utilization in tests) + +### Router configuration: +# hostname - the router hostname as apears in ______# cli prefix +# ip_address - the router's ip that can be used to communicate with +# image - the desired imaged wished to be loaded as the router's running config +# line_password - router password when access via Telent +# en_password - router password when changing to "enable" mode +# interfaces - an array of client-server pairs, representing the interfaces configurations of the router +# configurations - an array of configurations that could possibly loaded into the router during the test. +# The "clean" configuration is a mandatory configuration the router will load with to run the basic test bench + +### TFTP configuration: +# hostname - the tftp hostname +# ip_address - the tftp's ip address +# images_path - the tftp's relative path in which the router's images are located + +### Test_misc configuration: +# expected_bw - the "golden" bandwidth (in Gbps) results planned on receiving from the test + +trex: + hostname : csi-trex-11 + cores : 1 + modes : ['loopback', 'VM', 'virt_nics'] diff --git a/scripts/automation/regression/setups/trex11/benchmark.yaml b/scripts/automation/regression/setups/trex11/benchmark.yaml index b366b3fb..a4969d2d 100644 --- a/scripts/automation/regression/setups/trex11/benchmark.yaml +++ b/scripts/automation/regression/setups/trex11/benchmark.yaml @@ -1,31 +1,44 @@ -################################################################ +############################################################### #### TRex benchmark configuration file #### -################################################################ +############################################################### -### stateful ### +#### common templates ### -test_jumbo: - multiplier : 2.8 - cores : 1 - bw_per_core : 106.652 +#test_jumbo: +# multiplier : 2.8 +# cores : 1 +# bw_per_core : 962.464 test_routing_imix: multiplier : 0.5 cores : 1 - bw_per_core : 11.577 + bw_per_core : 48.130 test_routing_imix_64: multiplier : 28 cores : 1 - bw_per_core : 2.030 + bw_per_core : 12.699 test_static_routing_imix_asymmetric: - multiplier : 0.8 + multiplier : 0.5 cores : 1 - bw_per_core : 13.742 + bw_per_core : 50.561 + + +test_ipv6_simple: + multiplier : 0.5 + cores : 1 + bw_per_core : 19.5 + + +test_rx_check_http: &rx_http + multiplier : 1000 + cores : 1 + rx_sample_rate : 128 + bw_per_core : 49.464 @@ -140,16 +153,21 @@ test_CPU_benchmark: bw_per_core : 1 - name : stl/pcap.py - kwargs : {ipg_usec: 4, loop_count: 0} + 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/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 + #- name : stl/hlt/hlt_udp_rand_len_9k.py + # cpu_util : 1 + # bw_per_core : 1 + +test_all_profiles : + mult : "5%" + skip : ['udp_rand_len_9k.py','udp_inc_len_9k.py'] # due to VIC 9K defect trex-282 + diff --git a/scripts/automation/regression/setups/trex11/config.yaml b/scripts/automation/regression/setups/trex11/config.yaml index 782b7542..393c8749 100644 --- a/scripts/automation/regression/setups/trex11/config.yaml +++ b/scripts/automation/regression/setups/trex11/config.yaml @@ -4,15 +4,16 @@ ### TRex configuration: -# hostname - can be DNS name or IP for the TRex machine for ssh to the box -# password - root password for TRex machine -# is_dual - should the TRex inject with -p ? -# version_path - path to the TRex version and executable -# cores - how many cores should be used -# latency - rate of latency packets injected by the TRex -# modes - list of modes (tagging) of this setup (loopback, virtual etc.) -# * loopback - Trex works via loopback. Router and TFTP configurations may be skipped. -# * virtual - virtual OS (accept low CPU utilization in tests) +# hostname - can be DNS name or IP for the TRex machine for ssh to the box +# password - root password for TRex machine +# is_dual - should the TRex inject with -p ? +# version_path - path to the TRex version and executable +# cores - how many cores should be used +# latency - rate of latency packets injected by the TRex +# modes - list of modes (tagging) of this setup (loopback etc.) +# * loopback - Trex works via loopback. Router and TFTP configurations may be skipped. +# * VM - Virtual OS (accept low CPU utilization in tests, latency can get spikes) +# * virt_nics - NICs are virtual (VMXNET3 etc.) ### Router configuration: # hostname - the router hostname as apears in ______# cli prefix @@ -35,4 +36,6 @@ trex: hostname : csi-trex-11 cores : 1 - modes : ['loopback', 'VM', 'virt_nics'] + modes : ['loopback'] + + diff --git a/scripts/automation/regression/stateful_tests/trex_client_cfg_test.py b/scripts/automation/regression/stateful_tests/trex_client_cfg_test.py new file mode 100644 index 00000000..852e745d --- /dev/null +++ b/scripts/automation/regression/stateful_tests/trex_client_cfg_test.py @@ -0,0 +1,52 @@ +#!/router/bin/python +from .trex_general_test import CTRexGeneral_Test, CTRexScenario +from CPlatform import CStaticRouteConfig +from .tests_exceptions import * +#import sys +import time +from nose.tools import nottest + +# Testing client cfg ARP resolve. Actually, just need to check that TRex run finished with no errors. +# If resolve will fail, TRex will exit with exit code != 0 +class CTRexClientCfg_Test(CTRexGeneral_Test): + """This class defines the IMIX testcase of the TRex traffic generator""" + def __init__(self, *args, **kwargs): + # super(CTRexClientCfg_Test, self).__init__() + CTRexGeneral_Test.__init__(self, *args, **kwargs) + + def setUp(self): + if CTRexScenario.setup_name == 'kiwi02': + self.skip("Can't run currently on kiwi02") + super(CTRexClientCfg_Test, self).setUp() # launch super test class setUp process + pass + + def test_client_cfg(self): + # test initializtion + if self.is_loopback: + return + else: + self.router.configure_basic_interfaces() + self.router.config_pbr(mode = "config") + + ret = self.trex.start_trex( + c = 1, + m = 1, + d = 10, + f = 'cap2/dns.yaml', + v = 3, + client_cfg = 'automation/regression/cfg/client_cfg.yaml', + l = 1000) + + trex_res = self.trex.sample_to_run_finish() + + print("\nLATEST RESULT OBJECT:") + print(trex_res) + + self.check_general_scenario_results(trex_res) + + def tearDown(self): + CTRexGeneral_Test.tearDown(self) + pass + +if __name__ == "__main__": + pass diff --git a/scripts/automation/regression/stateless_tests/stl_client_test.py b/scripts/automation/regression/stateless_tests/stl_client_test.py index acf5dc61..73dac734 100644 --- a/scripts/automation/regression/stateless_tests/stl_client_test.py +++ b/scripts/automation/regression/stateless_tests/stl_client_test.py @@ -241,6 +241,7 @@ class STLClient_Test(CStlGeneral_Test): return default_mult = self.get_benchmark_param('mult',default="30%") + skip_tests = self.get_benchmark_param('skip',default=[]) try: print("\n"); @@ -248,6 +249,17 @@ class STLClient_Test(CStlGeneral_Test): for profile in self.profiles: + skip=False + if skip_tests: + for skip_test in skip_tests: + if skip_test in profile: + skip=True; + break; + if skip: + print("skipping testing profile due to config file {0}...\n".format(profile)) + continue; + + print("now testing profile {0}...\n".format(profile)) p1 = STLProfile.load(profile, port_id = self.tx_port) diff --git a/scripts/automation/regression/stateless_tests/stl_rx_test.py b/scripts/automation/regression/stateless_tests/stl_rx_test.py index d28fca54..4dad712f 100644 --- a/scripts/automation/regression/stateless_tests/stl_rx_test.py +++ b/scripts/automation/regression/stateless_tests/stl_rx_test.py @@ -61,6 +61,13 @@ class STLRX_Test(CStlGeneral_Test): 'latency_9k_max_latency': 250, }, + 'rte_enic_pmd': { + 'rate_percent': 1, + 'total_pkts': 50, + 'rate_latency': 1, + 'latency_9k_enable': False, + }, + } 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 e9d2b8a0..5d992c6e 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 @@ -150,10 +150,8 @@ class CTRexClient(object): user = user or self.__default_user try: d = int(d) - if d < 30 and not trex_development: # test duration should be at least 30 seconds, unless trex_development flag is specified. - raise ValueError except ValueError: - raise ValueError('d parameter must be integer, specifying how long TRex run, and must be larger than 30 secs.') + raise ValueError('d parameter must be integer, specifying how long TRex run.') trex_cmd_options.update( {'f' : f, 'd' : d} ) if not trex_cmd_options.get('l'): 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 b23b5f1f..b33b0447 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -202,7 +202,7 @@ class TRexConsole(TRexGeneralCmd): func_name = f.__name__ if func_name.startswith("do_"): func_name = func_name[3:] - + if not inst.stateless_client.is_connected(): print(format_text("\n'{0}' cannot be executed on offline mode\n".format(func_name), 'bold')) return @@ -313,6 +313,7 @@ class TRexConsole(TRexGeneralCmd): def do_shell (self, line): self.do_history(line) + @verify_connected def do_push (self, line): '''Push a local PCAP file\n''' self.stateless_client.push_line(line) @@ -320,6 +321,7 @@ class TRexConsole(TRexGeneralCmd): def help_push (self): self.do_push("-h") + @verify_connected def do_portattr (self, line): '''Change/show port(s) attributes\n''' self.stateless_client.set_port_attr_line(line) @@ -328,6 +330,22 @@ class TRexConsole(TRexGeneralCmd): self.do_portattr("-h") @verify_connected + def do_set_rx_sniffer (self, line): + '''Sets a port sniffer on RX channel as PCAP recorder''' + self.stateless_client.set_rx_sniffer_line(line) + + def help_sniffer (self): + self.do_set_rx_sniffer("-h") + + @verify_connected + def do_resolve (self, line): + '''Resolve ARP for ports''' + self.stateless_client.resolve_line(line) + + def help_sniffer (self): + self.do_resolve("-h") + + @verify_connected def do_map (self, line): '''Maps ports topology\n''' ports = self.stateless_client.get_acquired_ports() @@ -416,6 +434,7 @@ class TRexConsole(TRexGeneralCmd): '''Release ports\n''' self.stateless_client.release_line(line) + @verify_connected def do_reacquire (self, line): '''reacquire all the ports under your logged user name''' self.stateless_client.reacquire_line(line) diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_imix_bidir_update.py b/scripts/automation/trex_control_plane/stl/examples/stl_imix_bidir_update.py new file mode 100644 index 00000000..22cceb8f --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_imix_bidir_update.py @@ -0,0 +1,123 @@ +import stl_path +from trex_stl_lib.api import * + +import imp +import time +import json +from pprint import pprint +import argparse + +# IMIX test +# it maps the ports to sides +# then it load a predefind profile 'IMIX' +# and attach it to both sides and inject +# at a certain rate for some time +# finally it checks that all packets arrived +def imix_test (server): + + + # create client + c = STLClient(server = server) + passed = True + + + try: + + # connect to server + c.connect() + + # take all the ports + c.reset() + + dir_0 = [0] + dir_1 = [1] + + print "Mapped ports to sides {0} <--> {1}".format(dir_0, dir_1) + + # load IMIX profile + profile_file = os.path.join(stl_path.STL_PROFILES_PATH, 'imix.py') + profile1 = STLProfile.load_py(profile_file, direction=0) + profile2 = STLProfile.load_py(profile_file, direction=1) + stream1 = profile1.get_streams() + stream2 = profile2.get_streams() + + # add both streams to ports + c.add_streams(stream1, ports = dir_0) + c.add_streams(stream2, ports = dir_1) + + + # clear the stats before injecting + c.clear_stats() + + c.start(ports = (dir_0 + dir_1), mult = "100kpps", total = True) + + while True: + for rate in range(200,3100,10): + + # choose rate and start traffic for 10 seconds on 5 mpps + #mult = "30%" + my_mult = ("%dkpps"%rate) + print "Injecting {0} <--> {1} on total rate of '{2}' ".format(dir_0, dir_1, my_mult) + c.clear_stats() + + + c.update(ports = (dir_0 + dir_1), mult = my_mult) + + #time.sleep(1); + + # block until done + #c.wait_on_traffic(ports = (dir_0 + dir_1)) + + # read the stats after the test + stats = c.get_stats() + + # use this for debug info on all the stats + pprint(stats) + + # sum dir 0 + dir_0_opackets = sum([stats[i]["opackets"] for i in dir_0]) + dir_0_ipackets = sum([stats[i]["ipackets"] for i in dir_0]) + + # sum dir 1 + dir_1_opackets = sum([stats[i]["opackets"] for i in dir_1]) + dir_1_ipackets = sum([stats[i]["ipackets"] for i in dir_1]) + + + lost_0 = dir_0_opackets - dir_1_ipackets + lost_1 = dir_1_opackets - dir_0_ipackets + + print "\nPackets injected from {0}: {1:,}".format(dir_0, dir_0_opackets) + print "Packets injected from {0}: {1:,}".format(dir_1, dir_1_opackets) + + print "\npackets lost from {0} --> {1}: {2:,} pkts".format(dir_0, dir_1, lost_0) + print "packets lost from {0} --> {1}: {2:,} pkts".format(dir_1, dir_0, lost_1) + + if (lost_0 <= 0) and (lost_1 <= 0): # less or equal because we might have incoming arps etc. + passed = True + else: + passed = False + + + except STLError as e: + passed = False + print e + + finally: + c.disconnect() + + if passed: + print "\nTest has passed :-)\n" + else: + print "\nTest has failed :-(\n" + +parser = argparse.ArgumentParser(description="Example for TRex Stateless, sending IMIX traffic") +parser.add_argument('-s', '--server', + dest='server', + help='Remote trex address', + default='127.0.0.1', + type = str) +args = parser.parse_args() + +# run the tests +imix_test(args.server) + 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 6b53e67e..cc20e088 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 @@ -12,7 +12,7 @@ from .trex_stl_types import * from .trex_stl_async_client import CTRexAsyncClient from .utils import parsing_opts, text_tables, common -from .utils.common import list_intersect, list_difference, is_sub_list, PassiveTimer +from .utils.common import * from .utils.text_opts import * from functools import wraps @@ -24,6 +24,8 @@ import re import random import json import traceback +import os.path + ############################ logger ############################# ############################ ############################# @@ -32,9 +34,10 @@ import traceback # logger API for the client class LoggerApi(object): # verbose levels - VERBOSE_QUIET = 0 - VERBOSE_REGULAR = 1 - VERBOSE_HIGH = 2 + VERBOSE_QUIET = 0 + VERBOSE_REGULAR_SYNC = 1 + VERBOSE_REGULAR = 2 + VERBOSE_HIGH = 3 def __init__(self): self.level = LoggerApi.VERBOSE_REGULAR @@ -62,7 +65,7 @@ class LoggerApi(object): # simple log message with verbose - def log (self, msg, level = VERBOSE_REGULAR, newline = True): + def log (self, msg, level = VERBOSE_REGULAR_SYNC, newline = True): if not self.check_verbose(level): return @@ -90,19 +93,20 @@ class LoggerApi(object): # supress object getter - def supress (self): + def supress (self, level = VERBOSE_QUIET): class Supress(object): - def __init__ (self, logger): + def __init__ (self, logger, level): self.logger = logger + self.level = level def __enter__ (self): self.saved_level = self.logger.get_verbose() - self.logger.set_verbose(LoggerApi.VERBOSE_QUIET) + self.logger.set_verbose(self.level) def __exit__ (self, type, value, traceback): self.logger.set_verbose(self.saved_level) - return Supress(self) + return Supress(self, level) @@ -322,22 +326,25 @@ class EventsHandler(object): # port attr changed elif (event_type == 8): + port_id = int(data['port_id']) - if data['attr'] == self.client.ports[port_id].attr: - return # false alarm - old_info = self.client.ports[port_id].get_info() - self.__async_event_port_attr_changed(port_id, data['attr']) - new_info = self.client.ports[port_id].get_info() + + diff = self.__async_event_port_attr_changed(port_id, data['attr']) + if not diff: + return + + ev = "port {0} attributes changed".format(port_id) - for key, old_val in old_info.items(): - new_val = new_info[key] - if old_val != new_val: - ev += '\n {key}: {old} -> {new}'.format( - key = key, - old = old_val.lower() if type(old_val) is str else old_val, - new = new_val.lower() if type(new_val) is str else new_val) + for key, (old_val, new_val) in diff.items(): + ev += '\n {key}: {old} -> {new}'.format( + key = key, + old = old_val.lower() if type(old_val) is str else old_val, + new = new_val.lower() if type(new_val) is str else new_val) + show_event = True - + + + # server stopped elif (event_type == 100): ev = "Server has stopped" @@ -394,7 +401,7 @@ class EventsHandler(object): def __async_event_port_attr_changed (self, port_id, attr): if port_id in self.client.ports: - self.client.ports[port_id].async_event_port_attr_changed(attr) + return self.client.ports[port_id].async_event_port_attr_changed(attr) # add event to log def __add_event_log (self, origin, ev_type, msg, show = False): @@ -652,7 +659,7 @@ class STLClient(object): return rc - + def __add_streams(self, stream_list, port_id_list = None): port_id_list = self.__ports(port_id_list) @@ -801,16 +808,75 @@ class STLClient(object): return rc + def __resolve (self, port_id_list = None, retries = 0): + port_id_list = self.__ports(port_id_list) + + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].arp_resolve(retries)) + + return rc + + def __set_port_attr (self, port_id_list = None, attr_dict = None): port_id_list = self.__ports(port_id_list) rc = RC() + for port_id, port_attr_dict in zip(port_id_list, attr_dict): + rc.add(self.ports[port_id].set_attr(**port_attr_dict)) + + return rc + + + def __set_rx_sniffer (self, port_id_list, base_filename, limit): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + head, tail = os.path.splitext(base_filename) + filename = "{0}-{1}{2}".format(head, port_id, tail) + rc.add(self.ports[port_id].set_rx_sniffer(filename, limit)) + + return rc + + + def __remove_rx_sniffer (self, port_id_list): + port_id_list = self.__ports(port_id_list) + rc = RC() + for port_id in port_id_list: - rc.add(self.ports[port_id].set_attr(attr_dict)) + rc.add(self.ports[port_id].remove_rx_sniffer()) return rc + def __set_rx_queue (self, port_id_list, size): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].set_rx_queue(size)) + + return rc + + def __remove_rx_queue (self, port_id_list): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].remove_rx_queue()) + + return rc + + def __get_rx_queue_pkts (self, port_id_list): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].get_rx_queue_pkts()) + + return rc # connect to server @@ -1010,7 +1076,7 @@ class STLClient(object): if not port_id in valid_ports: raise STLError("Port ID '{0}' is not a valid port ID - valid values: {1}".format(port_id, valid_ports)) - return port_id_list + return list_remove_dup(port_id_list) # transmit request on the RPC link @@ -1315,6 +1381,12 @@ class STLClient(object): if port_obj.is_active()] + def get_resolvable_ports (self): + return [port_id + for port_id, port_obj in self.ports.items() + if port_obj.is_acquired() and port_obj.get_dst_addr()['ipv4'] is not None] + + # get paused ports def get_paused_ports (self, owned = True): if owned: @@ -1340,6 +1412,7 @@ class STLClient(object): # get stats + @__api_check(True) def get_stats (self, ports = None, sync_now = True): """ Return dictionary containing statistics information gathered from the server. @@ -1587,7 +1660,7 @@ class STLClient(object): ports = ports if ports is not None else self.get_all_ports() ports = self._validate_port_list(ports) - return [self.ports[port_id].get_info() for port_id in ports] + return [self.ports[port_id].get_formatted_info() for port_id in ports] ############################ Commands ############################# @@ -1702,6 +1775,9 @@ class STLClient(object): self.__release(ports) raise STLError(rc) + for port_id in ports: + if not self.ports[port_id].is_resolved(): + self.logger.log(format_text('*** Warning - Port {0} destination is unresolved ***'.format(port_id), 'bold')) @__api_check(True) def release (self, ports = None): @@ -1727,30 +1803,80 @@ class STLClient(object): if not rc: raise STLError(rc) + + @__api_check(True) - def ping(self): + def ping_rpc_server(self): """ - Pings the server + Pings the RPC server :parameters: - None - + None :raises: + :exc:`STLError` """ - + self.logger.pre_cmd("Pinging the server on '{0}' port '{1}': ".format(self.connection_info['server'], self.connection_info['sync_port'])) rc = self._transmit("ping", api_class = None) - + self.logger.post_cmd(rc) if not rc: raise STLError(rc) + @__api_check(True) + def ping_ip (self, src_port, dst_ipv4, pkt_size = 64, count = 5): + """ + Pings an IP address through a port + + :parameters: + src_port - on which port_id to send the ICMP PING request + dst_ipv4 - which IP to ping + pkt_size - packet size to use + count - how many times to ping + :raises: + + :exc:`STLError` + + """ + # validate src port + validate_type('src_port', src_port, int) + if src_port not in self.get_all_ports(): + raise STLError("src port is not a valid port id") + + if not is_valid_ipv4(dst_ipv4): + raise STLError("dst_ipv4 is not a valid IPv4 address: '{0}'".format(dst_ipv4)) + + if (pkt_size < 64) or (pkt_size > 9216): + raise STLError("pkt_size should be a value between 64 and 9216: '{0}'".format(pkt_size)) + + validate_type('count', count, int) + + + + self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ipv4, + src_port, + pkt_size)) + + # no async messages + with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC): + self.logger.log('') + for i in range(count): + rc = self.ports[src_port].ping(ping_ipv4 = dst_ipv4, pkt_size = pkt_size) + if not rc: + raise STLError(rc) + + self.logger.log(rc.data()) + + if i != (count - 1): + time.sleep(1) + + + + @__api_check(True) def server_shutdown (self, force = False): """ Sends the server a request for total shutdown @@ -1835,7 +1961,7 @@ class STLClient(object): @__api_check(True) - def reset(self, ports = None): + def reset(self, ports = None, restart = False): """ Force acquire ports, stop the traffic, remove all streams and clear stats @@ -1843,7 +1969,9 @@ class STLClient(object): ports : list Ports on which to execute the command - + restart: bool + Restart the NICs (link down / up) + :raises: + :exc:`STLError` @@ -1853,12 +1981,34 @@ class STLClient(object): ports = ports if ports is not None else self.get_all_ports() ports = self._validate_port_list(ports) - # force take the port and ignore any streams on it - self.acquire(ports, force = True, sync_streams = False) - self.stop(ports, rx_delay_ms = 0) - self.remove_all_streams(ports) - self.clear_stats(ports) + + if restart: + self.logger.pre_cmd("Hard resetting ports {0}:".format(ports)) + else: + self.logger.pre_cmd("Resetting ports {0}:".format(ports)) + + + try: + with self.logger.supress(): + # force take the port and ignore any streams on it + self.acquire(ports, force = True, sync_streams = False) + self.stop(ports, rx_delay_ms = 0) + self.remove_all_streams(ports) + self.clear_stats(ports) + self.set_port_attr(ports, + promiscuous = False, + link_up = True if restart else None, + rxf = 'hw') + self.remove_rx_sniffer(ports) + self.remove_rx_queue(ports) + + except STLError as e: + self.logger.post_cmd(False) + raise e + + self.logger.post_cmd(RC_OK()) + @__api_check(True) def remove_all_streams (self, ports = None): @@ -1997,7 +2147,25 @@ class STLClient(object): raise STLError(rc) - + # common checks for start API + def __pre_start_check (self, ports, force): + + # verify link status + ports_link_down = [port_id for port_id in ports if not self.ports[port_id].is_up()] + if ports_link_down and not force: + raise STLError("Port(s) %s - link DOWN - check the connection or specify 'force'" % ports_link_down) + + # verify ports are stopped or force stop them + active_ports = [port_id for port_id in ports if self.ports[port_id].is_active()] + if active_ports and not force: + raise STLError("Port(s) {0} are active - please stop them or specify 'force'".format(active_ports)) + + # warn if ports are not resolved + unresolved_ports = [port_id for port_id in ports if not self.ports[port_id].is_resolved()] + if unresolved_ports and not force: + raise STLError("Port(s) {0} have unresolved destination addresses - please resolve them or specify 'force'".format(unresolved_ports)) + + @__api_check(True) def start (self, ports = None, @@ -2052,11 +2220,10 @@ class STLClient(object): validate_type('total', total, bool) validate_type('core_mask', core_mask, (int, list)) - # verify link status - ports_link_down = [port_id for port_id in ports if self.ports[port_id].attr.get('link',{}).get('up') == False] - if not force and ports_link_down: - raise STLError("Port(s) %s - link DOWN - check the connection or specify 'force'" % ports_link_down) - + + # some sanity checks before attempting start + self.__pre_start_check(ports, force) + ######################### # decode core mask argument decoded_mask = self.__decode_core_mask(ports, core_mask) @@ -2070,17 +2237,12 @@ class STLClient(object): raise STLArgumentError('mult', mult) - # verify ports are stopped or force stop them + # stop active ports if needed active_ports = list(set(self.get_active_ports()).intersection(ports)) - if active_ports: - if not force: - raise STLError("Port(s) {0} are active - please stop them or specify 'force'".format(active_ports)) - else: - rc = self.stop(active_ports) - if not rc: - raise STLError(rc) - + if active_ports and force: + self.stop(active_ports) + # start traffic self.logger.pre_cmd("Starting traffic on port(s) {0}:".format(ports)) rc = self.__start(mult_obj, duration, ports, force, decoded_mask) @@ -2117,7 +2279,7 @@ class STLClient(object): return ports = self._validate_port_list(ports) - + self.logger.pre_cmd("Stopping traffic on port(s) {0}:".format(ports)) rc = self.__stop(ports) self.logger.post_cmd(rc) @@ -2629,16 +2791,28 @@ class STLClient(object): @__api_check(True) - def set_port_attr (self, ports = None, promiscuous = None, link_up = None, led_on = None, flow_ctrl = None): + def set_port_attr (self, + ports = None, + promiscuous = None, + link_up = None, + led_on = None, + flow_ctrl = None, + rxf = None, + ipv4 = None, + dest = None, + resolve = True): """ Set port attributes :parameters: - promiscuous - True or False - link_up - True or False - led_on - True or False - flow_ctrl - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable - + promiscuous - True or False + link_up - True or False + led_on - True or False + flow_ctrl - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable + rxf - 'hw' for hardware rules matching packets only or 'all' all packets + ipv4 - configure IPv4 address for port(s). for multiple ports should be a list of IPv4 addresses in the same length of the ports array + dest - configure destination address for port(s) in either IPv4 or MAC format. for multiple ports should be a list in the same length of the ports array + resolve - if true, in case a destination address is configured as IPv4 try to resolve it :raises: + :exe:'STLError' @@ -2652,29 +2826,229 @@ class STLClient(object): validate_type('link_up', link_up, (bool, type(None))) validate_type('led_on', led_on, (bool, type(None))) validate_type('flow_ctrl', flow_ctrl, (int, type(None))) - - # build attributes - attr_dict = {} - if promiscuous is not None: - attr_dict['promiscuous'] = {'enabled': promiscuous} - if link_up is not None: - attr_dict['link_status'] = {'up': link_up} - if led_on is not None: - attr_dict['led_status'] = {'on': led_on} - if flow_ctrl is not None: - attr_dict['flow_ctrl_mode'] = {'mode': flow_ctrl} + validate_choice('rxf', rxf, ['hw', 'all']) + + # common attributes for all ports + cmn_attr_dict = {} + + cmn_attr_dict['promiscuous'] = promiscuous + cmn_attr_dict['link_status'] = link_up + cmn_attr_dict['led_status'] = led_on + cmn_attr_dict['flow_ctrl_mode'] = flow_ctrl + cmn_attr_dict['rx_filter_mode'] = rxf + + # each port starts with a set of the common attributes + attr_dict = [dict(cmn_attr_dict) for _ in ports] + + # default value for IPv4 / dest is none for all ports + if ipv4 is None: + ipv4 = [None] * len(ports) + if dest is None: + dest = [None] * len(ports) + + ipv4 = listify(ipv4) + if len(ipv4) != len(ports): + raise STLError("'ipv4' must be a list in the same length of ports - 'ports': {0}, 'ip': {1}".format(ports, ipv4)) + + dest = listify(dest) + if len(dest) != len(ports): + raise STLError("'dest' must be a list in the same length of ports - 'ports': {0}, 'dest': {1}".format(ports, dest)) + + # update each port attribute with ipv4 + for addr, port_attr in zip(ipv4, attr_dict): + port_attr['ipv4'] = addr + + # update each port attribute with dest + for addr, port_attr in zip(dest, attr_dict): + port_attr['dest'] = addr + - # no attributes to set - if not attr_dict: - return - self.logger.pre_cmd("Applying attributes on port(s) {0}:".format(ports)) rc = self.__set_port_attr(ports, attr_dict) self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + # automatic resolve + if resolve: + # find any port with a dest configured as IPv4 + resolve_ports = [port_id for port_id, port_dest in zip(ports, dest) if is_valid_ipv4(port_dest)] + + if resolve_ports: + self.resolve(ports = resolve_ports) + + + + + @__api_check(True) + def resolve (self, ports = None, retries = 0): + """ + Resolves ports (ARP resolution) + + :parameters: + ports - for which ports to apply a unique sniffer (each port gets a unique file) + retires - how many times to retry on each port (intervals of 100 milliseconds) + :raises: + + :exe:'STLError' + + """ + # by default - resolve all the ports that are configured with IPv4 dest + ports = ports if ports is not None else self.get_resolvable_ports() + ports = self._validate_port_list(ports) + + active_ports = list_intersect(ports, self.get_active_ports()) + if active_ports: + raise STLError('Port(s) {0} are active, please stop them before resolving'.format(active_ports)) + + + self.logger.pre_cmd('Resolving destination on port(s) {0}:'.format(ports)) + with self.logger.supress(): + rc = self.__resolve(ports, retries) + + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + # print the ARP transaction + self.logger.log(rc) + self.logger.log('') + + + + @__api_check(True) + def set_rx_sniffer (self, ports = None, base_filename = 'rx.pcap', limit = 1000): + """ + Sets a RX sniffer for port(s) written to a PCAP file + + :parameters: + ports - for which ports to apply a unique sniffer (each port gets a unique file) + base_filename - filename will be appended with '-<port_number>', e.g. rx.pcap --> rx-0.pcap, rx-1.pcap etc. + limit - limit how many packets will be written + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + # check arguments + validate_type('base_filename', base_filename, basestring) + validate_type('limit', limit, (int)) + if limit <= 0: + raise STLError("'limit' must be a positive value") + + self.logger.pre_cmd("Setting RX sniffers on port(s) {0}:".format(ports)) + rc = self.__set_rx_sniffer(ports, base_filename, limit) + self.logger.post_cmd(rc) + + + if not rc: + raise STLError(rc) + + + + @__api_check(True) + def remove_rx_sniffer (self, ports = None): + """ + Removes RX sniffer from port(s) + + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + self.logger.pre_cmd("Removing RX sniffers on port(s) {0}:".format(ports)) + rc = self.__remove_rx_sniffer(ports) + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + @__api_check(True) + def set_rx_queue (self, ports = None, size = 1000): + """ + Sets RX queue for port(s) + The queue is cyclic and will hold last 'size' packets + + :parameters: + ports - for which ports to apply a queue + size - size of the queue + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + # check arguments + validate_type('size', size, (int)) + if size <= 0: + raise STLError("'size' must be a positive value") + + self.logger.pre_cmd("Setting RX queue on port(s) {0}:".format(ports)) + rc = self.__set_rx_queue(ports, size) + self.logger.post_cmd(rc) + + + if not rc: + raise STLError(rc) + + + + @__api_check(True) + def remove_rx_queue (self, ports = None): + """ + Removes RX queue from port(s) + + :parameters: + ports - for which ports to remove the RX queue + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + self.logger.pre_cmd("Removing RX queue on port(s) {0}:".format(ports)) + rc = self.__remove_rx_queue(ports) + self.logger.post_cmd(rc) if not rc: raise STLError(rc) + + + @__api_check(True) + def get_rx_queue_pkts (self, ports = None): + """ + Returns any packets queued on the RX side by the server + return value is a dictonary per port + + :parameters: + ports - for which ports to fetch + """ + + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + rc = self.__get_rx_queue_pkts(ports) + if not rc: + raise STLError(rc) + + # decode the data back to the user + result = {} + for port, r in zip(ports, rc.data()): + result[port] = r + + return result + + def clear_events (self): """ Clear all events @@ -2718,10 +3092,29 @@ class STLClient(object): @__console def ping_line (self, line): - '''pings the server''' - self.ping() - return RC_OK() + '''pings the server / specific IP''' + + # no parameters - so ping server + if not line: + self.ping_rpc_server() + return True + + parser = parsing_opts.gen_parser(self, + "ping", + self.ping_line.__doc__, + parsing_opts.SOURCE_PORT, + parsing_opts.PING_IPV4, + parsing_opts.PKT_SIZE, + parsing_opts.PING_COUNT) + + opts = parser.parse_args(line.split()) + if not opts: + return opts + + # IP ping + self.ping_ip(opts.source_port, opts.ping_ipv4, opts.pkt_size, opts.count) + @__console def shutdown_line (self, line): '''shutdown the server''' @@ -2849,13 +3242,14 @@ class STLClient(object): parser = parsing_opts.gen_parser(self, "reset", self.reset_line.__doc__, - parsing_opts.PORT_LIST_WITH_ALL) + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.PORT_RESTART) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) if not opts: return opts - self.reset(ports = opts.ports) + self.reset(ports = opts.ports, restart = opts.restart) return RC_OK() @@ -2891,14 +3285,19 @@ class STLClient(object): # just for sanity - will be checked on the API as well self.__decode_core_mask(opts.ports, core_mask) + # for better use experience - check this first + try: + self.__pre_start_check(opts.ports, opts.force) + except STLError as e: + msg = e.brief() + self.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) + + + # stop ports if needed 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 RC_ERR(msg) - else: - self.stop(active_ports) + if active_ports and opts.force: + self.stop(active_ports) # process tunables @@ -3240,7 +3639,7 @@ class STLClient(object): '''Sets port attributes ''' parser = parsing_opts.gen_parser(self, - "port_attr", + "portattr", self.set_port_attr_line.__doc__, parsing_opts.PORT_LIST_WITH_ALL, parsing_opts.PROMISCUOUS, @@ -3248,24 +3647,27 @@ class STLClient(object): parsing_opts.LED_STATUS, parsing_opts.FLOW_CTRL, parsing_opts.SUPPORTED, + parsing_opts.RX_FILTER_MODE, + parsing_opts.IPV4, + parsing_opts.DEST ) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) if not opts: return opts - opts.prom = parsing_opts.ON_OFF_DICT.get(opts.prom) - opts.link = parsing_opts.UP_DOWN_DICT.get(opts.link) - opts.led = parsing_opts.ON_OFF_DICT.get(opts.led) - opts.flow_ctrl = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl) + opts.prom = parsing_opts.ON_OFF_DICT.get(opts.prom) + opts.link = parsing_opts.UP_DOWN_DICT.get(opts.link) + opts.led = parsing_opts.ON_OFF_DICT.get(opts.led) + opts.flow_ctrl = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl) # if no attributes - fall back to printing the status - if not filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp]): + if not list(filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp, opts.rx_filter_mode, opts.ipv4, opts.dest])): self.show_stats_line("--ps --port {0}".format(' '.join(str(port) for port in opts.ports))) return if opts.supp: - info = self.ports[0].get_info() # assume for now all ports are same + info = self.ports[0].get_formatted_info() # assume for now all ports are same print('') print('Supported attributes for current NICs:') print(' Promiscuous: yes') @@ -3274,15 +3676,79 @@ class STLClient(object): print(' Flow control: %s' % info['fc_supported']) print('') else: - return self.set_port_attr(opts.ports, opts.prom, opts.link, opts.led, opts.flow_ctrl) + self.set_port_attr(opts.ports, + opts.prom, + opts.link, + opts.led, + opts.flow_ctrl, + opts.rx_filter_mode, + opts.ipv4, + opts.dest) + + + + + @__console + def set_rx_sniffer_line (self, line): + '''Sets a port sniffer on RX channel in form of a PCAP file''' + + parser = parsing_opts.gen_parser(self, + "set_rx_sniffer", + self.set_rx_sniffer_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.OUTPUT_FILENAME, + parsing_opts.LIMIT, + parsing_opts.ALL_FILES) + + opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) + if not opts: + return opts + + rxf = 'all' if opts.all else None + + if rxf: + self.set_port_attr(opts.ports, rxf = rxf) + + self.set_rx_sniffer(opts.ports, opts.output_filename, opts.limit) + return RC_OK() + @__console + def resolve_line (self, line): + '''Performs a port ARP resolution''' + + parser = parsing_opts.gen_parser(self, + "resolve", + self.resolve_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.RETRIES) + + opts = parser.parse_args(line.split(), default_ports = self.get_resolvable_ports(), verify_acquired = True) + if not opts: + return opts + + ports = list_intersect(opts.ports, self.get_resolvable_ports()) + if not ports: + if not opts.ports: + msg = 'resolve - no ports with IPv4 destination' + else: + msg = 'pause - none of ports {0} are configured with IPv4 destination'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) + + self.resolve(ports = ports, retries = opts.retries) + + return RC_OK() + + + @__console def show_profile_line (self, line): '''Shows profile information''' parser = parsing_opts.gen_parser(self, - "port", + "profile", self.show_profile_line.__doc__, parsing_opts.FILE_PATH) @@ -3378,3 +3844,6 @@ class STLClient(object): else: return "{0} {1}>".format(prefix, self.get_acquired_ports()) + + + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py index 1461fcec..93a930e4 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py @@ -172,6 +172,10 @@ class JsonRpcClient(object): self.disconnect() return RC_ERR("*** [RPC] - Failed to send message to server") + except KeyboardInterrupt as e: + # must restore the socket to a sane state + self.reconnect() + raise e tries = 0 while True: @@ -184,6 +188,10 @@ class JsonRpcClient(object): self.disconnect() return RC_ERR("*** [RPC] - Failed to get server response from {0}".format(self.transport)) + except KeyboardInterrupt as e: + # must restore the socket to a sane state + self.reconnect() + raise e return response @@ -255,11 +263,6 @@ class JsonRpcClient(object): self.connected = True - rc = self.invoke_rpc_method('ping', api_class = None) - if not rc: - self.connected = False - return rc - return RC_OK() @@ -267,12 +270,6 @@ class JsonRpcClient(object): # connect using current values return self.connect() - if not self.connected: - return RC_ERR("Not connected to server") - - # reconnect - return self.connect(self.server, self.port) - def is_connected(self): return self.connected diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py index cec3761f..ef74a85e 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py @@ -4,12 +4,14 @@ from collections import namedtuple, OrderedDict from .trex_stl_packet_builder_scapy import STLPktBuilder from .trex_stl_streams import STLStream from .trex_stl_types import * +from .trex_stl_rx_features import ARPResolver, PingResolver from . import trex_stl_stats from .utils.constants import FLOW_CTRL_DICT_REVERSED import base64 import copy from datetime import datetime, timedelta +import threading StreamOnPort = namedtuple('StreamOnPort', ['compiled_stream', 'metadata']) @@ -50,7 +52,9 @@ class Port(object): def __init__ (self, port_id, user, comm_link, session_id, info): self.port_id = port_id + self.state = self.STATE_IDLE + self.handler = None self.comm_link = comm_link self.transmit = comm_link.transmit @@ -62,7 +66,7 @@ class Port(object): self.streams = {} self.profile = None self.session_id = session_id - self.attr = {} + self.status = {} self.port_stats = trex_stl_stats.CPortStats(self) @@ -72,31 +76,31 @@ class Port(object): self.owner = '' self.last_factor_type = None - + + self.__attr = {} + self.attr_lock = threading.Lock() + # decorator to verify port is up def up(func): - def func_wrapper(*args): + def func_wrapper(*args, **kwargs): port = args[0] if not port.is_up(): return port.err("{0} - port is down".format(func.__name__)) - return func(*args) + return func(*args, **kwargs) return func_wrapper # owned def owned(func): - def func_wrapper(*args): + def func_wrapper(*args, **kwargs): port = args[0] - if not port.is_up(): - return port.err("{0} - port is down".format(func.__name__)) - if not port.is_acquired(): return port.err("{0} - port is not owned".format(func.__name__)) - return func(*args) + return func(*args, **kwargs) return func_wrapper @@ -106,9 +110,6 @@ class Port(object): def func_wrapper(*args, **kwargs): port = args[0] - if not port.is_up(): - return port.err("{0} - port is down".format(func.__name__)) - if not port.is_acquired(): return port.err("{0} - port is not owned".format(func.__name__)) @@ -128,16 +129,16 @@ class Port(object): return RC_OK(data) def get_speed_bps (self): - return (self.info['speed'] * 1000 * 1000 * 1000) + return (self.get_speed_gbps() * 1000 * 1000 * 1000) - def get_formatted_speed (self): - return "{0} Gbps".format(self.info['speed']) + def get_speed_gbps (self): + return self.__attr['speed'] def is_acquired(self): return (self.handler != None) def is_up (self): - return (self.state != self.STATE_DOWN) + return self.__attr['link']['up'] def is_active(self): return (self.state == self.STATE_TX ) or (self.state == self.STATE_PAUSE) or (self.state == self.STATE_PCAP_TX) @@ -165,7 +166,6 @@ class Port(object): # take the port - @up def acquire(self, force = False, sync_streams = True): params = {"port_id": self.port_id, "user": self.user, @@ -185,7 +185,6 @@ class Port(object): # sync all the streams with the server - @up def sync_streams (self): params = {"port_id": self.port_id} @@ -201,7 +200,6 @@ class Port(object): return self.ok() # release the port - @up def release(self): params = {"port_id": self.port_id, "handler": self.handler} @@ -219,7 +217,6 @@ class Port(object): - @up def sync(self): params = {"port_id": self.port_id} @@ -250,10 +247,10 @@ class Port(object): self.next_available_id = int(rc.data()['max_stream_id']) + 1 - # attributes - self.attr = rc.data()['attr'] - if 'speed' in rc.data(): - self.info['speed'] = rc.data()['speed'] // 1000 + self.status = rc.data() + + # replace the attributes in a thread safe manner + self.set_ts_attr(rc.data()['attr']) return self.ok() @@ -424,8 +421,8 @@ class Port(object): # save this for TUI self.last_factor_type = mul['type'] - - return self.ok() + + return rc # stop traffic @@ -445,8 +442,9 @@ class Port(object): return self.err(rc.err()) self.state = self.STATE_STREAMS + self.last_factor_type = None - + # timestamp for last tx self.tx_stopped_ts = datetime.now() @@ -487,6 +485,101 @@ class Port(object): return self.ok() + + @owned + def set_rx_sniffer (self, pcap_filename, limit): + + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "capture", + "enabled": True, + "pcap_filename": pcap_filename, + "limit": limit} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + + @owned + def remove_rx_sniffer (self): + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "capture", + "enabled": False} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + + @owned + def set_arp_resolution (self, ipv4, mac): + + params = {"handler": self.handler, + "port_id": self.port_id, + "ipv4": ipv4, + "mac": mac} + + rc = self.transmit("set_arp_resolution", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + + + + @owned + def set_rx_queue (self, size): + + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "queue", + "enabled": True, + "size": size} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + @owned + def remove_rx_queue (self): + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "queue", + "enabled": False} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + @owned + def get_rx_queue_pkts (self): + params = {"handler": self.handler, + "port_id": self.port_id} + + rc = self.transmit("get_rx_queue_pkts", params) + if rc.bad(): + return self.err(rc.err()) + + pkts = rc.data()['pkts'] + + # decode the packets from base64 to binary + for i in range(len(pkts)): + pkts[i]['binary'] = base64.b64decode(pkts[i]['binary']) + + return RC_OK(pkts) + + @owned def pause (self): @@ -568,21 +661,44 @@ class Port(object): @owned - def set_attr (self, attr_dict): + def set_attr (self, **kwargs): + + json_attr = {} + + if kwargs.get('promiscuous') is not None: + json_attr['promiscuous'] = {'enabled': kwargs.get('promiscuous')} + + if kwargs.get('link_status') is not None: + json_attr['link_status'] = {'up': kwargs.get('link_status')} + + if kwargs.get('led_status') is not None: + json_attr['led_status'] = {'on': kwargs.get('led_status')} + + if kwargs.get('flow_ctrl_mode') is not None: + json_attr['flow_ctrl_mode'] = {'on': kwargs.get('flow_ctrl_mode')} + + if kwargs.get('rx_filter_mode') is not None: + json_attr['rx_filter_mode'] = {'mode': kwargs.get('rx_filter_mode')} + + if kwargs.get('ipv4') is not None: + json_attr['ipv4'] = {'addr': kwargs.get('ipv4')} + + if kwargs.get('dest') is not None: + json_attr['dest'] = {'addr': kwargs.get('dest')} + params = {"handler": self.handler, "port_id": self.port_id, - "attr": attr_dict} + "attr": json_attr} rc = self.transmit("set_port_attr", params) if rc.bad(): return self.err(rc.err()) + # update the dictionary from the server explicitly + return self.sync() - #self.attr.update(attr_dict) - - return self.ok() - + @writeable def push_remote (self, pcap_filename, ipg_usec, speedup, count, duration, is_dual, slave_handler): @@ -607,7 +723,16 @@ class Port(object): def get_profile (self): return self.profile - + # invalidates the current ARP + def invalidate_arp (self): + dest = self.__attr['dest'] + + if dest['type'] != 'mac': + return self.set_attr(dest = dest['ipv4']) + else: + return self.ok() + + def print_profile (self, mult, duration): if not self.get_profile(): return @@ -648,24 +773,32 @@ class Port(object): format_time(exp_time_factor_sec))) print("\n") - # generate port info - def get_info (self): + # generate formatted (console friendly) port info + def get_formatted_info (self, sync = True): + + # sync the status + if sync: + self.sync() + + # get a copy of the current attribute set (safe against manipulation) + attr = self.get_ts_attr() + info = dict(self.info) info['status'] = self.get_port_state_name() - if 'link' in self.attr: - info['link'] = 'UP' if self.attr['link']['up'] else 'DOWN' + if 'link' in attr: + info['link'] = 'UP' if attr['link']['up'] else 'DOWN' else: info['link'] = 'N/A' - if 'fc' in self.attr: - info['fc'] = FLOW_CTRL_DICT_REVERSED.get(self.attr['fc']['mode'], 'N/A') + if 'fc' in attr: + info['fc'] = FLOW_CTRL_DICT_REVERSED.get(attr['fc']['mode'], 'N/A') else: info['fc'] = 'N/A' - if 'promiscuous' in self.attr: - info['prom'] = "on" if self.attr['promiscuous']['enabled'] else "off" + if 'promiscuous' in attr: + info['prom'] = "on" if attr['promiscuous']['enabled'] else "off" else: info['prom'] = "N/A" @@ -692,34 +825,122 @@ class Port(object): else: info['is_virtual'] = 'N/A' + # speed + info['speed'] = self.get_speed_gbps() + + # RX filter mode + info['rx_filter_mode'] = 'hardware match' if attr['rx_filter_mode'] == 'hw' else 'fetch all' + + # src MAC and IPv4 + info['src_mac'] = attr['src_mac'] + info['src_ipv4'] = attr['src_ipv4'] + + if info['src_ipv4'] is None: + info['src_ipv4'] = 'Not Configured' + + # dest + dest = attr['dest'] + if dest['type'] == 'mac': + info['dest'] = dest['mac'] + info['arp'] = '-' + + elif dest['type'] == 'ipv4': + info['dest'] = dest['ipv4'] + info['arp'] = dest['arp'] + + elif dest['type'] == 'ipv4_u': + info['dest'] = dest['ipv4'] + info['arp'] = 'unresolved' + + + # RX info + rx_info = self.status['rx_info'] + + # RX sniffer + sniffer = rx_info['sniffer'] + info['rx_sniffer'] = '{0}\n[{1} / {2}]'.format(sniffer['pcap_filename'], sniffer['count'], sniffer['limit']) if sniffer['is_active'] else 'off' + + + # RX queue + queue = rx_info['queue'] + info['rx_queue'] = '[{0} / {1}]'.format(queue['count'], queue['size']) if queue['is_active'] else 'off' + return info def get_port_state_name(self): return self.STATES_MAP.get(self.state, "Unknown") + def get_src_addr (self): + src_mac = self.__attr['src_mac'] + src_ipv4 = self.__attr['src_ipv4'] + + return {'mac': src_mac, 'ipv4': src_ipv4} + + + def get_dst_addr (self): + dest = self.__attr['dest'] + + if dest['type'] == 'mac': + return {'ipv4': None, 'mac': dest['mac']} + + elif dest['type'] == 'ipv4': + return {'ipv4': dest['ipv4'], 'mac': dest['arp']} + + elif dest['type'] == 'ipv4_u': + return {'ipv4': dest['ipv4'], 'mac': None} + + else: + assert(0) + + + # port is considered resolved if it's dest is either MAC or resolved IPv4 + def is_resolved (self): + return (self.get_dst_addr()['mac'] is not None) + + # return True if the port is valid for resolve (has an IPv4 address as dest) + def is_resolvable (self): + return (self.get_dst_addr()['ipv4'] is not None) + + @writeable + def arp_resolve (self, retries): + return ARPResolver(self).resolve(retries) + + @writeable + def ping (self, ping_ipv4, pkt_size): + return PingResolver(self, ping_ipv4, pkt_size).resolve() + + ################# stats handler ###################### def generate_port_stats(self): return self.port_stats.generate_stats() def generate_port_status(self): - info = self.get_info() + info = self.get_formatted_info() - return {"driver": info['driver'], - "description": info.get('description', 'N/A')[:18], - "HW src mac": info['hw_macaddr'], - "SW src mac": info['src_macaddr'], - "SW dst mac": info['dst_macaddr'], - "PCI Address": info['pci_addr'], - "NUMA Node": info['numa'], + return {"driver": info['driver'], + "description": info.get('description', 'N/A')[:18], + "src MAC": info['src_mac'], + "src IPv4": info['src_ipv4'], + "Destination": info['dest'], + "ARP Resolution": info['arp'], + "PCI Address": info['pci_addr'], + "NUMA Node": info['numa'], "--": "", "---": "", - "link speed": "{speed} Gb/s".format(speed=info['speed']), + "----": "", + "-----": "", + "link speed": info['speed'], "port status": info['status'], "link status": info['link'], "promiscuous" : info['prom'], "flow ctrl" : info['fc'], + + "RX Filter Mode": info['rx_filter_mode'], + "RX Queueing": info['rx_queue'], + "RX sniffer": info['rx_sniffer'], + } def clear_stats(self): @@ -756,17 +977,54 @@ class Port(object): return {"streams" : OrderedDict(sorted(data.items())) } - + ######## attributes are a complex type (dict) that might be manipulated through the async thread ############# + + # get in a thread safe manner a duplication of attributes + def get_ts_attr (self): + with self.attr_lock: + return dict(self.__attr) + + # set in a thread safe manner a new dict of attributes + def set_ts_attr (self, new_attr): + with self.attr_lock: + self.__attr = new_attr + + ################# events handler ###################### def async_event_port_job_done (self): # until thread is locked - order is important self.tx_stopped_ts = datetime.now() self.state = self.STATE_STREAMS + self.last_factor_type = None - def async_event_port_attr_changed (self, attr): - self.info['speed'] = attr['speed'] // 1000 - self.attr = attr + def async_event_port_attr_changed (self, new_attr): + + # get a thread safe duplicate + cur_attr = self.get_ts_attr() + + # check if anything changed + if new_attr == cur_attr: + return None + + # generate before + before = self.get_formatted_info(sync = False) + + # update + self.set_ts_attr(new_attr) + + # generate after + after = self.get_formatted_info(sync = False) + + # return diff + diff = {} + for key, new_value in after.items(): + old_value = before.get(key, 'N/A') + if new_value != old_value: + diff[key] = (old_value, new_value) + + return diff + # rest of the events are used for TUI / read only sessions def async_event_port_stopped (self): @@ -792,3 +1050,4 @@ class Port(object): def async_event_released (self): self.owner = '' + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_rx_features.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_rx_features.py new file mode 100644 index 00000000..3754e608 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_rx_features.py @@ -0,0 +1,255 @@ + +from .trex_stl_streams import STLStream, STLTXSingleBurst +from .trex_stl_packet_builder_scapy import STLPktBuilder + +from scapy.layers.l2 import Ether, ARP +from scapy.layers.inet import IP, ICMP + +import time + +# a generic abstract class for resolving using the server +class Resolver(object): + def __init__ (self, port, queue_size = 100): + self.port = port + + # code to execute before sending any request - return RC object + def pre_send (self): + raise NotImplementedError() + + # return a list of streams for request + def generate_request (self): + raise NotImplementedError() + + # return None for more packets otherwise RC object + def on_pkt_rx (self, pkt): + raise NotImplementedError() + + # return value in case of timeout + def on_timeout_err (self, retries): + raise NotImplementedError() + + ##################### API ###################### + def resolve (self, retries = 0): + + # first cleanup + rc = self.port.remove_all_streams() + if not rc: + return rc + + # call the specific class implementation + rc = self.pre_send() + if not rc: + return rc + + # start the iteration + try: + + # add the stream(s) + self.port.add_streams(self.generate_request()) + + rc = self.port.set_attr(rx_filter_mode = 'all') + if not rc: + return rc + + rc = self.port.set_rx_queue(size = 100) + if not rc: + return rc + + return self.resolve_wrapper(retries) + + finally: + # best effort restore + self.port.set_attr(rx_filter_mode = 'hw') + self.port.remove_rx_queue() + self.port.remove_all_streams() + + + # main resolve function + def resolve_wrapper (self, retries): + + # retry for 'retries' + index = 0 + while True: + rc = self.resolve_iteration() + if rc is not None: + return rc + + if index >= retries: + return self.on_timeout_err(retries) + + index += 1 + time.sleep(0.1) + + + + def resolve_iteration (self): + + mult = {'op': 'abs', 'type' : 'percentage', 'value': 100} + rc = self.port.start(mul = mult, force = False, duration = -1, mask = 0xffffffff) + if not rc: + return rc + + # save the start timestamp + self.start_ts = rc.data()['ts'] + + # block until traffic finishes + while self.port.is_active(): + time.sleep(0.01) + + return self.wait_for_rx_response() + + + def wait_for_rx_response (self): + + # we try to fetch response for 5 times + polling = 5 + + while polling > 0: + + # fetch the queue + rx_pkts = self.port.get_rx_queue_pkts() + + # might be an error + if not rx_pkts: + return rx_pkts + + # for each packet - examine it + for pkt in rx_pkts.data(): + rc = self.on_pkt_rx(pkt) + if rc is not None: + return rc + + if polling == 0: + return None + + polling -= 1 + time.sleep(0.1) + + + + + +class ARPResolver(Resolver): + def __init__ (self, port_id): + super(ARPResolver, self).__init__(port_id) + + # before resolve + def pre_send (self): + self.dst = self.port.get_dst_addr() + self.src = self.port.get_src_addr() + + if self.dst['ipv4'] is None: + return self.port.err("Port has a non-IPv4 destination: '{0}'".format(self.dst['mac'])) + + if self.src['ipv4'] is None: + return self.port.err('Port must have an IPv4 source address configured') + + # invalidate the current ARP resolution (if exists) + return self.port.invalidate_arp() + + + # return a list of streams for request + def generate_request (self): + + base_pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(psrc = self.src['ipv4'], pdst = self.dst['ipv4'], hwsrc = self.src['mac']) + s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) ) + + return [s1] + + + # return None in case more packets are needed else the status rc + def on_pkt_rx (self, pkt): + scapy_pkt = Ether(pkt['binary']) + if not 'ARP' in scapy_pkt: + return None + + arp = scapy_pkt['ARP'] + + # check this is the right ARP (ARP reply with the address) + if (arp.op != 2) or (arp.psrc != self.dst['ipv4']): + return None + + + rc = self.port.set_arp_resolution(arp.psrc, arp.hwsrc) + if not rc: + return rc + + return self.port.ok('Port {0} - Recieved ARP reply from: {1}, hw: {2}'.format(self.port.port_id, arp.psrc, arp.hwsrc)) + + + def on_timeout_err (self, retries): + return self.port.err('failed to receive ARP response ({0} retries)'.format(retries)) + + + + + #################### ping resolver #################### + +class PingResolver(Resolver): + def __init__ (self, port, ping_ip, pkt_size): + super(PingResolver, self).__init__(port) + self.ping_ip = ping_ip + self.pkt_size = pkt_size + + def pre_send (self): + + self.src = self.port.get_src_addr() + self.dst = self.port.get_dst_addr() + + if self.src['ipv4'] is None: + return self.port.err('Ping - port does not have an IPv4 address configured') + + if self.dst['mac'] is None: + return self.port.err('Ping - port has an unresolved destination, cannot determine next hop MAC address') + + if self.ping_ip == self.src['ipv4']: + return self.port.err('Ping - cannot ping own IP') + + return self.port.ok() + + + # return a list of streams for request + def generate_request (self): + + src = self.port.get_src_addr() + dst = self.port.get_dst_addr() + + base_pkt = Ether(dst = dst['mac'])/IP(src = src['ipv4'], dst = self.ping_ip)/ICMP(type = 8) + pad = max(0, self.pkt_size - len(base_pkt)) + + base_pkt = base_pkt / (pad * 'x') + + #base_pkt.show2() + s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) ) + + return [s1] + + # return None for more packets otherwise RC object + def on_pkt_rx (self, pkt): + scapy_pkt = Ether(pkt['binary']) + if not 'ICMP' in scapy_pkt: + return None + + #scapy_pkt.show2() + ip = scapy_pkt['IP'] + + icmp = scapy_pkt['ICMP'] + + dt = pkt['ts'] - self.start_ts + + if icmp.type == 0: + # echo reply + return self.port.ok('Reply from {0}: bytes={1}, time={2:.2f}ms, TTL={3}'.format(ip.src, len(pkt['binary']), dt * 1000, ip.ttl)) + + # unreachable + elif icmp.type == 3: + return self.port.ok('Reply from {0}: Destination host unreachable'.format(icmp.src)) + else: + scapy_pkt.show2() + return self.port.err('unknown ICMP reply') + + + + # return the str of a timeout err + def on_timeout_err (self, retries): + return self.port.ok('Request timed out.') 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 9f601484..6a59126f 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 @@ -670,12 +670,18 @@ class CTRexInfoGenerator(object): ("promiscuous", []), ("flow ctrl", []), ("--", []), - ("HW src mac", []), - ("SW src mac", []), - ("SW dst mac", []), + ("src IPv4", []), + ("src MAC", []), ("---", []), + ("Destination", []), + ("ARP Resolution", []), + ("----", []), ("PCI Address", []), ("NUMA Node", []), + ("-----", []), + ("RX Filter Mode", []), + ("RX Queueing", []), + ("RX sniffer", []), ] ) @@ -1103,13 +1109,7 @@ class CPortStats(CTRexStats): port_state = format_text(port_state, 'bold') if self._port_obj: - if 'link' in self._port_obj.attr: - if self._port_obj.attr.get('link', {}).get('up') == False: - link_state = format_text('DOWN', 'red', 'bold') - else: - link_state = 'UP' - else: - link_state = 'N/A' + link_state = 'UP' if self._port_obj.is_up() else format_text('DOWN', 'red', 'bold') else: link_state = '' @@ -1130,7 +1130,7 @@ class CPortStats(CTRexStats): return {"owner": owner, "state": "{0}".format(port_state), 'link': link_state, - "speed": self._port_obj.get_formatted_speed() if self._port_obj else '', + "speed": "%g Gb/s" % self._port_obj.get_speed_gbps() if self._port_obj else '', "CPU util.": "{0} {1}%".format(self.get_trend_gui("m_cpu_util", use_raw = True), format_threshold(round_float(self.get("m_cpu_util")), [85, 100], [0, 85])) if self._port_obj else '' , "--": " ", diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py index aa6c4218..81015ddc 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py @@ -135,6 +135,12 @@ def validate_type(arg_name, arg, valid_types): else: raise STLError('validate_type: valid_types should be type or list or tuple of types') + +def validate_choice (arg_name, arg, choices): + if arg is not None and not arg in choices: + raise STLError("validate_choice: argument '{0}' can only be one of '{1}'".format(arg_name, choices)) + + # throws STLError if not exactly one argument is present def verify_exclusive_arg (args_list): if not (len(list(filter(lambda x: x is not None, args_list))) == 1): diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py index 72ee8972..cbbacb27 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py @@ -3,6 +3,8 @@ import sys import string import random import time +import socket +import re try: import pwd @@ -86,3 +88,23 @@ class PassiveTimer(object): return (time.time() > self.expr_sec) +def is_valid_ipv4 (addr): + try: + socket.inet_pton(socket.AF_INET, addr) + return True + except (socket.error, TypeError): + return False + +def is_valid_mac (mac): + return bool(re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower())) + +def list_remove_dup (l): + tmp = list() + + for x in l: + if not x in tmp: + tmp.append(x) + + return tmp + + 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 34cafd79..7ae22e89 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 @@ -1,6 +1,6 @@ import argparse from collections import namedtuple, OrderedDict -from .common import list_intersect, list_difference +from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_mac, list_remove_dup from .text_opts import format_text from ..trex_stl_types import * from .constants import ON_OFF_DICT, UP_DOWN_DICT, FLOW_CTRL_DICT @@ -45,6 +45,21 @@ FLOW_CTRL = 28 SUPPORTED = 29 FILE_PATH_NO_CHECK = 30 +OUTPUT_FILENAME = 31 +ALL_FILES = 32 +LIMIT = 33 +PORT_RESTART = 34 + +IPV4 = 35 +DEST = 36 +RETRIES = 37 + +RX_FILTER_MODE = 38 +SOURCE_PORT = 39 +PING_IPV4 = 40 +PING_COUNT = 41 +PKT_SIZE = 42 + GLOBAL_STATS = 50 PORT_STATS = 51 PORT_STATUS = 52 @@ -218,8 +233,30 @@ def is_valid_file(filename): return filename +def check_ipv4_addr (ipv4_str): + if not is_valid_ipv4(ipv4_str): + raise argparse.ArgumentTypeError("invalid IPv4 address: '{0}'".format(ipv4_str)) + + return ipv4_str +def check_pkt_size (pkt_size): + try: + pkt_size = int(pkt_size) + except ValueError: + raise argparse.ArgumentTypeError("invalid packet size type: '{0}'".format(pkt_size)) + + if (pkt_size < 64) or (pkt_size > 9216): + raise argparse.ArgumentTypeError("invalid packet size: '{0}' - valid range is 64 to 9216".format(pkt_size)) + + return pkt_size + +def check_dest_addr (addr): + if not (is_valid_ipv4(addr) or is_valid_mac(addr)): + raise argparse.ArgumentTypeError("not a valid IPv4 or MAC address: '{0}'".format(addr)) + + return addr + def decode_tunables (tunable_str): tunables = {} @@ -304,6 +341,62 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'dest': 'flow_ctrl', 'choices': FLOW_CTRL_DICT}), + RX_FILTER_MODE: ArgumentPack(['--rxf'], + {'help': 'Set RX filtering mode', + 'dest': 'rx_filter_mode', + 'choices': ['hw', 'all']}), + + + IPV4: ArgumentPack(['--ipv4'], + {'help': 'IPv4 address(s) for the port(s)', + 'dest': 'ipv4', + 'nargs': '+', + 'default': None, + 'type': check_ipv4_addr}), + + DEST: ArgumentPack(['--dest'], + {'help': 'Destination address(s) for the port(s) in either IPv4 or MAC format', + 'dest': 'dest', + 'nargs': '+', + 'default': None, + 'type': check_dest_addr}), + + RETRIES: ArgumentPack(['-r', '--retries'], + {'help': 'retries count [default is zero]', + 'dest': 'retries', + 'default': 0, + 'type': int}), + + + OUTPUT_FILENAME: ArgumentPack(['-o', '--output'], + {'help': 'Output PCAP filename', + 'dest': 'output_filename', + 'default': None, + 'required': True, + 'type': str}), + + + PORT_RESTART: ArgumentPack(['-r', '--restart'], + {'help': 'hard restart port(s)', + 'dest': 'restart', + 'default': False, + 'action': 'store_true'}), + + + ALL_FILES: ArgumentPack(['--all'], + {'help': 'change RX port filter to fetch all packets', + 'dest': 'all', + 'default': False, + 'action': "store_true"}), + + + LIMIT: ArgumentPack(['-l', '--limit'], + {'help': 'Limit the packet count to be written to the file', + 'dest': 'limit', + 'default': 1000, + 'type': int}), + + SUPPORTED: ArgumentPack(['--supp'], {'help': 'Show which attributes are supported by current NICs', 'default': None, @@ -326,6 +419,32 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'help': "A list of ports on which to apply the command", 'default': []}), + + SOURCE_PORT: ArgumentPack(['--port', '-p'], + {'dest':'source_port', + 'type': int, + 'help': 'source port for the action', + 'required': True}), + + PING_IPV4: ArgumentPack(['-d'], + {'help': 'which IPv4 to ping', + 'dest': 'ping_ipv4', + 'required': True, + 'type': check_ipv4_addr}), + + PING_COUNT: ArgumentPack(['-n', '--count'], + {'help': 'How many times to ping [default is 5]', + 'dest': 'count', + 'default': 5, + 'type': int}), + + PKT_SIZE: ArgumentPack(['-s'], + {'dest':'pkt_size', + 'help': 'packet size to use', + 'default': 64, + 'type': check_pkt_size}), + + ALL_PORTS: ArgumentPack(['-a'], {"action": "store_true", "dest": "all_ports", @@ -461,6 +580,7 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], ALL_PORTS], {'required': False}), + STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH, FILE_FROM_DB], {'required': True}), @@ -531,6 +651,8 @@ class CCmdArgParser(argparse.ArgumentParser): else: opts.ports = default_ports + opts.ports = list_remove_dup(opts.ports) + # so maybe we have ports configured invalid_ports = list_difference(opts.ports, self.stateless_client.get_all_ports()) if invalid_ports: diff --git a/scripts/t-rex-64 b/scripts/t-rex-64 index 5515ba03..fc8318a5 100755 --- a/scripts/t-rex-64 +++ b/scripts/t-rex-64 @@ -26,7 +26,7 @@ cd $(dirname $0) export LD_LIBRARY_PATH=$PWD #Add dummy lib in case we don't find it, e.g. there is no OFED installed -if ldd _t-rex-64 | grep "libibverbs.so" | grep -q "not found"; then +if ldd _$(basename $0) | grep "libibverbs.so" | grep -q "not found"; then export LD_LIBRARY_PATH=$PWD:$PWD/dumy_libs fi diff --git a/scripts/t-rex-64-valgrind b/scripts/t-rex-64-valgrind new file mode 100755 index 00000000..d11491ba --- /dev/null +++ b/scripts/t-rex-64-valgrind @@ -0,0 +1,69 @@ +#! /bin/bash +if [ "$(id -u)" != 0 ]; then + echo 'Error: Please run as root (sudo etc.)' + exit -1 +fi + +INPUT_ARGS=${@//[–—]/-} # replace bizarre minuses with normal one + +./trex-cfg $INPUT_ARGS +RESULT=$? +if [ $RESULT -ne 0 ]; then + exit $RESULT +fi + +pci_desc_re='^(\S+) - (.+)$' +source find_python.sh +while read line +do + if [[ "$line" =~ $pci_desc_re ]]; then + pci_name="pci$(echo ${BASH_REMATCH[1]} | tr ':' '_' | tr '.' '_')" # make alphanumeric name + export $pci_name="${BASH_REMATCH[2]}" + fi +done <<< "$($PYTHON dpdk_setup_ports.py --dump-pci-description)" + +cd $(dirname $0) +export LD_LIBRARY_PATH=$PWD + +#Add dummy lib in case we don't find it, e.g. there is no OFED installed +if ldd ./_t-rex-64 | grep "libibverbs.so" | grep -q "not found"; then +export LD_LIBRARY_PATH=$PWD:$PWD/dumy_libs +fi + +export VALGRIND_LIB=/auto/proj-pcube-b/apps/PL-b/tools/valgrind-dpdk/lib/valgrind +export VALGRIND_BIN="/auto/proj-pcube-b/apps/PL-b/tools/valgrind-dpdk/bin/valgrind --leak-check=full --error-exitcode=1 --suppressions=valgrind.sup" +export GLIBCXX_FORCE_NEW=1 + +if [ -t 0 ] && [ -t 1 ]; then + export is_tty=true + saveterm="$(stty -g)" +else + export is_tty=false +fi + +# if we have a new core run optimized trex +if grep -q avx /proc/cpuinfo ; then + $VALGRIND_BIN ./_t-rex-64 $INPUT_ARGS + RESULT=$? + if [ $RESULT -eq 132 ]; then + echo " WARNING this program is optimized for the new Intel processors. " + echo " try the ./t-rex-64-o application that should work for any Intel processor but might be slower. " + echo " try to run t-rex-64-o .. " + + $VALGRIND_BIN ./_t-rex-64 $INPUT_ARGS + RESULT=$? + fi +else + $VALGRIND_BIN ./_t-rex-64 $INPUT_ARGS + RESULT=$? +fi + +if $is_tty; then + stty $saveterm +fi + +if [ $RESULT -ne 0 ]; then + exit $RESULT +fi + + diff --git a/scripts/trex_show_threads.py b/scripts/trex_show_threads.py index fabe6d68..1824d073 100755 --- a/scripts/trex_show_threads.py +++ b/scripts/trex_show_threads.py @@ -58,8 +58,8 @@ def isnum (x): def find_trex_pid (): procs = [x for x in os.listdir('/proc/') if isnum(x)] for proc in procs: - cmd = open('/proc/{0}/{1}'.format(proc, 'cmdline')).readline() - if '_t-rex' in cmd: + cmd = open('/proc/{0}/{1}'.format(proc, 'comm')).readline() + if cmd.startswith('_t-rex-64'): return proc return None diff --git a/scripts/valgrind.sup b/scripts/valgrind.sup new file mode 100644 index 00000000..b6bcc883 --- /dev/null +++ b/scripts/valgrind.sup @@ -0,0 +1,57 @@ +{ + DL issue + Memcheck:Cond + fun:index + fun:expand_dynamic_string_token + fun:fillin_rpath + fun:_dl_init_paths + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start_final + fun:_dl_start + obj:/lib/x86_64-linux-gnu/ld-2.19.so + obj:* + obj:* + obj:* + obj:* +} + +{ + DPDK threads + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls + fun:allocate_stack + fun:pthread_create@@GLIBC_2.2.5 + fun:rte_eal_init + fun:_Z9main_testiPPc + fun:(below main) +} + +{ + DPDK interrupt thread + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls + fun:allocate_stack + fun:pthread_create@@GLIBC_2.2.5 + fun:rte_eal_intr_init + fun:rte_eal_init + fun:_Z9main_testiPPc + fun:(below main) +} + +{ + DPDK epoll ctl + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:eal_intr_thread_main + fun:start_thread + fun:clone +} + diff --git a/src/common/basic_utils.cpp b/src/common/basic_utils.cpp index 04e7aaba..fded49ec 100755 --- a/src/common/basic_utils.cpp +++ b/src/common/basic_utils.cpp @@ -20,6 +20,10 @@ limitations under the License. #include <sstream> #include <sys/resource.h> +#include "pal_utl.h" + +int my_inet_pton4(const char *src, unsigned char *dst); + bool utl_is_file_exists (const std::string& name) { if (FILE *file = fopen(name.c_str(), "r")) { fclose(file); @@ -174,13 +178,6 @@ void TestDump(void){ utl_DumpBuffer2(stdout,buffer,31,1,4,SHOW_BUFFER_ADDR_EN |SHOW_BUFFER_CHAR); } -std::string -utl_macaddr_to_str(const uint8_t *mac) { - std::string tmp; - utl_macaddr_to_str(mac, tmp); - return tmp; -} - void utl_macaddr_to_str(const uint8_t *macaddr, std::string &output) { for (int i = 0; i < 6; i++) { @@ -197,6 +194,26 @@ void utl_macaddr_to_str(const uint8_t *macaddr, std::string &output) { } +std::string utl_macaddr_to_str(const uint8_t *macaddr) { + std::string tmp; + utl_macaddr_to_str(macaddr, tmp); + + return tmp; +} + +bool utl_str_to_macaddr(const std::string &s, uint8_t *mac) { + int last = -1; + int rc = sscanf(s.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx%n", + mac + 0, mac + 1, mac + 2, mac + 3, mac + 4, mac + 5, + &last); + + if ( (rc != 6) || (s.size() != last) ) { + return false; + } + + return true; +} + /** * generate a random connection handler * @@ -255,3 +272,23 @@ void utl_set_coredump_size(long size, bool map_huge_pages) { fprintf(fp, "%08x\n", mask); fclose(fp); } + +bool utl_ipv4_to_uint32(const char *ipv4_str, uint32_t &ipv4_num) { + + uint32_t tmp; + + int rc = my_inet_pton4(ipv4_str, (unsigned char *)&tmp); + if (!rc) { + return false; + } + + ipv4_num = PAL_NTOHL(tmp); + + return true; +} + +std::string utl_uint32_to_ipv4(uint32_t ipv4_addr) { + std::stringstream ss; + ss << ((ipv4_addr >> 24) & 0xff) << "." << ((ipv4_addr >> 16) & 0xff) << "." << ((ipv4_addr >> 8) & 0xff) << "." << (ipv4_addr & 0xff); + return ss.str(); +} diff --git a/src/common/basic_utils.h b/src/common/basic_utils.h index 1079ecfc..36f9db85 100755 --- a/src/common/basic_utils.h +++ b/src/common/basic_utils.h @@ -87,6 +87,8 @@ bool utl_is_file_exists (const std::string& name) ; void utl_macaddr_to_str(const uint8_t *macaddr, std::string &output); std::string utl_macaddr_to_str(const uint8_t *macaddr); +bool utl_str_to_macaddr(const std::string &s, uint8_t *mac); + std::string utl_generate_random_str(unsigned int &seed, int len); /** @@ -99,6 +101,9 @@ std::string utl_generate_random_str(unsigned int &seed, int len); */ void utl_set_coredump_size(long size, bool map_huge_pages = false); +bool utl_ipv4_to_uint32(const char *ipv4_str, uint32_t &ipv4_num); +std::string utl_uint32_to_ipv4(uint32_t ipv4_addr); + #endif diff --git a/src/common/captureFile.h b/src/common/captureFile.h index fefa62bd..d87e57b6 100755 --- a/src/common/captureFile.h +++ b/src/common/captureFile.h @@ -227,9 +227,10 @@ class CFileWriterBase { public: - virtual ~CFileWriterBase(){}; - virtual bool Create(char * name) = 0; + virtual ~CFileWriterBase(){}; + virtual bool Create(char * name) = 0; virtual bool write_packet(CCapPktRaw * lpPacket)=0; + virtual void flush_to_disk() = 0; }; diff --git a/src/common/erf.cpp b/src/common/erf.cpp index 76945b01..f872a281 100755 --- a/src/common/erf.cpp +++ b/src/common/erf.cpp @@ -280,7 +280,11 @@ bool CErfFileWriter::write_packet(CCapPktRaw * lpPacket){ return true; } - +void CErfFileWriter::flush_to_disk() { + if (m_fd) { + fflush(m_fd); + } +} bool CPcapFileWriter::Create(char *file_name){ m_fd=CAP_FOPEN_64(file_name,"wb"); diff --git a/src/common/erf.h b/src/common/erf.h index e1b83e46..bec94872 100755 --- a/src/common/erf.h +++ b/src/common/erf.h @@ -224,6 +224,13 @@ public: virtual bool Create(char *file_name); void Delete(); virtual bool write_packet(CCapPktRaw * lpPacket); + + /** + * flush all packets to disk + * + */ + void flush_to_disk(); + private: FILE *m_fd; int m_cnt; diff --git a/src/common/pcap.cpp b/src/common/pcap.cpp index 8e9bf0ac..b976aed7 100755 --- a/src/common/pcap.cpp +++ b/src/common/pcap.cpp @@ -223,11 +223,18 @@ bool LibPCapWriter::Create(char * name) printf(" ERROR create file \n"); return(false); } + /* prepare the write counter */ m_pkt_count = 0; return init(); } +void LibPCapWriter::flush_to_disk() { + if (m_is_open) { + fflush(m_file_handler); + } +} + /** * * Write the libpcap header. diff --git a/src/common/pcap.h b/src/common/pcap.h index 3f8dfd21..265ea17b 100755 --- a/src/common/pcap.h +++ b/src/common/pcap.h @@ -1,5 +1,5 @@ -#ifndef __LIBPCAP_H__ -#define __LIBPCAP_H__ +#ifndef __TREX_LIBPCAP_H__ +#define __TREX_LIBPCAP_H__ /* Copyright (c) 2015-2015 Cisco Systems, Inc. @@ -143,6 +143,13 @@ public: */ void Close(); + /** + * flush all packets to disk + * + * @author imarom (11/24/2016) + */ + void flush_to_disk(); + private: bool init(); @@ -151,4 +158,5 @@ private: bool m_is_open; uint32_t m_pkt_count; }; + #endif diff --git a/src/debug.cpp b/src/debug.cpp index d0b7cf11..e272424c 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -142,7 +142,7 @@ rte_mbuf_t *CTrexDebug::create_test_pkt(int ip_ver, uint16_t l4_proto, uint8_t t pkt = CTestPktGen::create_test_pkt(l3_type, l4_proto, ttl, ip_id, flags, 1000, pkt_size); - /* DEBUG print the packet + /* DEBUG print the packet utl_k12_pkt_format(stdout,pkt, pkt_size) ; */ @@ -370,6 +370,7 @@ int CTrexDebug::verify_hw_rules(bool recv_all) { case STL: if ( CGlobalInfo::m_options.is_stateless() ) { exp_q = MAIN_DPDK_RX_Q; + pkt_flags |= DPF_TOS_1; } else { exp_q = MAIN_DPDK_DATA_Q; } @@ -379,6 +380,7 @@ int CTrexDebug::verify_hw_rules(bool recv_all) { exp_q = MAIN_DPDK_DATA_Q; } else { exp_q = MAIN_DPDK_RX_Q; + pkt_flags |= DPF_TOS_1; } break; default: diff --git a/src/dpdk/drivers/net/enic/enic_ethdev.c b/src/dpdk/drivers/net/enic/enic_ethdev.c index c05476b2..6a86e23f 100644 --- a/src/dpdk/drivers/net/enic/enic_ethdev.c +++ b/src/dpdk/drivers/net/enic/enic_ethdev.c @@ -436,22 +436,6 @@ static void enicpmd_dev_stats_reset(struct rte_eth_dev *eth_dev) } -int enicpmd_dev_get_fw_support(int port_id, - uint32_t *ver){ - struct rte_eth_dev *dev; - - RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL); - - dev = &rte_eth_devices[port_id]; - *ver=0; - - struct enic *enic = pmd_priv(dev); - enic->adv_filters; - if ( enic->adv_filters ==0 ) { - return (-1); - } - return (0); -} static void enicpmd_dev_info_get(struct rte_eth_dev *eth_dev, diff --git a/src/dpdk/drivers/net/enic/enic_main.c b/src/dpdk/drivers/net/enic/enic_main.c index 889bc692..473bfc3c 100644 --- a/src/dpdk/drivers/net/enic/enic_main.c +++ b/src/dpdk/drivers/net/enic/enic_main.c @@ -166,6 +166,7 @@ void enic_dev_stats_get(struct enic *enic, struct rte_eth_stats *r_stats) return; } + /* The number of truncated packets can only be calculated by * subtracting a hardware counter from error packets received by * the driver. Note: this causes transient inaccuracies in the @@ -180,7 +181,7 @@ void enic_dev_stats_get(struct enic *enic, struct rte_eth_stats *r_stats) r_stats->ipackets = stats->rx.rx_frames_ok - rx_truncated; r_stats->opackets = stats->tx.tx_frames_ok; - r_stats->ibytes = stats->rx.rx_bytes_ok; + r_stats->ibytes = stats->rx.rx_unicast_bytes_ok+stats->rx.rx_multicast_bytes_ok+stats->rx.rx_broadcast_bytes_ok; r_stats->obytes = stats->tx.tx_bytes_ok; r_stats->ierrors = stats->rx.rx_errors + stats->rx.rx_drop; diff --git a/src/dpdk/drivers/net/ixgbe/ixgbe_ethdev.c b/src/dpdk/drivers/net/ixgbe/ixgbe_ethdev.c index d478a159..72963a89 100644 --- a/src/dpdk/drivers/net/ixgbe/ixgbe_ethdev.c +++ b/src/dpdk/drivers/net/ixgbe/ixgbe_ethdev.c @@ -5784,13 +5784,17 @@ ixgbe_add_del_ethertype_filter(struct rte_eth_dev *dev, if (filter->queue >= IXGBE_MAX_RX_QUEUE_NUM) return -EINVAL; - +#define TREX_PATCH +#ifndef TREX_PATCH + // no real reason to block this. + // We configure rules using FDIR and ethertype that point to same queue, so there are no race condition issues. if (filter->ether_type == ETHER_TYPE_IPv4 || filter->ether_type == ETHER_TYPE_IPv6) { PMD_DRV_LOG(ERR, "unsupported ether_type(0x%04x) in" " ethertype filter.", filter->ether_type); return -EINVAL; } +#endif if (filter->flags & RTE_ETHTYPE_FLAGS_MAC) { PMD_DRV_LOG(ERR, "mac compare is unsupported."); diff --git a/src/flow_stat.cpp b/src/flow_stat.cpp index 84be590f..92cbca6e 100644 --- a/src/flow_stat.cpp +++ b/src/flow_stat.cpp @@ -461,6 +461,7 @@ CFlowStatRuleMgr::CFlowStatRuleMgr() { 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)); m_num_ports = 0; // need to call create to init + m_mode = FLOW_STAT_MODE_NORMAL; } CFlowStatRuleMgr::~CFlowStatRuleMgr() { @@ -802,11 +803,14 @@ int CFlowStatRuleMgr::start_stream(TrexStream * stream) { #endif 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)); + #if 0 // 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. if (m_rx_core) { // in simulation, m_rx_core will be NULL @@ -819,6 +823,8 @@ int CFlowStatRuleMgr::start_stream(TrexStream * stream) { } } } + #endif + } else { // make sure rx core is working. If not, we got really confused somehow. if (m_rx_core) @@ -966,13 +972,24 @@ int CFlowStatRuleMgr::set_mode(enum flow_stat_mode_e mode) { extern bool rx_should_stop; void CFlowStatRuleMgr::send_start_stop_msg_to_rx(bool is_start) { TrexStatelessCpToRxMsgBase *msg; - + if (is_start) { - msg = new TrexStatelessRxStartMsg(); + static MsgReply<bool> reply; + reply.reset(); + + msg = new TrexStatelessRxEnableLatency(reply); + m_ring_to_rx->Enqueue((CGenNode *)msg); + + /* hold until message was ack'ed - otherwise we might lose packets */ + if (m_rx_core) { + reply.wait_for_reply(); + assert(m_rx_core->is_working()); + } + } else { - msg = new TrexStatelessRxStopMsg(); + msg = new TrexStatelessRxDisableLatency(); + m_ring_to_rx->Enqueue((CGenNode *)msg); } - m_ring_to_rx->Enqueue((CGenNode *)msg); } // return false if no counters changed since last run. true otherwise diff --git a/src/internal_api/trex_platform_api.h b/src/internal_api/trex_platform_api.h index 631f9a3e..5723503c 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 "trex_stateless_rx_defs.h" #include "trex_port_attr.h" #include <json/json.h> @@ -112,19 +113,13 @@ public: IF_STAT_RX_BYTES_COUNT = 8, // Card support counting rx bytes }; - struct mac_cfg_st { - uint8_t hw_macaddr[6]; - uint8_t src_macaddr[6]; - uint8_t dst_macaddr[6]; - }; - /** * interface static info * */ struct intf_info_st { std::string driver_name; - mac_cfg_st mac_info; + uint8_t hw_macaddr[6]; std::string pci_addr; int numa_node; bool has_crc; @@ -234,7 +229,7 @@ public: info.has_crc = true; info.numa_node = 0; - memset(&info.mac_info, 0, sizeof(info.mac_info)); + memset(&info.hw_macaddr, 0, sizeof(info.hw_macaddr)); } virtual void get_interface_stats(uint8_t interface_id, TrexPlatformInterfaceStats &stats) const { diff --git a/src/main_dpdk.cpp b/src/main_dpdk.cpp index 6f18b0b8..c9e0c6a1 100644 --- a/src/main_dpdk.cpp +++ b/src/main_dpdk.cpp @@ -132,7 +132,7 @@ static char global_master_id_str[10]; class CTRexExtendedDriverBase { public: - + /* by default NIC driver adds CRC */ virtual bool has_crc_added() { return true; @@ -168,6 +168,7 @@ public: virtual CFlowStatParser *get_flow_stat_parser(); virtual int set_rcv_all(CPhyEthIF * _if, bool set_on)=0; virtual TRexPortAttr * create_port_attr(uint8_t port_id) = 0; + virtual uint8_t get_num_crc_fix_bytes() {return 0;} /* Does this NIC type support automatic packet dropping in case of a link down? in case it is supported the packets will be dropped, else there would be a back pressure to tx queues @@ -277,6 +278,18 @@ public: virtual int set_rcv_all(CPhyEthIF * _if, bool set_on) {return 0;} }; +class CTRexExtendedDriverBaseE1000 : public CTRexExtendedDriverBase1GVm { + CTRexExtendedDriverBaseE1000() { + // E1000 driver is only relevant in VM in our case + CGlobalInfo::m_options.preview.set_vm_one_queue_enable(true); + } +public: + static CTRexExtendedDriverBase * create() { + return ( new CTRexExtendedDriverBaseE1000() ); + } + // e1000 driver handing us packets with ethernet CRC, so we need to chop them + virtual uint8_t get_num_crc_fix_bytes() {return 4;} +}; class CTRexExtendedDriverBase10G : public CTRexExtendedDriverBase { public: @@ -316,7 +329,8 @@ public: | TrexPlatformApi::IF_STAT_PAYLOAD; } virtual CFlowStatParser *get_flow_stat_parser(); - virtual int set_rcv_all(CPhyEthIF * _if, bool set_on) {return 0;} + int add_del_eth_filter(CPhyEthIF * _if, bool is_add, uint16_t ethertype); + virtual int set_rcv_all(CPhyEthIF * _if, bool set_on); }; class CTRexExtendedDriverBase40G : public CTRexExtendedDriverBase10G { @@ -379,7 +393,7 @@ private: uint8_t m_if_per_card; }; -class CTRexExtendedDriverBaseVIC : public CTRexExtendedDriverBase40G { +class CTRexExtendedDriverBaseVIC : public CTRexExtendedDriverBase { public: CTRexExtendedDriverBaseVIC(){ m_if_per_card=2; @@ -396,6 +410,22 @@ public: virtual bool is_hardware_filter_is_supported(){ return (true); } + virtual void update_global_config_fdir(port_cfg_t * cfg){ + } + + + virtual bool is_hardware_support_drop_queue(){ + return(true); + } + + void clear_extended_stats(CPhyEthIF * _if); + + void get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats); + + + virtual int get_min_sample_rate(void){ + return (RX_CHECK_MIX_SAMPLE_RATE); + } virtual int verify_fw_ver(int i); @@ -556,7 +586,7 @@ private: /* virtual devices */ - register_driver(std::string("rte_em_pmd"),CTRexExtendedDriverBase1GVm::create); + register_driver(std::string("rte_em_pmd"),CTRexExtendedDriverBaseE1000::create); register_driver(std::string("rte_vmxnet3_pmd"),CTRexExtendedDriverBase1GVm::create); register_driver(std::string("rte_virtio_pmd"),CTRexExtendedDriverBase1GVm::create); @@ -1078,7 +1108,7 @@ static int parse_options(int argc, char *argv[], CParserOption* po, bool first_t } if (po->preview.get_is_rx_check_enable() || po->is_latency_enabled() || CGlobalInfo::is_learn_mode() - || (CGlobalInfo::m_options.m_arp_ref_per != 0)) { + || (CGlobalInfo::m_options.m_arp_ref_per != 0) || get_vm_one_queue_enable()) { po->set_rx_enabled(); } @@ -1627,6 +1657,7 @@ int DpdkTRexPortAttr::set_led(bool on){ int DpdkTRexPortAttr::get_flow_ctrl(int &mode) { int ret = rte_eth_dev_flow_ctrl_get(m_port_id, &fc_conf_tmp); if (ret) { + mode = -1; return ret; } mode = (int) fc_conf_tmp.mode; @@ -1723,12 +1754,19 @@ bool DpdkTRexPortAttr::update_link_status_nowait(){ rte_eth_link new_link; bool changed = false; rte_eth_link_get_nowait(m_port_id, &new_link); + if (new_link.link_speed != m_link.link_speed || new_link.link_duplex != m_link.link_duplex || new_link.link_autoneg != m_link.link_autoneg || new_link.link_status != m_link.link_status) { changed = true; + + /* in case of link status change - notify the dest object */ + if (new_link.link_status != m_link.link_status) { + get_dest().on_link_down(); + } } + m_link = new_link; return changed; } @@ -1770,7 +1808,7 @@ bool DpdkTRexPortAttr::get_promiscuous(){ } -void DpdkTRexPortAttr::macaddr_get(struct ether_addr *mac_addr){ +void DpdkTRexPortAttr::get_hw_src_mac(struct ether_addr *mac_addr){ rte_eth_macaddr_get(m_port_id , mac_addr); } @@ -1961,7 +1999,6 @@ public: virtual int send_node(CGenNode * node); virtual void send_one_pkt(pkt_dir_t dir, rte_mbuf_t *m); virtual int flush_tx_queue(void); - __attribute__ ((noinline)) void handle_rx_queue(); __attribute__ ((noinline)) void handle_slowpath_features(CGenNode *node, rte_mbuf_t *m, uint8_t *p, pkt_dir_t dir); void apply_client_cfg(const ClientCfgBase *cfg, rte_mbuf_t *m, pkt_dir_t dir, uint8_t *p); @@ -2037,46 +2074,6 @@ bool CCoreEthIF::Create(uint8_t core_id, return (true); } -// This function is only relevant if we are in VM. In this case, we only have one rx queue. Can't have -// rules to drop queue 0 packets, and pass queue 1 packets to RX core, like in other cases. -// We receive all packets in the same core that transmitted, and handle them to RX core. -void CCoreEthIF::handle_rx_queue(void) { - if ( likely( ! get_vm_one_queue_enable() ) ) { - return; - } - - pkt_dir_t dir; - bool is_rx = get_is_rx_thread_enabled(); - for (dir=CLIENT_SIDE; dir<CS_NUM; dir++) { - CCorePerPort * lp_port=&m_ports[dir]; - CPhyEthIF * lp=lp_port->m_port; - - rte_mbuf_t * rx_pkts[32]; - int j=0; - - while (true) { - j++; - uint16_t cnt =lp->rx_burst(0,rx_pkts,32); - if ( cnt ) { - int i; - for (i=0; i<(int)cnt;i++) { - rte_mbuf_t * m=rx_pkts[i]; - if ( is_rx ){ - if (!process_rx_pkt(dir,m)){ - rte_pktmbuf_free(m); - } - }else{ - rte_pktmbuf_free(m); - } - } - } - if ((cnt<5) || j>10 ) { - break; - } - } - } -} - int CCoreEthIF::flush_tx_queue(void){ /* flush both sides */ pkt_dir_t dir; @@ -2089,8 +2086,6 @@ int CCoreEthIF::flush_tx_queue(void){ } } - handle_rx_queue(); - return 0; } @@ -2580,10 +2575,11 @@ private: class CLatencyVmPort : public CPortLatencyHWBase { public: void Create(uint8_t port_index,CNodeRing * ring, - CLatencyManager * mgr){ + CLatencyManager * mgr, CPhyEthIF * p) { m_dir = (port_index%2); m_ring_to_dp = ring; m_mgr = mgr; + m_port = p; } virtual int tx(rte_mbuf_t * m){ @@ -2610,17 +2606,23 @@ public: return (-1); } - virtual rte_mbuf_t * rx(){ - return (0); + virtual rte_mbuf_t * rx() { + rte_mbuf_t * rx_pkts[1]; + uint16_t cnt = m_port->rx_burst(0, rx_pkts, 1); + if (cnt) { + return (rx_pkts[0]); + } else { + return (0); + } } - virtual uint16_t rx_burst(struct rte_mbuf **rx_pkts, - uint16_t nb_pkts){ - return (0); + virtual uint16_t rx_burst(struct rte_mbuf **rx_pkts, uint16_t nb_pkts) { + uint16_t cnt = m_port->rx_burst(0, rx_pkts, nb_pkts); + return (cnt); } - private: + CPhyEthIF * m_port; uint8_t m_dir; CNodeRing * m_ring_to_dp; /* ring dp -> latency thread */ CLatencyManager * m_mgr; @@ -3128,9 +3130,9 @@ public: uint32_t m_max_ports; /* active number of ports supported options are 2,4,8,10,12 */ uint32_t m_max_cores; /* current number of cores , include master and latency ==> ( master)1+c*(m_max_ports>>1)+1( latency ) */ uint32_t m_cores_mul; /* how cores multipler given c=4 ==> m_cores_mul */ - uint32_t m_max_queues_per_port; - uint32_t m_cores_to_dual_ports; /* number of ports that will handle dual ports */ - uint16_t m_latency_tx_queue_id; + uint32_t m_max_queues_per_port; // Number of TX queues per port + uint32_t m_cores_to_dual_ports; /* number of TX cores allocated for each port pair */ + uint16_t m_rx_core_tx_q_id; /* TX q used by rx core */ // statistic CPPSMeasure m_cps; float m_expected_pps; @@ -3317,11 +3319,20 @@ void CGlobalTRex::pre_test() { // we don't have dest MAC. Get it from what we resolved. uint32_t ip = CGlobalInfo::m_options.m_ip_cfg[port_id].get_def_gw(); uint16_t vlan = CGlobalInfo::m_options.m_ip_cfg[port_id].get_vlan(); - if (! pretest.get_mac(port_id, ip, vlan, mac)) { + + if (!pretest.get_mac(port_id, ip, vlan, mac)) { fprintf(stderr, "Failed resolving dest MAC for default gateway:%d.%d.%d.%d on port %d\n" , (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF, port_id); - exit(1); + + if (get_is_stateless()) { + continue; + } else { + exit(1); + } } + + + memcpy(CGlobalInfo::m_options.m_mac_addr[port_id].u.m_mac.dest, mac, ETHER_ADDR_LEN); // if port is connected in loopback, no need to send gratuitous ARP. It will only confuse our ingress counters. if (need_grat_arp[port_id] && (! pretest.is_loopback(port_id))) { @@ -3340,6 +3351,16 @@ void CGlobalTRex::pre_test() { // Configure port back to normal mode. Only relevant packets handled by software. CTRexExtendedDriverDb::Ins()->get_drv()->set_rcv_all(pif, false); + + /* set resolved IPv4 */ + uint32_t dg = CGlobalInfo::m_options.m_ip_cfg[port_id].get_def_gw(); + const uint8_t *dst_mac = CGlobalInfo::m_options.m_mac_addr[port_id].u.m_mac.dest; + if (dg) { + m_ports[port_id].get_port_attr()->get_dest().set_dest(dg, dst_mac); + } else { + m_ports[port_id].get_port_attr()->get_dest().set_dest(dst_mac); + } + } } } @@ -3408,12 +3429,14 @@ bool CGlobalTRex::is_all_links_are_up(bool dump){ void CGlobalTRex::try_stop_all_cores(){ TrexStatelessDpQuit * dp_msg= new TrexStatelessDpQuit(); - TrexStatelessRxQuit * rx_msg= new TrexStatelessRxQuit(); send_message_all_dp(dp_msg); + delete dp_msg; + if (get_is_stateless()) { + TrexStatelessRxQuit * rx_msg= new TrexStatelessRxQuit(); send_message_to_rx(rx_msg); } - delete dp_msg; + // no need to delete rx_msg. Deleted by receiver bool all_core_finished = false; int i; @@ -3463,6 +3486,7 @@ int CGlobalTRex::ixgbe_rx_queue_flush(){ } +// init stateful rx core void CGlobalTRex::ixgbe_configure_mg(void) { int i; CLatencyManagerCfg mg_cfg; @@ -3485,13 +3509,13 @@ void CGlobalTRex::ixgbe_configure_mg(void) { if ( get_vm_one_queue_enable() ) { /* vm mode, indirect queues */ for (i=0; i<m_max_ports; i++) { - + CPhyEthIF * _if = &m_ports[i]; CMessagingManager * rx_dp=CMsgIns::Ins()->getRxDp(); uint8_t thread_id = (i>>1); CNodeRing * r = rx_dp->getRingCpToDp(thread_id); - m_latency_vm_vports[i].Create((uint8_t)i,r,&m_mg); + m_latency_vm_vports[i].Create((uint8_t)i, r, &m_mg, _if); mg_cfg.m_ports[i] =&m_latency_vm_vports[i]; } @@ -3500,7 +3524,7 @@ void CGlobalTRex::ixgbe_configure_mg(void) { for (i=0; i<m_max_ports; i++) { CPhyEthIF * _if=&m_ports[i]; _if->dump_stats(stdout); - m_latency_vports[i].Create(_if,m_latency_tx_queue_id,1); + m_latency_vports[i].Create(_if, m_rx_core_tx_q_id, 1); mg_cfg.m_ports[i] =&m_latency_vports[i]; } @@ -3517,20 +3541,22 @@ void CGlobalTRex::rx_sl_configure(void) { int i; rx_sl_cfg.m_max_ports = m_max_ports; + rx_sl_cfg.m_num_crc_fix_bytes = get_ex_drv()->get_num_crc_fix_bytes(); if ( get_vm_one_queue_enable() ) { /* vm mode, indirect queues */ for (i=0; i < m_max_ports; i++) { + CPhyEthIF * _if = &m_ports[i]; CMessagingManager * rx_dp = CMsgIns::Ins()->getRxDp(); uint8_t thread_id = (i >> 1); CNodeRing * r = rx_dp->getRingCpToDp(thread_id); - m_latency_vm_vports[i].Create((uint8_t)i, r, &m_mg); + m_latency_vm_vports[i].Create(i, r, &m_mg, _if); rx_sl_cfg.m_ports[i] = &m_latency_vm_vports[i]; } } else { for (i = 0; i < m_max_ports; i++) { CPhyEthIF * _if = &m_ports[i]; - m_latency_vports[i].Create(_if, m_latency_tx_queue_id, 1); + m_latency_vports[i].Create(_if, m_rx_core_tx_q_id, 1); rx_sl_cfg.m_ports[i] = &m_latency_vports[i]; } } @@ -3541,88 +3567,53 @@ void CGlobalTRex::rx_sl_configure(void) { int CGlobalTRex::ixgbe_start(void){ int i; for (i=0; i<m_max_ports; i++) { - + socket_id_t socket_id = CGlobalInfo::m_socket.port_to_socket((port_id_t)i); + assert(CGlobalInfo::m_mem_pool[socket_id].m_mbuf_pool_2048); CPhyEthIF * _if=&m_ports[i]; _if->Create((uint8_t)i); - /* last TX queue if for latency check */ - if ( get_vm_one_queue_enable() ) { - /* one tx one rx */ + if ( get_vm_one_queue_enable() ) { /* VMXNET3 does claim to support 16K but somehow does not work */ /* reduce to 2000 */ m_port_cfg.m_port_conf.rxmode.max_rx_pkt_len = 2000; - - _if->configure(1, - 1, - &m_port_cfg.m_port_conf); - - /* will not be used */ - m_latency_tx_queue_id= m_cores_to_dual_ports; - - socket_id_t socket_id = CGlobalInfo::m_socket.port_to_socket((port_id_t)i); - assert(CGlobalInfo::m_mem_pool[socket_id].m_mbuf_pool_2048); - - - + /* In VM case, there is one tx q and one rx q */ + _if->configure(1, 1, &m_port_cfg.m_port_conf); + // Only 1 rx queue, so use it for everything + m_rx_core_tx_q_id = 0; _if->set_rx_queue(0); - _if->rx_queue_setup(0, - RTE_TEST_RX_DESC_VM_DEFAULT, - socket_id, - &m_port_cfg.m_rx_conf, + _if->rx_queue_setup(0, RTE_TEST_RX_DESC_VM_DEFAULT, socket_id, &m_port_cfg.m_rx_conf, CGlobalInfo::m_mem_pool[socket_id].m_mbuf_pool_2048); + // 1 TX queue in VM case + _if->tx_queue_setup(0, RTE_TEST_TX_DESC_VM_DEFAULT, socket_id, &m_port_cfg.m_tx_conf); + } else { + // 2 rx queues. + // TX queues: 1 for each core handling the port pair + 1 for latency pkts + 1 for use by RX core + _if->configure(2, m_cores_to_dual_ports + 2, &m_port_cfg.m_port_conf); + m_rx_core_tx_q_id = m_cores_to_dual_ports; - int qid; - for ( qid=0; qid<(m_max_queues_per_port); qid++) { - _if->tx_queue_setup((uint16_t)qid, - RTE_TEST_TX_DESC_VM_DEFAULT , - socket_id, - &m_port_cfg.m_tx_conf); - - } - - }else{ - _if->configure(2, - m_cores_to_dual_ports+1, - &m_port_cfg.m_port_conf); - - /* the latency queue for latency measurement packets */ - m_latency_tx_queue_id= m_cores_to_dual_ports; - - socket_id_t socket_id = CGlobalInfo::m_socket.port_to_socket((port_id_t)i); - assert(CGlobalInfo::m_mem_pool[socket_id].m_mbuf_pool_2048); - - - /* drop queue */ - _if->rx_queue_setup(0, + // setup RX drop queue + _if->rx_queue_setup(MAIN_DPDK_DATA_Q, RTE_TEST_RX_DESC_DEFAULT, socket_id, &m_port_cfg.m_rx_conf, CGlobalInfo::m_mem_pool[socket_id].m_mbuf_pool_2048); - - - /* set the filter queue */ - _if->set_rx_queue(1); - /* latency measurement ring is 1 */ - _if->rx_queue_setup(1, + // setup RX filter queue + _if->set_rx_queue(MAIN_DPDK_RX_Q); + _if->rx_queue_setup(MAIN_DPDK_RX_Q, RTE_TEST_RX_LATENCY_DESC_DEFAULT, socket_id, &m_port_cfg.m_rx_conf, CGlobalInfo::m_mem_pool[socket_id].m_mbuf_pool_9k); - - int qid; - for ( qid=0; qid<(m_max_queues_per_port+1); qid++) { + // setup TX queues + for (int qid = 0; qid < m_max_queues_per_port; qid++) { _if->tx_queue_setup((uint16_t)qid, RTE_TEST_TX_DESC_DEFAULT , socket_id, &m_port_cfg.m_tx_conf); - } - } - _if->stats_clear(); - _if->start(); _if->configure_rx_duplicate_rules(); @@ -3647,8 +3638,7 @@ int CGlobalTRex::ixgbe_start(void){ printf(" WARNING : there is no link on one of the ports, driver support auto drop in case of link down - continue\n"); }else{ dump_links_status(stdout); - rte_exit(EXIT_FAILURE, " " - " one of the link is down \n"); + rte_exit(EXIT_FAILURE, " One of the links is down \n"); } } } else { @@ -3679,7 +3669,7 @@ int CGlobalTRex::ixgbe_start(void){ if ( get_vm_one_queue_enable() ) { lat_q_id = 0; } else { - lat_q_id = get_cores_tx() / get_base_num_cores(); + lat_q_id = get_cores_tx() / get_base_num_cores() + 1; } for (i=0; i<get_cores_tx(); i++) { int j=(i+1); @@ -3704,6 +3694,7 @@ int CGlobalTRex::ixgbe_start(void){ } fprintf(stdout," -------------------------------\n"); + fprintf(stdout, "RX core uses TX queue number %d on all ports\n", m_rx_core_tx_q_id); CCoreEthIF::DumpIfCfgHeader(stdout); for (i=0; i<get_cores_tx(); i++) { m_cores_vif[i+1]->DumpIfCfg(stdout); @@ -3807,7 +3798,14 @@ bool CGlobalTRex::Create(){ } void CGlobalTRex::Delete(){ + m_zmq_publisher.Delete(); + m_fl.Delete(); + + if (m_trex_stateless) { + delete m_trex_stateless; + m_trex_stateless = NULL; + } } @@ -3930,12 +3928,12 @@ int CGlobalTRex::queues_prob_init(){ m_cores_to_dual_ports = 2; */ - /* number of queue - 1 per core for dual ports*/ - m_max_queues_per_port = m_cores_to_dual_ports; + // One q for each core allowed to send on this port + 1 for latency q (Used in stateless) + 1 for RX core. + m_max_queues_per_port = m_cores_to_dual_ports + 2; if (m_max_queues_per_port > BP_MAX_TX_QUEUE) { rte_exit(EXIT_FAILURE, - "maximum number of queue should be maximum %d \n",BP_MAX_TX_QUEUE); + "Error: Number of TX queues exceeds %d. Try running with lower -c <val> \n",BP_MAX_TX_QUEUE); } assert(m_max_queues_per_port>0); @@ -4405,18 +4403,9 @@ CGlobalTRex:: publish_async_port_attr_changed(uint8_t port_id) { Json::Value data; data["port_id"] = port_id; TRexPortAttr * _attr = m_ports[port_id].get_port_attr(); - - /* attributes */ - data["attr"]["speed"] = _attr->get_link_speed(); - data["attr"]["promiscuous"]["enabled"] = _attr->get_promiscuous(); - data["attr"]["link"]["up"] = _attr->is_link_up(); - int mode; - int ret = _attr->get_flow_ctrl(mode); - if (ret != 0) { - mode = -1; - } - data["attr"]["fc"]["mode"] = mode; - + + _attr->to_json(data["attr"]); + m_zmq_publisher.publish_event(TrexPublisher::EVENT_PORT_ATTR_CHANGED, data); } @@ -4617,8 +4606,11 @@ void CGlobalTRex::shutdown() { for (int i = 0; i < m_max_ports; i++) { m_ports[i].stop(); } + if (m_mark_for_shutdown != SHUTDOWN_TEST_ENDED) { /* we should stop latency and exit to stop agents */ + Delete(); + utl_termio_reset(); exit(-1); } } @@ -4782,7 +4774,6 @@ int CGlobalTRex::stop_master(){ dump_stats(stdout,CGlobalStats::dmpSTANDARD); dump_post_test_stats(stdout); - m_fl.Delete(); return (0); } @@ -4922,6 +4913,18 @@ bool CPhyEthIF::Create(uint8_t portid) { m_last_tx_pps = 0.0; m_port_attr = g_trex.m_drv->create_port_attr(portid); + + uint32_t src_ipv4 = CGlobalInfo::m_options.m_ip_cfg[m_port_id].get_ip(); + if (src_ipv4) { + m_port_attr->set_src_ipv4(src_ipv4); + } + + /* for now set as unresolved IPv4 destination */ + uint32_t dest_ipv4 = CGlobalInfo::m_options.m_ip_cfg[m_port_id].get_def_gw(); + if (dest_ipv4) { + m_port_attr->get_dest().set_dest(dest_ipv4); + } + return true; } @@ -5623,7 +5626,7 @@ int main_test(int argc , char * argv[]){ g_trex.stop_master(); g_trex.Delete(); utl_termio_reset(); - + return (0); } @@ -6001,6 +6004,7 @@ void CTRexExtendedDriverBase10G::update_configuration(port_cfg_t * cfg){ } int CTRexExtendedDriverBase10G::configure_rx_filter_rules(CPhyEthIF * _if) { + set_rcv_all(_if, false); if ( get_is_stateless() ) { return configure_rx_filter_rules_stateless(_if); } else { @@ -6031,7 +6035,7 @@ int CTRexExtendedDriverBase10G::configure_rx_filter_rules_stateless(CPhyEthIF * res = rte_eth_dev_filter_ctrl(port_id, RTE_ETH_FILTER_FDIR, RTE_ETH_FILTER_ADD, &fdir_filter); if (res != 0) { - rte_exit(EXIT_FAILURE, " ERROR rte_eth_dev_filter_ctrl : %d\n",res); + rte_exit(EXIT_FAILURE, "Error: rte_eth_dev_filter_ctrl in configure_rx_filter_rules_stateless: %d\n",res); } } @@ -6100,12 +6104,51 @@ int CTRexExtendedDriverBase10G::configure_rx_filter_rules_statefull(CPhyEthIF * res = rte_eth_dev_filter_ctrl(port_id, RTE_ETH_FILTER_FDIR, RTE_ETH_FILTER_ADD, &fdir_filter); if (res != 0) { - rte_exit(EXIT_FAILURE, " ERROR rte_eth_dev_filter_ctrl : %d\n",res); + rte_exit(EXIT_FAILURE, "Error: rte_eth_dev_filter_ctrl in configure_rx_filter_rules_statefull: %d\n",res); } } return (0); } +int CTRexExtendedDriverBase10G::add_del_eth_filter(CPhyEthIF * _if, bool is_add, uint16_t ethertype) { + int res = 0; + uint8_t port_id=_if->get_rte_port_id(); + struct rte_eth_ethertype_filter filter; + enum rte_filter_op op; + + memset(&filter, 0, sizeof(filter)); + filter.ether_type = ethertype; + res = rte_eth_dev_filter_ctrl(port_id, RTE_ETH_FILTER_ETHERTYPE, RTE_ETH_FILTER_GET, &filter); + + if (is_add && (res >= 0)) + return 0; + if ((! is_add) && (res == -ENOENT)) + return 0; + + if (is_add) { + op = RTE_ETH_FILTER_ADD; + } else { + op = RTE_ETH_FILTER_DELETE; + } + + filter.queue = 1; + res = rte_eth_dev_filter_ctrl(port_id, RTE_ETH_FILTER_ETHERTYPE, op, &filter); + if (res != 0) { + printf("Error: %s L2 filter for ethertype 0x%04x returned %d\n", is_add ? "Adding":"Deleting", ethertype, res); + exit(1); + } + return 0; +} + +int CTRexExtendedDriverBase10G::set_rcv_all(CPhyEthIF * _if, bool set_on) { + int res = 0; + res = add_del_eth_filter(_if, set_on, ETHER_TYPE_ARP); + res |= add_del_eth_filter(_if, set_on, ETHER_TYPE_IPv4); + res |= add_del_eth_filter(_if, set_on, ETHER_TYPE_IPv6); + + return res; +} + void CTRexExtendedDriverBase10G::get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats){ int i; @@ -6852,26 +6895,58 @@ int CTRexExtendedDriverBaseVIC::configure_rx_filter_rules_statefull(CPhyEthIF * return 0; } -extern "C" int enicpmd_dev_get_fw_support(int port_id, - uint32_t *ver); + +void CTRexExtendedDriverBaseVIC::clear_extended_stats(CPhyEthIF * _if){ + rte_eth_stats_reset(_if->get_port_id()); +} + +void CTRexExtendedDriverBaseVIC::get_extended_stats(CPhyEthIF * _if,CPhyEthIFStats *stats) { + struct rte_eth_stats stats1; + struct rte_eth_stats *prev_stats = &stats->m_prev_stats; + rte_eth_stats_get(_if->get_port_id(), &stats1); + + stats->ipackets += stats1.ipackets - prev_stats->ipackets; + stats->ibytes += stats1.ibytes - prev_stats->ibytes + - ((stats1.ipackets << 2) - (prev_stats->ipackets << 2)); + stats->opackets += stats1.opackets - prev_stats->opackets; + stats->obytes += stats1.obytes - prev_stats->obytes; + stats->f_ipackets += 0; + stats->f_ibytes += 0; + stats->ierrors += stats1.imissed + stats1.ierrors + stats1.rx_nombuf + - prev_stats->imissed - prev_stats->ierrors - prev_stats->rx_nombuf; + stats->oerrors += stats1.oerrors - prev_stats->oerrors; + stats->imcasts += 0; + stats->rx_nombuf += stats1.rx_nombuf - prev_stats->rx_nombuf; + + prev_stats->ipackets = stats1.ipackets; + prev_stats->ibytes = stats1.ibytes; + prev_stats->opackets = stats1.opackets; + prev_stats->obytes = stats1.obytes; + prev_stats->imissed = stats1.imissed; + prev_stats->oerrors = stats1.oerrors; + prev_stats->ierrors = stats1.ierrors; + prev_stats->rx_nombuf = stats1.rx_nombuf; +} int CTRexExtendedDriverBaseVIC::verify_fw_ver(int port_id) { - uint32_t ver; - int ret=enicpmd_dev_get_fw_support(port_id,&ver); + struct rte_eth_fdir_info fdir_info; - if (ret==0) { - if (CGlobalInfo::m_options.preview.getVMode() >= 1) { - printf("VIC port %d: FW support advanced filtering \n", port_id); + if ( rte_eth_dev_filter_ctrl(port_id,RTE_ETH_FILTER_FDIR, RTE_ETH_FILTER_INFO,(void *)&fdir_info) == 0 ){ + if ( fdir_info.flow_types_mask[0] & (1<< RTE_ETH_FLOW_NONFRAG_IPV4_OTHER) ) { + /* support new features */ + if (CGlobalInfo::m_options.preview.getVMode() >= 1) { + printf("VIC port %d: FW support advanced filtering \n", port_id); + } + return (0); } - }else{ - printf("Error: VIC firmware should upgrade to support advanced filtering \n"); - printf(" Please refer to %s for upgrade instructions\n", - "https://trex-tgn.cisco.com/trex/doc/trex_manual.html"); - exit(1); } - return (0); + + printf("Error: VIC firmware should upgrade to support advanced filtering \n"); + printf(" Please refer to %s for upgrade instructions\n", + "https://trex-tgn.cisco.com/trex/doc/trex_manual.html"); + exit(1); } @@ -7110,19 +7185,10 @@ TrexDpdkPlatformApi::get_interface_info(uint8_t interface_id, intf_info_st &info /* mac INFO */ /* hardware */ - g_trex.m_ports[interface_id].get_port_attr()->macaddr_get(&rte_mac_addr); + g_trex.m_ports[interface_id].get_port_attr()->get_hw_src_mac(&rte_mac_addr); assert(ETHER_ADDR_LEN == 6); - /* software */ - uint8_t sw_macaddr[12]; - memcpy(sw_macaddr, CGlobalInfo::m_options.get_dst_src_mac_addr(interface_id), 12); - - for (int i = 0; i < 6; i++) { - info.mac_info.hw_macaddr[i] = rte_mac_addr.addr_bytes[i]; - info.mac_info.dst_macaddr[i] = sw_macaddr[i]; - info.mac_info.src_macaddr[i] = sw_macaddr[6 + i]; - - } + memcpy(info.hw_macaddr, rte_mac_addr.addr_bytes, 6); info.numa_node = g_trex.m_ports[interface_id].m_dev_info.pci_dev->numa_node; struct rte_pci_addr *loc = &g_trex.m_ports[interface_id].m_dev_info.pci_dev->addr; @@ -7228,6 +7294,25 @@ TRexPortAttr *TrexDpdkPlatformApi::getPortAttrObj(uint8_t port_id) const { return g_trex.m_ports[port_id].get_port_attr(); } + +int DpdkTRexPortAttr::set_rx_filter_mode(rx_filter_mode_e rx_filter_mode) { + + if (rx_filter_mode == m_rx_filter_mode) { + return (0); + } + + CPhyEthIF *_if = &g_trex.m_ports[m_port_id]; + bool recv_all = (rx_filter_mode == RX_FILTER_MODE_ALL); + int rc = CTRexExtendedDriverDb::Ins()->get_drv()->set_rcv_all(_if, recv_all); + if (rc != 0) { + return (rc); + } + + m_rx_filter_mode = rx_filter_mode; + + return (0); +} + /** * marks the control plane for a total server shutdown * @@ -7236,3 +7321,6 @@ TRexPortAttr *TrexDpdkPlatformApi::getPortAttrObj(uint8_t port_id) const { void TrexDpdkPlatformApi::mark_for_shutdown() const { g_trex.mark_for_shutdown(CGlobalTRex::SHUTDOWN_RPC_REQ); } + + + diff --git a/src/pkt_gen.cpp b/src/pkt_gen.cpp index 45e3a298..656b1b06 100644 --- a/src/pkt_gen.cpp +++ b/src/pkt_gen.cpp @@ -222,7 +222,7 @@ char *CTestPktGen::create_test_pkt(uint16_t l3_type, uint16_t l4_proto, uint8_t switch(l3_type) { case EthernetHeader::Protocol::IP: ip->setTimeToLive(ttl); - if (ttl==TTL_RESERVE_DUPLICATE || ttl==(TTL_RESERVE_DUPLICATE-1)) { + if (flags & DPF_TOS_1) { ip->setTOS(TOS_TTL_RESERVE_DUPLICATE); }else{ ip->setTOS(0x2); @@ -232,11 +232,11 @@ char *CTestPktGen::create_test_pkt(uint16_t l3_type, uint16_t l4_proto, uint8_t break; case EthernetHeader::Protocol::IPv6: ipv6->setHopLimit(ttl); - if (ttl==TTL_RESERVE_DUPLICATE || ttl==(TTL_RESERVE_DUPLICATE-1)) { + if (flags & DPF_TOS_1) { ipv6->setTrafficClass(TOS_TTL_RESERVE_DUPLICATE); }else{ ipv6->setTrafficClass(0x2); - } + } break; } diff --git a/src/pkt_gen.h b/src/pkt_gen.h index 309e02b9..8dcba624 100644 --- a/src/pkt_gen.h +++ b/src/pkt_gen.h @@ -37,7 +37,8 @@ enum { enum { DPF_VLAN = 0x1, DPF_QINQ = 0X2, - DPF_RXCHECK = 0x4 + DPF_RXCHECK = 0x4, + DPF_TOS_1 = 0x8, }; class CTestPktGen { diff --git a/src/pre_test.cpp b/src/pre_test.cpp index df753be5..7127645d 100644 --- a/src/pre_test.cpp +++ b/src/pre_test.cpp @@ -35,6 +35,15 @@ CPretestOnePortInfo::CPretestOnePortInfo() { m_stats.clear(); } +CPretestOnePortInfo::~CPretestOnePortInfo() { + for (std::vector<COneIPInfo *>::iterator it = m_src_info.begin(); it != m_src_info.end(); ++it) { + delete *it; + } + for (std::vector<COneIPInfo *>::iterator it = m_dst_info.begin(); it != m_dst_info.end(); ++it) { + delete *it; + } +} + void CPretestOnePortInfo::add_src(uint32_t ip, uint16_t vlan, MacAddress mac) { COneIPv4Info *one_ip = new COneIPv4Info(ip, vlan, mac); assert(one_ip); diff --git a/src/pre_test.h b/src/pre_test.h index 9573ff02..14b444cf 100644 --- a/src/pre_test.h +++ b/src/pre_test.h @@ -49,6 +49,7 @@ class CPretestOnePortInfo { public: CPretestOnePortInfo(); + ~CPretestOnePortInfo(); void add_src(uint32_t ip, uint16_t vlan, MacAddress mac); void add_dst(uint32_t ip, uint16_t vlan); void add_src(uint16_t ip[8], uint16_t vlan, MacAddress mac); diff --git a/src/rpc-server/commands/trex_rpc_cmd_general.cpp b/src/rpc-server/commands/trex_rpc_cmd_general.cpp index 109cc1a4..3d541fe5 100644 --- a/src/rpc-server/commands/trex_rpc_cmd_general.cpp +++ b/src/rpc-server/commands/trex_rpc_cmd_general.cpp @@ -27,6 +27,8 @@ limitations under the License. #include <internal_api/trex_platform_api.h> +#include "trex_stateless_rx_core.h" + #include <fstream> #include <iostream> #include <unistd.h> @@ -289,19 +291,15 @@ TrexRpcCmdGetSysInfo::_run(const Json::Value ¶ms, Json::Value &result) { section["ports"] = Json::arrayValue; for (int i = 0; i < main->get_port_count(); i++) { - uint32_t speed; string driver; - string hw_macaddr; - string src_macaddr; - string dst_macaddr; string pci_addr; string description; supp_speeds_t supp_speeds; int numa; TrexStatelessPort *port = main->get_port_by_id(i); - port->get_properties(driver, speed); - port->get_macaddr(hw_macaddr, src_macaddr, dst_macaddr); + + port->get_properties(driver); port->get_pci_info(pci_addr, numa); main->get_platform_api()->getPortAttrObj(i)->get_description(description); @@ -311,9 +309,6 @@ TrexRpcCmdGetSysInfo::_run(const Json::Value ¶ms, Json::Value &result) { section["ports"][i]["driver"] = driver; section["ports"][i]["description"] = description; - section["ports"][i]["hw_macaddr"] = hw_macaddr; - section["ports"][i]["src_macaddr"] = src_macaddr; - section["ports"][i]["dst_macaddr"] = dst_macaddr; section["ports"][i]["pci_addr"] = pci_addr; section["ports"][i]["numa"] = numa; @@ -330,7 +325,6 @@ TrexRpcCmdGetSysInfo::_run(const Json::Value ¶ms, Json::Value &result) { section["ports"][i]["rx"]["caps"].append("rx_bytes"); } section["ports"][i]["rx"]["counters"] = port->get_rx_count_num(); - section["ports"][i]["speed"] = (uint16_t) speed / 1000; section["ports"][i]["is_fc_supported"] = get_stateless_obj()->get_platform_api()->getPortAttrObj(i)->is_fc_change_supported(); section["ports"][i]["is_led_supported"] = get_stateless_obj()->get_platform_api()->getPortAttrObj(i)->is_led_change_supported(); section["ports"][i]["is_link_supported"] = get_stateless_obj()->get_platform_api()->getPortAttrObj(i)->is_link_change_supported(); @@ -345,6 +339,69 @@ TrexRpcCmdGetSysInfo::_run(const Json::Value ¶ms, Json::Value &result) { return (TREX_RPC_CMD_OK); } + +int +TrexRpcCmdSetPortAttr::parse_rx_filter_mode(const Json::Value &msg, uint8_t port_id, Json::Value &result) { + const std::string type = parse_choice(msg, "mode", {"hw", "all"}, result); + + rx_filter_mode_e filter_mode; + if (type == "hw") { + filter_mode = RX_FILTER_MODE_HW; + } else if (type == "all") { + filter_mode = RX_FILTER_MODE_ALL; + } else { + /* can't happen - parsed choice */ + assert(0); + } + + return get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_rx_filter_mode(filter_mode); +} + +int +TrexRpcCmdSetPortAttr::parse_ipv4(const Json::Value &msg, uint8_t port_id, Json::Value &result) { + + const std::string ipv4_str = parse_string(msg, "addr", result); + + uint32_t ipv4_addr; + if (!utl_ipv4_to_uint32(ipv4_str.c_str(), ipv4_addr)) { + std::stringstream ss; + ss << "invalid IPv4 address: '" << ipv4_str << "'"; + generate_parse_err(result, ss.str()); + } + + get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_src_ipv4(ipv4_addr); + return (0); +} + +int +TrexRpcCmdSetPortAttr::parse_dest(const Json::Value &msg, uint8_t port_id, Json::Value &result) { + + /* can be either IPv4 or MAC */ + const std::string addr = parse_string(msg, "addr", result); + + TRexPortAttr *port_attr = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id); + + /* try IPv4 */ + uint32_t ipv4_addr; + uint8_t mac[6]; + + if (utl_ipv4_to_uint32(addr.c_str(), ipv4_addr)) { + port_attr->get_dest().set_dest(ipv4_addr); + + } else if (utl_str_to_macaddr(addr, mac)) { + port_attr->get_dest().set_dest(mac); + + } else { + std::stringstream ss; + ss << "'dest' is not an IPv4 address or a MAC address: '" << addr << "'"; + generate_parse_err(result, ss.str()); + } + + + return (0); +} + + /** * set port commands * @@ -361,46 +418,64 @@ TrexRpcCmdSetPortAttr::_run(const Json::Value ¶ms, Json::Value &result) { uint8_t port_id = parse_port(params, result); const Json::Value &attr = parse_object(params, "attr", result); + int ret = 0; - bool changed = false; + /* iterate over all attributes in the dict */ for (const std::string &name : attr.getMemberNames()) { + if (name == "promiscuous") { bool enabled = parse_bool(attr[name], "enabled", result); ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_promiscuous(enabled); } + else if (name == "link_status") { bool up = parse_bool(attr[name], "up", result); ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_link_up(up); } + else if (name == "led_status") { bool on = parse_bool(attr[name], "on", result); ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_led(on); - } else if (name == "flow_ctrl_mode") { + } + + else if (name == "flow_ctrl_mode") { int mode = parse_int(attr[name], "mode", result); ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->set_flow_ctrl(mode); - } else { - generate_execute_err(result, "Not recognized attribute: " + name); - break; } - if (ret != 0){ - if ( ret == -ENOTSUP ) { - generate_execute_err(result, "Error applying " + name + ": operation is not supported for this NIC."); - } - else if (ret) { - generate_execute_err(result, "Error applying " + name + " attribute, return value: " + to_string(ret)); - } + + else if (name == "rx_filter_mode") { + const Json::Value &rx = parse_object(attr, name, result); + ret = parse_rx_filter_mode(rx, port_id, result); + } + + else if (name == "ipv4") { + const Json::Value &ipv4 = parse_object(attr, name, result); + ret = parse_ipv4(ipv4, port_id, result); + } + + else if (name == "dest") { + const Json::Value &dest = parse_object(attr, name, result); + ret = parse_dest(dest, port_id, result); + } + + /* unknown attribute */ + else { + generate_execute_err(result, "unknown attribute type: '" + name + "'"); break; - } else { - changed = true; } - } - if (changed) { - get_stateless_obj()->get_platform_api()->publish_async_port_attr_changed(port_id); - } + /* check error code */ + if ( ret == -ENOTSUP ) { + generate_execute_err(result, "Error applying " + name + ": operation is not supported for this NIC."); + } else if (ret) { + generate_execute_err(result, "Error applying " + name + " attribute, return value: " + to_string(ret)); + } + } + result["result"] = Json::objectValue; return (TREX_RPC_CMD_OK); + } @@ -568,18 +643,17 @@ TrexRpcCmdGetPortStatus::_run(const Json::Value ¶ms, Json::Value &result) { result["result"]["owner"] = (port->get_owner().is_free() ? "" : port->get_owner().get_name()); result["result"]["state"] = port->get_state_as_string(); result["result"]["max_stream_id"] = port->get_max_stream_id(); - result["result"]["speed"] = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->get_link_speed(); /* attributes */ - result["result"]["attr"]["promiscuous"]["enabled"] = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->get_promiscuous(); - result["result"]["attr"]["link"]["up"] = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->is_link_up(); - int mode; - int ret = get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->get_flow_ctrl(mode); - if (ret != 0) { - mode = -1; + get_stateless_obj()->get_platform_api()->getPortAttrObj(port_id)->to_json(result["result"]["attr"]); + + /* RX info */ + try { + result["result"]["rx_info"] = port->rx_features_to_json(); + } catch (const TrexException &ex) { + generate_execute_err(result, ex.what()); } - result["result"]["attr"]["fc"]["mode"] = mode; - + return (TREX_RPC_CMD_OK); } @@ -640,3 +714,151 @@ TrexRpcCmdPushRemote::_run(const Json::Value ¶ms, Json::Value &result) { } +/** + * set on/off RX software receive mode + * + */ +trex_rpc_cmd_rc_e +TrexRpcCmdSetRxFeature::_run(const Json::Value ¶ms, Json::Value &result) { + + uint8_t port_id = parse_port(params, result); + TrexStatelessPort *port = get_stateless_obj()->get_port_by_id(port_id); + + /* decide which feature is being set */ + const std::string type = parse_choice(params, "type", {"capture", "queue", "server"}, result); + + if (type == "capture") { + parse_capture_msg(params, port, result); + } else if (type == "queue") { + parse_queue_msg(params, port, result); + } else if (type == "server") { + parse_server_msg(params, port, result); + } else { + assert(0); + } + + result["result"] = Json::objectValue; + return (TREX_RPC_CMD_OK); + +} + +void +TrexRpcCmdSetRxFeature::parse_capture_msg(const Json::Value &msg, TrexStatelessPort *port, Json::Value &result) { + + bool enabled = parse_bool(msg, "enabled", result); + + if (enabled) { + + std::string pcap_filename = parse_string(msg, "pcap_filename", result); + uint64_t limit = parse_uint32(msg, "limit", result); + + if (limit == 0) { + generate_parse_err(result, "limit cannot be zero"); + } + + try { + port->start_rx_capture(pcap_filename, limit); + } catch (const TrexException &ex) { + generate_execute_err(result, ex.what()); + } + + } else { + + try { + port->stop_rx_capture(); + } catch (const TrexException &ex) { + generate_execute_err(result, ex.what()); + } + + } + +} + +void +TrexRpcCmdSetRxFeature::parse_queue_msg(const Json::Value &msg, TrexStatelessPort *port, Json::Value &result) { + bool enabled = parse_bool(msg, "enabled", result); + + if (enabled) { + + uint64_t size = parse_uint32(msg, "size", result); + + if (size == 0) { + generate_parse_err(result, "queue size cannot be zero"); + } + + try { + port->start_rx_queue(size); + } catch (const TrexException &ex) { + generate_execute_err(result, ex.what()); + } + + } else { + + try { + port->stop_rx_queue(); + } catch (const TrexException &ex) { + generate_execute_err(result, ex.what()); + } + + } + +} + +void +TrexRpcCmdSetRxFeature::parse_server_msg(const Json::Value &msg, TrexStatelessPort *port, Json::Value &result) { +} + + +trex_rpc_cmd_rc_e +TrexRpcCmdGetRxQueuePkts::_run(const Json::Value ¶ms, Json::Value &result) { + + uint8_t port_id = parse_port(params, result); + + TrexStatelessPort *port = get_stateless_obj()->get_port_by_id(port_id); + + try { + const RXPacketBuffer *pkt_buffer = port->get_rx_queue_pkts(); + if (pkt_buffer) { + result["result"]["pkts"] = pkt_buffer->to_json(); + delete pkt_buffer; + + } else { + result["result"]["pkts"] = Json::arrayValue; + } + + } catch (const TrexException &ex) { + generate_execute_err(result, ex.what()); + } + + + return (TREX_RPC_CMD_OK); +} + +trex_rpc_cmd_rc_e +TrexRpcCmdSetARPRes::_run(const Json::Value ¶ms, Json::Value &result) { + uint8_t port_id = parse_port(params, result); + + TrexStatelessPort *port = get_stateless_obj()->get_port_by_id(port_id); + + const std::string ipv4_str = parse_string(params, "ipv4", result); + const std::string mac_str = parse_string(params, "mac", result); + + uint32_t ipv4_addr; + if (!utl_ipv4_to_uint32(ipv4_str.c_str(), ipv4_addr)) { + std::stringstream ss; + ss << "invalid IPv4 address: '" << ipv4_str << "'"; + generate_parse_err(result, ss.str()); + } + + uint8_t mac[6]; + if (!utl_str_to_macaddr(mac_str, mac)) { + std::stringstream ss; + ss << "'invalid MAC address: '" << mac_str << "'"; + generate_parse_err(result, ss.str()); + } + + port->getPortAttrObj()->get_dest().set_dest(ipv4_addr, mac); + + return (TREX_RPC_CMD_OK); + +} diff --git a/src/rpc-server/commands/trex_rpc_cmd_stream.cpp b/src/rpc-server/commands/trex_rpc_cmd_stream.cpp index c950e011..9a57c5f9 100644 --- a/src/rpc-server/commands/trex_rpc_cmd_stream.cpp +++ b/src/rpc-server/commands/trex_rpc_cmd_stream.cpp @@ -670,6 +670,7 @@ TrexRpcCmdStartTraffic::_run(const Json::Value ¶ms, Json::Value &result) { generate_parse_err(result, "start message can only specify absolute speed rate"); } + dsec_t ts = now_sec(); TrexPortMultiplier mul(type, op, value); try { @@ -680,7 +681,8 @@ TrexRpcCmdStartTraffic::_run(const Json::Value ¶ms, Json::Value &result) { } result["result"]["multiplier"] = port->get_multiplier(); - + result["result"]["ts"] = ts; + return (TREX_RPC_CMD_OK); } diff --git a/src/rpc-server/commands/trex_rpc_cmds.h b/src/rpc-server/commands/trex_rpc_cmds.h index 5fde1d0c..2b2178e2 100644 --- a/src/rpc-server/commands/trex_rpc_cmds.h +++ b/src/rpc-server/commands/trex_rpc_cmds.h @@ -27,6 +27,7 @@ limitations under the License. #include <memory> class TrexStream; +class TrexStatelessPort; /* all the RPC commands decl. goes here */ @@ -89,10 +90,17 @@ TREX_RPC_CMD_DEFINE(TrexRpcCmdRelease, "release", 1, true, APIClass: */ TREX_RPC_CMD_DEFINE(TrexRpcCmdGetPortStats, "get_port_stats", 1, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetPortStatus, "get_port_status", 1, false, APIClass::API_CLASS_TYPE_CORE); -TREX_RPC_CMD_DEFINE(TrexRpcCmdSetPortAttr, "set_port_attr", 2, true, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetPortXStatsValues, "get_port_xstats_values", 1, false, APIClass::API_CLASS_TYPE_CORE); TREX_RPC_CMD_DEFINE(TrexRpcCmdGetPortXStatsNames, "get_port_xstats_names", 1, false, APIClass::API_CLASS_TYPE_CORE); +TREX_RPC_CMD_DEFINE_EXTENDED(TrexRpcCmdSetPortAttr, "set_port_attr", 2, true, APIClass::API_CLASS_TYPE_CORE, + + int parse_rx_filter_mode(const Json::Value &msg, uint8_t port_id, Json::Value &result); + int parse_ipv4(const Json::Value &msg, uint8_t port_id, Json::Value &result); + int parse_dest(const Json::Value &msg, uint8_t port_id, Json::Value &result); +); + + /** * stream cmds */ @@ -144,5 +152,15 @@ TREX_RPC_CMD_DEFINE(TrexRpcCmdPushRemote, "push_remote", 6, true, APIClass::API_ TREX_RPC_CMD_DEFINE(TrexRpcCmdShutdown, "shutdown", 2, false, APIClass::API_CLASS_TYPE_CORE); +TREX_RPC_CMD_DEFINE_EXTENDED(TrexRpcCmdSetRxFeature, "set_rx_feature", 3, false, APIClass::API_CLASS_TYPE_CORE, + void parse_capture_msg(const Json::Value &msg, TrexStatelessPort *port, Json::Value &result); + void parse_queue_msg(const Json::Value &msg, TrexStatelessPort *port, Json::Value &result); + void parse_server_msg(const Json::Value &msg, TrexStatelessPort *port, Json::Value &result); + +); + +TREX_RPC_CMD_DEFINE(TrexRpcCmdGetRxQueuePkts, "get_rx_queue_pkts", 2, false, APIClass::API_CLASS_TYPE_CORE); +TREX_RPC_CMD_DEFINE(TrexRpcCmdSetARPRes, "set_arp_resolution", 2, false, APIClass::API_CLASS_TYPE_CORE); + #endif /* __TREX_RPC_CMD_H__ */ diff --git a/src/rpc-server/trex_rpc_cmds_table.cpp b/src/rpc-server/trex_rpc_cmds_table.cpp index cddf19b9..919be1f1 100644 --- a/src/rpc-server/trex_rpc_cmds_table.cpp +++ b/src/rpc-server/trex_rpc_cmds_table.cpp @@ -71,6 +71,11 @@ TrexRpcCommandsTable::TrexRpcCommandsTable() { register_command(new TrexRpcCmdPushRemote()); register_command(new TrexRpcCmdShutdown()); + + register_command(new TrexRpcCmdSetRxFeature()); + register_command(new TrexRpcCmdGetRxQueuePkts()); + + register_command(new TrexRpcCmdSetARPRes()); } diff --git a/src/stateful_rx_core.cpp b/src/stateful_rx_core.cpp index 7ee802df..65d0a17d 100644 --- a/src/stateful_rx_core.cpp +++ b/src/stateful_rx_core.cpp @@ -677,58 +677,6 @@ void CLatencyManager::handle_rx_pkt(CLatencyManagerPerPort * lp, rte_pktmbuf_free(m); } -// In VM, we receive the RX packets in DP core, and send message to RX core with the packet -void CLatencyManager::handle_latency_pkt_msg(uint8_t thread_id, CGenNodeLatencyPktInfo * msg) { - - assert(msg->m_latency_offset==0xdead); - - uint8_t rx_port_index=(thread_id<<1)+(msg->m_dir&1); - assert( rx_port_index <m_max_ports ) ; - CLatencyManagerPerPort * lp=&m_ports[rx_port_index]; - handle_rx_pkt(lp,(rte_mbuf_t *)msg->m_pkt); -} - - -void CLatencyManager::run_rx_queue_msgs(uint8_t thread_id, - CNodeRing * r){ - - while ( true ) { - CGenNode * node; - if ( r->Dequeue(node)!=0 ){ - break; - } - assert(node); - - CGenNodeMsgBase * msg=(CGenNodeMsgBase *)node; - - uint8_t msg_type = msg->m_msg_type; - switch (msg_type ) { - case CGenNodeMsgBase::LATENCY_PKT: - handle_latency_pkt_msg(thread_id,(CGenNodeLatencyPktInfo *) msg); - break; - default: - printf("ERROR latency-thread message type is not valid %d \n",msg_type); - assert(0); - } - - CGlobalInfo::free_node(node); - } -} - -// VM mode function. Handle messages from DP -void CLatencyManager::try_rx_queues(){ - - CMessagingManager * rx_dp = CMsgIns::Ins()->getRxDp(); - uint8_t threads=CMsgIns::Ins()->get_num_threads(); - int ti; - for (ti=0; ti<(int)threads; ti++) { - CNodeRing * r = rx_dp->getRingDpToCp(ti); - if ( !r->isEmpty() ){ - run_rx_queue_msgs((uint8_t)ti,r); - } - } -} - void CLatencyManager::try_rx(){ rte_mbuf_t * rx_pkts[64]; int i; @@ -790,8 +738,6 @@ void CLatencyManager::start(int iter, bool activate_watchdog) { m_p_queue.push(node); } - bool do_try_rx_queue = CGlobalInfo::m_options.preview.get_vm_one_queue_enable() ? true : false; - if (activate_watchdog) { m_monitor.create("STF RX CORE", 1); TrexWatchDog::getInstance().register_monitor(&m_monitor); @@ -807,9 +753,6 @@ void CLatencyManager::start(int iter, bool activate_watchdog) { if (dt> (0.0)) { break; } - if (do_try_rx_queue){ - try_rx_queues(); - } try_rx(); rte_pause(); } diff --git a/src/stateful_rx_core.h b/src/stateful_rx_core.h index 48fbeb97..2df406de 100644 --- a/src/stateful_rx_core.h +++ b/src/stateful_rx_core.h @@ -355,12 +355,8 @@ private: double grat_arp_timeout(); void send_one_grat_arp(); void try_rx(); - void try_rx_queues(); - void run_rx_queue_msgs(uint8_t thread_id, CNodeRing * r); void wait_for_rx_dump(); void handle_rx_pkt(CLatencyManagerPerPort * lp, rte_mbuf_t * m); - /* messages handlers */ - void handle_latency_pkt_msg(uint8_t thread_id, CGenNodeLatencyPktInfo * msg); private: pqueue_t m_p_queue; /* priorty queue */ diff --git a/src/stateless/cp/trex_stateless_port.cpp b/src/stateless/cp/trex_stateless_port.cpp index 9bb20990..7edf1a31 100644 --- a/src/stateless/cp/trex_stateless_port.cpp +++ b/src/stateless/cp/trex_stateless_port.cpp @@ -25,6 +25,7 @@ limitations under the License. #include <trex_streams_compiler.h> #include <common/basic_utils.h> #include <common/captureFile.h> +#include "trex_stateless_rx_defs.h" #include <string> @@ -156,9 +157,9 @@ private: TrexStatelessPort::TrexStatelessPort(uint8_t port_id, const TrexPlatformApi *api) : m_dp_events(this) { std::vector<std::pair<uint8_t, uint8_t>> core_pair_list; - m_port_id = port_id; - m_port_state = PORT_STATE_IDLE; - m_platform_api = api; + m_port_id = port_id; + m_port_state = PORT_STATE_IDLE; + m_platform_api = api; /* get the platform specific data */ api->get_interface_info(port_id, m_api_info); @@ -584,10 +585,9 @@ TrexStatelessPort::get_max_stream_id() const { } void -TrexStatelessPort::get_properties(std::string &driver, uint32_t &speed) { +TrexStatelessPort::get_properties(std::string &driver) { driver = m_api_info.driver_name; - speed = m_platform_api->getPortAttrObj(m_port_id)->get_link_speed(); } bool @@ -888,16 +888,6 @@ TrexStatelessPort::get_port_effective_rate(double &pps, } void -TrexStatelessPort::get_macaddr(std::string &hw_macaddr, - std::string &src_macaddr, - std::string &dst_macaddr) { - - utl_macaddr_to_str(m_api_info.mac_info.hw_macaddr, hw_macaddr); - utl_macaddr_to_str(m_api_info.mac_info.src_macaddr, src_macaddr); - utl_macaddr_to_str(m_api_info.mac_info.dst_macaddr, dst_macaddr); -} - -void TrexStatelessPort::get_pci_info(std::string &pci_addr, int &numa_node) { pci_addr = m_api_info.pci_addr; numa_node = m_api_info.numa_node; @@ -944,6 +934,71 @@ TrexStatelessPort::remove_and_delete_all_streams() { } } +void +TrexStatelessPort::start_rx_capture(const std::string &pcap_filename, uint64_t limit) { + static MsgReply<bool> reply; + + reply.reset(); + + TrexStatelessRxStartCapture *msg = new TrexStatelessRxStartCapture(m_port_id, pcap_filename, limit, reply); + send_message_to_rx((TrexStatelessCpToRxMsgBase *)msg); + + /* as below, must wait for ACK from RX core before returning ACK */ + reply.wait_for_reply(); +} + +void +TrexStatelessPort::stop_rx_capture() { + TrexStatelessCpToRxMsgBase *msg = new TrexStatelessRxStopCapture(m_port_id); + send_message_to_rx(msg); +} + +void +TrexStatelessPort::start_rx_queue(uint64_t size) { + static MsgReply<bool> reply; + + reply.reset(); + + TrexStatelessRxStartQueue *msg = new TrexStatelessRxStartQueue(m_port_id, size, reply); + send_message_to_rx( (TrexStatelessCpToRxMsgBase *)msg ); + + /* we cannot return ACK to the user until the RX core has approved + this might cause the user to lose some packets from the queue + */ + reply.wait_for_reply(); +} + +void +TrexStatelessPort::stop_rx_queue() { + TrexStatelessCpToRxMsgBase *msg = new TrexStatelessRxStopQueue(m_port_id); + send_message_to_rx(msg); +} + + +const RXPacketBuffer * +TrexStatelessPort::get_rx_queue_pkts() { + static MsgReply<const RXPacketBuffer *> reply; + + reply.reset(); + + TrexStatelessRxQueueGetPkts *msg = new TrexStatelessRxQueueGetPkts(m_port_id, reply); + send_message_to_rx( (TrexStatelessCpToRxMsgBase *)msg ); + + return reply.wait_for_reply(); +} + +Json::Value +TrexStatelessPort::rx_features_to_json() { + static MsgReply<Json::Value> reply; + + reply.reset(); + + TrexStatelessRxFeaturesToJson *msg = new TrexStatelessRxFeaturesToJson(m_port_id, reply); + send_message_to_rx( (TrexStatelessCpToRxMsgBase *)msg ); + + return reply.wait_for_reply(); +} + /************* Trex Port Owner **************/ TrexPortOwner::TrexPortOwner() { diff --git a/src/stateless/cp/trex_stateless_port.h b/src/stateless/cp/trex_stateless_port.h index e2a2aeba..74ab17f1 100644 --- a/src/stateless/cp/trex_stateless_port.h +++ b/src/stateless/cp/trex_stateless_port.h @@ -24,12 +24,15 @@ limitations under the License. #include "common/basic_utils.h" #include "internal_api/trex_platform_api.h" #include "trex_dp_port_events.h" +#include "trex_stateless_rx_defs.h" #include "trex_stream.h" class TrexStatelessCpToDpMsgBase; class TrexStatelessCpToRxMsgBase; class TrexStreamsGraphObj; class TrexPortMultiplier; +class RXPacketBuffer; + /** * TRex port owner can perform @@ -255,11 +258,8 @@ public: * @author imarom (16-Sep-15) * * @param driver - * @param speed */ - void get_properties(std::string &driver, uint32_t &speed); - - + void get_properties(std::string &driver); /** * encode stats as JSON @@ -362,14 +362,58 @@ public: double &bps_L2, double &percentage); + void get_pci_info(std::string &pci_addr, int &numa_node); - void get_macaddr(std::string &hw_macaddr, - std::string &src_macaddr, - std::string &dst_macaddr); - void get_pci_info(std::string &pci_addr, int &numa_node); + /** + * enable RX capture on port + * + */ + void start_rx_capture(const std::string &pcap_filename, uint64_t limit); + /** + * disable RX capture if on + * + */ + void stop_rx_capture(); + + /** + * start RX queueing of packets + * + * @author imarom (11/7/2016) + * + * @param limit + */ + void start_rx_queue(uint64_t limit); + /** + * stop RX queueing + * + * @author imarom (11/7/2016) + */ + void stop_rx_queue(); + + /** + * fetch the RX queue packets from the queue + * + */ + const RXPacketBuffer *get_rx_queue_pkts(); + + /** + * generate a JSON describing the status + * of the RX features + * + */ + Json::Value rx_features_to_json(); + + /** + * return the port attribute object + * + */ + TRexPortAttr *getPortAttrObj() { + return m_platform_api->getPortAttrObj(m_port_id); + } + private: bool is_core_active(int core_id); @@ -401,7 +445,7 @@ private: * */ void send_message_to_rx(TrexStatelessCpToRxMsgBase *msg); - + /** * when a port stops, perform various actions * @@ -456,6 +500,7 @@ private: TrexPortOwner m_owner; int m_pending_async_stop_event; + }; @@ -502,9 +547,9 @@ public: static const std::initializer_list<std::string> g_types; static const std::initializer_list<std::string> g_ops; - mul_type_e m_type; - mul_op_e m_op; - double m_value; + mul_type_e m_type; + mul_op_e m_op; + double m_value; }; #endif /* __TREX_STATELESS_PORT_H__ */ diff --git a/src/stateless/messaging/trex_stateless_messaging.cpp b/src/stateless/messaging/trex_stateless_messaging.cpp index 95613b41..17acb21e 100644 --- a/src/stateless/messaging/trex_stateless_messaging.cpp +++ b/src/stateless/messaging/trex_stateless_messaging.cpp @@ -241,13 +241,15 @@ TrexDpPortEventMsg::handle() { } /************************* messages from CP to RX **********************/ -bool TrexStatelessRxStartMsg::handle (CRxCoreStateless *rx_core) { - rx_core->work(); +bool TrexStatelessRxEnableLatency::handle (CRxCoreStateless *rx_core) { + rx_core->enable_latency(); + m_reply.set_reply(true); + return true; } -bool TrexStatelessRxStopMsg::handle (CRxCoreStateless *rx_core) { - rx_core->idle(); +bool TrexStatelessRxDisableLatency::handle (CRxCoreStateless *rx_core) { + rx_core->disable_latency(); return true; } @@ -255,3 +257,62 @@ bool TrexStatelessRxQuit::handle (CRxCoreStateless *rx_core) { rx_core->quit(); return true; } + + +bool +TrexStatelessRxStartCapture::handle(CRxCoreStateless *rx_core) { + rx_core->start_recorder(m_port_id, m_pcap_filename, m_limit); + + /* mark as done */ + m_reply.set_reply(true); + + return true; +} + +bool +TrexStatelessRxStopCapture::handle(CRxCoreStateless *rx_core) { + rx_core->stop_recorder(m_port_id); + + return true; +} + +bool +TrexStatelessRxStartQueue::handle(CRxCoreStateless *rx_core) { + rx_core->start_queue(m_port_id, m_size); + + /* mark as done */ + m_reply.set_reply(true); + + return true; +} + +bool +TrexStatelessRxStopQueue::handle(CRxCoreStateless *rx_core) { + rx_core->stop_queue(m_port_id); + + return true; +} + + + +bool +TrexStatelessRxQueueGetPkts::handle(CRxCoreStateless *rx_core) { + const RXPacketBuffer *pkt_buffer = rx_core->get_rx_queue_pkts(m_port_id); + + /* set the reply */ + m_reply.set_reply(pkt_buffer); + + return true; +} + + +bool +TrexStatelessRxFeaturesToJson::handle(CRxCoreStateless *rx_core) { + Json::Value output = rx_core->get_rx_port_mngr(m_port_id).to_json(); + + /* set the reply */ + m_reply.set_reply(output); + + return true; +} + diff --git a/src/stateless/messaging/trex_stateless_messaging.h b/src/stateless/messaging/trex_stateless_messaging.h index fb2c27ab..79a6bf08 100644 --- a/src/stateless/messaging/trex_stateless_messaging.h +++ b/src/stateless/messaging/trex_stateless_messaging.h @@ -24,11 +24,66 @@ limitations under the License. #include "msg_manager.h" #include "trex_dp_port_events.h" +#include "trex_exception.h" +#include "trex_stateless_rx_defs.h" +#include "os_time.h" class TrexStatelessDpCore; class CRxCoreStateless; class TrexStreamsCompiledObj; class CFlowGenListPerThread; +class RXPacketBuffer; + +/** + * Generic message reply object + * + * @author imarom (11/27/2016) + */ +template<typename T> class MsgReply { + +public: + + MsgReply() { + reset(); + } + + void reset() { + m_pending = true; + } + + bool is_pending() const { + return m_pending; + } + + void set_reply(const T &reply) { + m_reply = reply; + + /* before marking as done make sure all stores are committed */ + asm volatile("mfence" ::: "memory"); + m_pending = false; + } + + T wait_for_reply(int timeout_ms = 500, int backoff_ms = 1) { + int guard = timeout_ms; + + while (is_pending()) { + guard -= backoff_ms; + if (guard < 0) { + throw TrexException("timeout: failed to get reply from core"); + } + + delay(backoff_ms); + } + + return m_reply; + + } + +protected: + volatile bool m_pending; + T m_reply; +}; + /** * defines the base class for CP to DP messages @@ -312,7 +367,7 @@ private: /************************* messages from DP to CP **********************/ /** - * defines the base class for CP to DP messages + * defines the base class for DP to CP messages * * @author imarom (27-Oct-15) */ @@ -404,11 +459,19 @@ public: }; -class TrexStatelessRxStartMsg : public TrexStatelessCpToRxMsgBase { + +class TrexStatelessRxEnableLatency : public TrexStatelessCpToRxMsgBase { +public: + TrexStatelessRxEnableLatency(MsgReply<bool> &reply) : m_reply(reply) { + } + bool handle (CRxCoreStateless *rx_core); + +private: + MsgReply<bool> &m_reply; }; -class TrexStatelessRxStopMsg : public TrexStatelessCpToRxMsgBase { +class TrexStatelessRxDisableLatency : public TrexStatelessCpToRxMsgBase { bool handle (CRxCoreStateless *rx_core); }; @@ -416,4 +479,114 @@ class TrexStatelessRxQuit : public TrexStatelessCpToRxMsgBase { bool handle (CRxCoreStateless *rx_core); }; + + +class TrexStatelessRxStartCapture : public TrexStatelessCpToRxMsgBase { +public: + TrexStatelessRxStartCapture(uint8_t port_id, + const std::string &pcap_filename, + uint64_t limit, + MsgReply<bool> &reply) : m_reply(reply) { + + m_port_id = port_id; + m_limit = limit; + m_pcap_filename = pcap_filename; + } + + virtual bool handle(CRxCoreStateless *rx_core); + +private: + uint8_t m_port_id; + std::string m_pcap_filename; + uint64_t m_limit; + MsgReply<bool> &m_reply; +}; + + +class TrexStatelessRxStopCapture : public TrexStatelessCpToRxMsgBase { +public: + TrexStatelessRxStopCapture(uint8_t port_id) { + m_port_id = port_id; + } + + virtual bool handle(CRxCoreStateless *rx_core); + +private: + uint8_t m_port_id; +}; + + +class TrexStatelessRxStartQueue : public TrexStatelessCpToRxMsgBase { +public: + TrexStatelessRxStartQueue(uint8_t port_id, + uint64_t size, + MsgReply<bool> &reply) : m_reply(reply) { + + m_port_id = port_id; + m_size = size; + } + + virtual bool handle(CRxCoreStateless *rx_core); + +private: + uint8_t m_port_id; + uint64_t m_size; + MsgReply<bool> &m_reply; +}; + + +class TrexStatelessRxStopQueue : public TrexStatelessCpToRxMsgBase { +public: + TrexStatelessRxStopQueue(uint8_t port_id) { + m_port_id = port_id; + } + + virtual bool handle(CRxCoreStateless *rx_core); + +private: + uint8_t m_port_id; +}; + + + +class TrexStatelessRxQueueGetPkts : public TrexStatelessCpToRxMsgBase { +public: + + TrexStatelessRxQueueGetPkts(uint8_t port_id, MsgReply<const RXPacketBuffer *> &reply) : m_reply(reply) { + m_port_id = port_id; + } + + /** + * virtual function to handle a message + * + */ + virtual bool handle(CRxCoreStateless *rx_core); + +private: + uint8_t m_port_id; + MsgReply<const RXPacketBuffer *> &m_reply; + +}; + +/** + * a request from RX core to dump to Json the RX features + */ +class TrexStatelessRxFeaturesToJson : public TrexStatelessCpToRxMsgBase { +public: + + TrexStatelessRxFeaturesToJson(uint8_t port_id, MsgReply<Json::Value> &reply) : m_reply(reply) { + m_port_id = port_id; + } + + /** + * virtual function to handle a message + * + */ + virtual bool handle(CRxCoreStateless *rx_core); + +private: + uint8_t m_port_id; + MsgReply<Json::Value> &m_reply; +}; + #endif /* __TREX_STATELESS_MESSAGING_H__ */ diff --git a/src/stateless/rx/trex_stateless_rx_core.cpp b/src/stateless/rx/trex_stateless_rx_core.cpp index d162c5b3..f2061bf7 100644 --- a/src/stateless/rx/trex_stateless_rx_core.cpp +++ b/src/stateless/rx/trex_stateless_rx_core.cpp @@ -26,6 +26,8 @@ #include "pal/linux/sanb_atomic.h" #include "trex_stateless_messaging.h" #include "trex_stateless_rx_core.h" +#include "trex_stateless.h" + void CRFC2544Info::create() { m_latency.Create(); @@ -64,15 +66,7 @@ void CRFC2544Info::export_data(rfc2544_info_t_ &obj) { obj.set_latency_json(json); }; -void CCPortLatencyStl::reset() { - for (int i = 0; i < MAX_FLOW_STATS; i++) { - m_rx_pg_stat[i].clear(); - m_rx_pg_stat_payload[i].clear(); - } -} - void CRxCoreStateless::create(const CRxSlCfg &cfg) { - m_rcv_all = false; m_capture = false; m_max_ports = cfg.m_max_ports; @@ -82,15 +76,19 @@ void CRxCoreStateless::create(const CRxSlCfg &cfg) { m_ring_to_cp = cp_rx->getRingDpToCp(0); m_state = STATE_IDLE; - for (int i = 0; i < m_max_ports; i++) { - CLatencyManagerPerPortStl * lp = &m_ports[i]; - lp->m_io = cfg.m_ports[i]; - lp->m_port.reset(); + for (int i = 0; i < MAX_FLOW_STATS_PAYLOAD; i++) { + m_rfc2544[i].create(); } + m_cpu_cp_u.Create(&m_cpu_dp_u); - for (int i = 0; i < MAX_FLOW_STATS_PAYLOAD; i++) { - m_rfc2544[i].create(); + /* create per port manager */ + for (int i = 0; i < m_max_ports; i++) { + m_rx_port_mngr[i].create(cfg.m_ports[i], + m_rfc2544, + &m_err_cntrs, + &m_cpu_dp_u, + cfg.m_num_crc_fix_bytes); } } @@ -124,10 +122,31 @@ bool CRxCoreStateless::periodic_check_for_cp_messages() { handle_cp_msg(msg); } + /* a message might result in a change of state */ + recalculate_next_state(); return true; } +void CRxCoreStateless::recalculate_next_state() { + if (m_state == STATE_QUIT) { + return; + } + + /* next state is determine by the question are there any ports with active features ? */ + m_state = (are_any_features_active() ? STATE_WORKING : STATE_IDLE); +} + +bool CRxCoreStateless::are_any_features_active() { + for (int i = 0; i < m_max_ports; i++) { + if (m_rx_port_mngr[i].has_features_set()) { + return true; + } + } + + return false; +} + void CRxCoreStateless::idle_state_loop() { const int SHORT_DELAY_MS = 2; const int LONG_DELAY_MS = 50; @@ -141,7 +160,7 @@ void CRxCoreStateless::idle_state_loop() { counter = 0; continue; } else { - flush_rx(); + flush_all_pending_pkts(); } /* enter deep sleep only if enough time had passed */ @@ -154,284 +173,88 @@ void CRxCoreStateless::idle_state_loop() { } } -void CRxCoreStateless::start() { - int count = 0; - int i = 0; - bool do_try_rx_queue =CGlobalInfo::m_options.preview.get_vm_one_queue_enable() ? true : false; - - /* register a watchdog handle on current core */ - m_monitor.create("STL RX CORE", 1); - TrexWatchDog::getInstance().register_monitor(&m_monitor); - - while (true) { - if (m_state == STATE_WORKING) { - i++; - if (i == 100000) { // approx 10msec - i = 0; - periodic_check_for_cp_messages(); // m_state might change in here - } - } else { - if (m_state == STATE_QUIT) - break; - count = 0; - i = 0; - set_working_msg_ack(false); - idle_state_loop(); - set_working_msg_ack(true); - } - if (do_try_rx_queue) { - try_rx_queues(); - } - count += try_rx(); +/** + * for each port give a tick (for flushing if needed) + * + */ +void CRxCoreStateless::port_manager_tick() { + for (int i = 0; i < m_max_ports; i++) { + m_rx_port_mngr[i].tick(); } - rte_pause(); - - m_monitor.disable(); } -void CRxCoreStateless::handle_rx_pkt(CLatencyManagerPerPortStl *lp, rte_mbuf_t *m) { - CFlowStatParser parser; - - if (m_rcv_all || parser.parse(rte_pktmbuf_mtod(m, uint8_t *), m->pkt_len) == 0) { - uint32_t ip_id; - if (m_rcv_all || (parser.get_ip_id(ip_id) == 0)) { - if (m_rcv_all || is_flow_stat_id(ip_id)) { - uint16_t hw_id; - if (m_rcv_all || is_flow_stat_payload_id(ip_id)) { - bool good_packet = true; - uint8_t *p = rte_pktmbuf_mtod(m, uint8_t*); - struct flow_stat_payload_header *fsp_head = (struct flow_stat_payload_header *) - (p + m->pkt_len - sizeof(struct flow_stat_payload_header)); - hw_id = fsp_head->hw_id; - CRFC2544Info *curr_rfc2544; - - if (unlikely(fsp_head->magic != FLOW_STAT_PAYLOAD_MAGIC) || hw_id >= MAX_FLOW_STATS_PAYLOAD) { - good_packet = false; - if (!m_rcv_all) - m_err_cntrs.m_bad_header++; - } else { - curr_rfc2544 = &m_rfc2544[hw_id]; - - if (fsp_head->flow_seq != curr_rfc2544->get_exp_flow_seq()) { - // bad flow seq num - // Might be the first packet of a new flow, packet from an old flow, or garbage. - - 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 - good_packet = true; - curr_rfc2544->set_exp_flow_seq(fsp_head->flow_seq); - } else { - // garbage packet - good_packet = false; - m_err_cntrs.m_bad_header++; - } - } - } - } - - if (good_packet) { - uint32_t pkt_seq = fsp_head->seq; - uint32_t exp_seq = curr_rfc2544->get_seq(); - if (unlikely(pkt_seq != exp_seq)) { - if (pkt_seq < exp_seq) { - if (exp_seq - pkt_seq > 100000) { - // packet loss while we had wrap around - curr_rfc2544->inc_seq_err(pkt_seq - exp_seq); - curr_rfc2544->inc_seq_err_too_big(); - curr_rfc2544->set_seq(pkt_seq + 1); - } else { - if (pkt_seq == (exp_seq - 1)) { - curr_rfc2544->inc_dup(); - } else { - curr_rfc2544->inc_ooo(); - // We thought it was lost, but it was just out of order - curr_rfc2544->dec_seq_err(); - } - curr_rfc2544->inc_seq_err_too_low(); - } - } else { - if (unlikely (pkt_seq - exp_seq > 100000)) { - // packet reorder while we had wrap around - if (pkt_seq == (exp_seq - 1)) { - curr_rfc2544->inc_dup(); - } else { - curr_rfc2544->inc_ooo(); - // We thought it was lost, but it was just out of order - curr_rfc2544->dec_seq_err(); - } - curr_rfc2544->inc_seq_err_too_low(); - } else { - // seq > curr_rfc2544->seq. Assuming lost packets - curr_rfc2544->inc_seq_err(pkt_seq - exp_seq); - curr_rfc2544->inc_seq_err_too_big(); - curr_rfc2544->set_seq(pkt_seq + 1); - } - } - } else { - curr_rfc2544->set_seq(pkt_seq + 1); - } - lp->m_port.m_rx_pg_stat_payload[hw_id].add_pkts(1); - lp->m_port.m_rx_pg_stat_payload[hw_id].add_bytes(m->pkt_len + 4); // +4 for ethernet CRC - uint64_t d = (os_get_hr_tick_64() - fsp_head->time_stamp ); - dsec_t ctime = ptime_convert_hr_dsec(d); - curr_rfc2544->add_sample(ctime); - } - } else { - hw_id = get_hw_id(ip_id); - 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 - } - } - } +void CRxCoreStateless::handle_work_stage() { + + /* set the next sync time to */ + dsec_t sync_time_sec = now_sec() + (1.0 / 1000); + + while (m_state == STATE_WORKING) { + process_all_pending_pkts(); + + dsec_t now = now_sec(); + + if ( (now - sync_time_sec) > 0 ) { + periodic_check_for_cp_messages(); + port_manager_tick(); + sync_time_sec = now + (1.0 / 1000); } + + rte_pause(); + } } -void CRxCoreStateless::capture_pkt(rte_mbuf_t *m) { - -} +void CRxCoreStateless::start() { + /* register a watchdog handle on current core */ + m_monitor.create("STL RX CORE", 1); + TrexWatchDog::getInstance().register_monitor(&m_monitor); -// In VM setup, handle packets coming as messages from DP cores. -void CRxCoreStateless::handle_rx_queue_msgs(uint8_t thread_id, CNodeRing * r) { - while ( true ) { - CGenNode * node; - if ( r->Dequeue(node) != 0 ) { + while (m_state != STATE_QUIT) { + switch (m_state) { + case STATE_IDLE: + idle_state_loop(); break; - } - assert(node); - - CGenNodeMsgBase * msg = (CGenNodeMsgBase *)node; - CGenNodeLatencyPktInfo * l_msg; - uint8_t msg_type = msg->m_msg_type; - uint8_t rx_port_index; - CLatencyManagerPerPortStl * lp; - - switch (msg_type) { - case CGenNodeMsgBase::LATENCY_PKT: - l_msg = (CGenNodeLatencyPktInfo *)msg; - assert(l_msg->m_latency_offset == 0xdead); - rx_port_index = (thread_id << 1) + (l_msg->m_dir & 1); - assert( rx_port_index < m_max_ports ); - lp = &m_ports[rx_port_index]; - handle_rx_pkt(lp, (rte_mbuf_t *)l_msg->m_pkt); - if (m_capture) - capture_pkt((rte_mbuf_t *)l_msg->m_pkt); - rte_pktmbuf_free((rte_mbuf_t *)l_msg->m_pkt); + case STATE_WORKING: + handle_work_stage(); break; + default: - printf("ERROR latency-thread message type is not valid %d \n", msg_type); assert(0); + break; } - CGlobalInfo::free_node(node); } -} - -// VM mode function. Handle messages from DP -void CRxCoreStateless::try_rx_queues() { - CMessagingManager * rx_dp = CMsgIns::Ins()->getRxDp(); - uint8_t threads=CMsgIns::Ins()->get_num_threads(); - int ti; - for (ti = 0; ti < (int)threads; ti++) { - CNodeRing * r = rx_dp->getRingDpToCp(ti); - if ( ! r->isEmpty() ) { - handle_rx_queue_msgs((uint8_t)ti, r); - } - } + m_monitor.disable(); } -// exactly the same as try_rx, without the handle_rx_pkt -// purpose is to flush rx queues when core is in idle state -void CRxCoreStateless::flush_rx() { - rte_mbuf_t * rx_pkts[64]; - int i, total_pkts = 0; - for (i = 0; i < m_max_ports; i++) { - CLatencyManagerPerPortStl * lp = &m_ports[i]; - rte_mbuf_t * m; - /* try to read 64 packets clean up the queue */ - uint16_t cnt_p = lp->m_io->rx_burst(rx_pkts, 64); - total_pkts += cnt_p; - if (cnt_p) { - m_cpu_dp_u.start_work1(); - int j; - for (j = 0; j < cnt_p; j++) { - m = rx_pkts[j]; - rte_pktmbuf_free(m); - } - /* commit only if there was work to do ! */ - m_cpu_dp_u.commit1(); - }/* if work */ - }// all ports -} +void CRxCoreStateless::capture_pkt(rte_mbuf_t *m) { -int CRxCoreStateless::try_rx() { - rte_mbuf_t * rx_pkts[64]; - int i, total_pkts = 0; - for (i = 0; i < m_max_ports; i++) { - CLatencyManagerPerPortStl * lp = &m_ports[i]; - rte_mbuf_t * m; - /* try to read 64 packets clean up the queue */ - uint16_t cnt_p = lp->m_io->rx_burst(rx_pkts, 64); - total_pkts += cnt_p; - if (cnt_p) { - m_cpu_dp_u.start_work1(); - int j; - for (j = 0; j < cnt_p; j++) { - m = rx_pkts[j]; - handle_rx_pkt(lp, m); - rte_pktmbuf_free(m); - } - /* commit only if there was work to do ! */ - m_cpu_dp_u.commit1(); - }/* if work */ - }// all ports - return total_pkts; } -bool CRxCoreStateless::is_flow_stat_id(uint32_t id) { - if ((id & 0x000fff00) == IP_ID_RESERVE_BASE) return true; - return false; -} +int CRxCoreStateless::process_all_pending_pkts(bool flush_rx) { -bool CRxCoreStateless::is_flow_stat_payload_id(uint32_t id) { - if (id == FLOW_STAT_PAYLOAD_IP_ID) return true; - return false; -} + int total_pkts = 0; + for (int i = 0; i < m_max_ports; i++) { + total_pkts += m_rx_port_mngr[i].process_all_pending_pkts(flush_rx); + } + + return total_pkts; -uint16_t CRxCoreStateless::get_hw_id(uint16_t id) { - return (0x00ff & id); } void CRxCoreStateless::reset_rx_stats(uint8_t port_id) { - for (int hw_id = 0; hw_id < MAX_FLOW_STATS; hw_id++) { - m_ports[port_id].m_port.m_rx_pg_stat[hw_id].clear(); - } + m_rx_port_mngr[port_id].clear_stats(); } int CRxCoreStateless::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) { - for (int hw_id = min; hw_id <= max; hw_id++) { - if (type == TrexPlatformApi::IF_STAT_PAYLOAD) { - rx_stats[hw_id - min] = m_ports[port_id].m_port.m_rx_pg_stat_payload[hw_id]; - } else { - rx_stats[hw_id - min] = m_ports[port_id].m_port.m_rx_pg_stat[hw_id]; - } - if (reset) { - if (type == TrexPlatformApi::IF_STAT_PAYLOAD) { - m_ports[port_id].m_port.m_rx_pg_stat_payload[hw_id].clear(); - } else { - m_ports[port_id].m_port.m_rx_pg_stat[hw_id].clear(); - } - } - } - return 0; + + /* for now only latency stats */ + m_rx_port_mngr[port_id].get_latency_stats(rx_stats, min, max, reset, type); + + return (0); + } int CRxCoreStateless::get_rfc2544_info(rfc2544_info_t *rfc2544_info, int min, int max, bool reset) { @@ -458,13 +281,6 @@ int CRxCoreStateless::get_rx_err_cntrs(CRxCoreErrCntrs *rx_err) { return 0; } -void CRxCoreStateless::set_working_msg_ack(bool val) { - sanb_smp_memory_barrier(); - m_ack_start_work_msg = val; - sanb_smp_memory_barrier(); -} - - void CRxCoreStateless::update_cpu_util(){ m_cpu_cp_u.Update(); } @@ -472,3 +288,53 @@ void CRxCoreStateless::update_cpu_util(){ double CRxCoreStateless::get_cpu_util() { return m_cpu_cp_u.GetVal(); } + + +void +CRxCoreStateless::start_recorder(uint8_t port_id, const std::string &pcap_filename, uint64_t limit) { + m_rx_port_mngr[port_id].start_recorder(pcap_filename, limit); + recalculate_next_state(); +} + +void +CRxCoreStateless::stop_recorder(uint8_t port_id) { + m_rx_port_mngr[port_id].stop_recorder(); + recalculate_next_state(); +} + +void +CRxCoreStateless::start_queue(uint8_t port_id, uint64_t size) { + m_rx_port_mngr[port_id].start_queue(size); + recalculate_next_state(); +} + +void +CRxCoreStateless::stop_queue(uint8_t port_id) { + m_rx_port_mngr[port_id].stop_queue(); + recalculate_next_state(); +} + +void +CRxCoreStateless::enable_latency() { + for (int i = 0; i < m_max_ports; i++) { + m_rx_port_mngr[i].enable_latency(); + } + + recalculate_next_state(); +} + +void +CRxCoreStateless::disable_latency() { + for (int i = 0; i < m_max_ports; i++) { + m_rx_port_mngr[i].disable_latency(); + } + + recalculate_next_state(); +} + +const RXPortManager & +CRxCoreStateless::get_rx_port_mngr(uint8_t port_id) { + assert(port_id < m_max_ports); + return m_rx_port_mngr[port_id]; + +} diff --git a/src/stateless/rx/trex_stateless_rx_core.h b/src/stateless/rx/trex_stateless_rx_core.h index 3f9fb6cc..cd16bb8a 100644 --- a/src/stateless/rx/trex_stateless_rx_core.h +++ b/src/stateless/rx/trex_stateless_rx_core.h @@ -25,6 +25,7 @@ #include "os_time.h" #include "pal/linux/sanb_atomic.h" #include "utl_cpuu.h" +#include "trex_stateless_rx_port_mngr.h" class TrexStatelessCpToRxMsgBase; @@ -37,25 +38,6 @@ class CCPortLatencyStl { rx_per_flow_t m_rx_pg_stat_payload[MAX_FLOW_STATS_PAYLOAD]; }; -class CLatencyManagerPerPortStl { -public: - CCPortLatencyStl m_port; - CPortLatencyHWBase * m_io; -}; - -class CRxSlCfg { - public: - CRxSlCfg (){ - m_max_ports = 0; - m_cps = 0.0; - } - - public: - uint32_t m_max_ports; - double m_cps; - CPortLatencyHWBase * m_ports[TREX_MAX_PORTS]; -}; - class CRFC2544Info { public: void create(); @@ -109,12 +91,20 @@ class CRxCoreErrCntrs { m_old_flow = 0; } - private: + public: uint64_t m_bad_header; uint64_t m_old_flow; }; +/** + * stateless RX core + * + */ class CRxCoreStateless { + + /** + * core states + */ enum state_e { STATE_IDLE, STATE_WORKING, @@ -129,47 +119,78 @@ class CRxCoreStateless { , TrexPlatformApi::driver_stat_cap_e type); int get_rfc2544_info(rfc2544_info_t *rfc2544_info, int min, int max, bool reset); 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);} - void set_working_msg_ack(bool val); + bool is_working() const {return (m_state == STATE_WORKING);} double get_cpu_util(); void update_cpu_util(); + const RXPacketBuffer *get_rx_queue_pkts(uint8_t port_id) { + return m_rx_port_mngr[port_id].get_pkt_buffer(); + } + /** + * start capturing of RX packets on a specific port + * + */ + void start_recorder(uint8_t port_id, const std::string &pcap_filename, uint64_t limit); + void stop_recorder(uint8_t port_id); + + /** + * start RX queueing of packets + * + */ + void start_queue(uint8_t port_id, uint64_t size); + void stop_queue(uint8_t port_id); + + /** + * enable latency feature for RX packets + * will be apply to all ports + */ + void enable_latency(); + void disable_latency(); + + const RXPortManager &get_rx_port_mngr(uint8_t port_id); + private: void handle_cp_msg(TrexStatelessCpToRxMsgBase *msg); bool periodic_check_for_cp_messages(); void tickle(); void idle_state_loop(); - void handle_rx_pkt(CLatencyManagerPerPortStl * lp, rte_mbuf_t * m); + + void recalculate_next_state(); + bool are_any_features_active(); + void capture_pkt(rte_mbuf_t *m); void handle_rx_queue_msgs(uint8_t thread_id, CNodeRing * r); - void flush_rx(); - int try_rx(); - void try_rx_queues(); - bool is_flow_stat_id(uint32_t id); - bool is_flow_stat_payload_id(uint32_t id); - uint16_t get_hw_id(uint16_t id); + void handle_work_stage(); + void port_manager_tick(); + + int process_all_pending_pkts(bool flush_rx = false); + void flush_all_pending_pkts() { + process_all_pending_pkts(true); + } + + void try_rx_queues(); + private: - TrexMonitor m_monitor; - uint32_t m_max_ports; - bool m_capture; - bool m_rcv_all; - CLatencyManagerPerPortStl m_ports[TREX_MAX_PORTS]; - state_e m_state; - CNodeRing *m_ring_from_cp; - CNodeRing *m_ring_to_cp; - CCpuUtlDp m_cpu_dp_u; - CCpuUtlCp m_cpu_cp_u; + TrexMonitor m_monitor; + uint32_t m_max_ports; + bool m_capture; + state_e m_state; + CNodeRing *m_ring_from_cp; + CNodeRing *m_ring_to_cp; + CCpuUtlDp m_cpu_dp_u; + 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]; + + RXPortManager m_rx_port_mngr[TREX_MAX_PORTS]; }; #endif diff --git a/src/stateless/rx/trex_stateless_rx_defs.h b/src/stateless/rx/trex_stateless_rx_defs.h new file mode 100644 index 00000000..aefcc133 --- /dev/null +++ b/src/stateless/rx/trex_stateless_rx_defs.h @@ -0,0 +1,62 @@ +/* + Itay Marom + 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 __TREX_STATELESS_RX_DEFS_H__ +#define __TREX_STATELESS_RX_DEFS_H__ + +#include "trex_defs.h" +#include <json/json.h> + +class CPortLatencyHWBase; + + +/** + * general SL cfg + * + */ +class CRxSlCfg { + public: + CRxSlCfg (){ + m_max_ports = 0; + m_cps = 0.0; + m_num_crc_fix_bytes = 0; + } + + public: + uint32_t m_max_ports; + double m_cps; + CPortLatencyHWBase * m_ports[TREX_MAX_PORTS]; + uint8_t m_num_crc_fix_bytes; +}; + +/** + * describes the filter type applied to the RX + * RX_FILTER_MODE_HW - only hardware filtered traffic will + * reach the RX core + * + */ +typedef enum rx_filter_mode_ { + RX_FILTER_MODE_HW, + RX_FILTER_MODE_ALL +} rx_filter_mode_e; + +#endif /* __TREX_STATELESS_RX_DEFS_H__ */ + diff --git a/src/stateless/rx/trex_stateless_rx_port_mngr.cpp b/src/stateless/rx/trex_stateless_rx_port_mngr.cpp new file mode 100644 index 00000000..afc6827c --- /dev/null +++ b/src/stateless/rx/trex_stateless_rx_port_mngr.cpp @@ -0,0 +1,515 @@ +/* + Itay Marom + 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 "bp_sim.h" +#include "trex_stateless_rx_port_mngr.h" +#include "common/captureFile.h" +#include "trex_stateless_rx_core.h" + +/************************************** + * latency RX feature + * + *************************************/ +RXLatency::RXLatency() { + m_rcv_all = false; + m_rfc2544 = NULL; + m_err_cntrs = NULL; + + for (int i = 0; i < MAX_FLOW_STATS; i++) { + m_rx_pg_stat[i].clear(); + m_rx_pg_stat_payload[i].clear(); + } +} + +void +RXLatency::create(CRFC2544Info *rfc2544, CRxCoreErrCntrs *err_cntrs) { + m_rfc2544 = rfc2544; + m_err_cntrs = err_cntrs; +} + +void +RXLatency::handle_pkt(const rte_mbuf_t *m) { + CFlowStatParser parser; + + if (m_rcv_all || parser.parse(rte_pktmbuf_mtod(m, uint8_t *), m->pkt_len) == 0) { + uint32_t ip_id; + if (m_rcv_all || (parser.get_ip_id(ip_id) == 0)) { + if (m_rcv_all || is_flow_stat_id(ip_id)) { + uint16_t hw_id; + if (m_rcv_all || is_flow_stat_payload_id(ip_id)) { + bool good_packet = true; + uint8_t *p = rte_pktmbuf_mtod(m, uint8_t*); + struct flow_stat_payload_header *fsp_head = (struct flow_stat_payload_header *) + (p + m->pkt_len - sizeof(struct flow_stat_payload_header)); + hw_id = fsp_head->hw_id; + CRFC2544Info *curr_rfc2544; + + if (unlikely(fsp_head->magic != FLOW_STAT_PAYLOAD_MAGIC) || hw_id >= MAX_FLOW_STATS_PAYLOAD) { + good_packet = false; + if (!m_rcv_all) + m_err_cntrs->m_bad_header++; + } else { + curr_rfc2544 = &m_rfc2544[hw_id]; + + if (fsp_head->flow_seq != curr_rfc2544->get_exp_flow_seq()) { + // bad flow seq num + // Might be the first packet of a new flow, packet from an old flow, or garbage. + + 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 + good_packet = true; + curr_rfc2544->set_exp_flow_seq(fsp_head->flow_seq); + } else { + // garbage packet + good_packet = false; + m_err_cntrs->m_bad_header++; + } + } + } + } + + if (good_packet) { + uint32_t pkt_seq = fsp_head->seq; + uint32_t exp_seq = curr_rfc2544->get_seq(); + if (unlikely(pkt_seq != exp_seq)) { + if (pkt_seq < exp_seq) { + if (exp_seq - pkt_seq > 100000) { + // packet loss while we had wrap around + curr_rfc2544->inc_seq_err(pkt_seq - exp_seq); + curr_rfc2544->inc_seq_err_too_big(); + curr_rfc2544->set_seq(pkt_seq + 1); + } else { + if (pkt_seq == (exp_seq - 1)) { + curr_rfc2544->inc_dup(); + } else { + curr_rfc2544->inc_ooo(); + // We thought it was lost, but it was just out of order + curr_rfc2544->dec_seq_err(); + } + curr_rfc2544->inc_seq_err_too_low(); + } + } else { + if (unlikely (pkt_seq - exp_seq > 100000)) { + // packet reorder while we had wrap around + if (pkt_seq == (exp_seq - 1)) { + curr_rfc2544->inc_dup(); + } else { + curr_rfc2544->inc_ooo(); + // We thought it was lost, but it was just out of order + curr_rfc2544->dec_seq_err(); + } + curr_rfc2544->inc_seq_err_too_low(); + } else { + // seq > curr_rfc2544->seq. Assuming lost packets + curr_rfc2544->inc_seq_err(pkt_seq - exp_seq); + curr_rfc2544->inc_seq_err_too_big(); + curr_rfc2544->set_seq(pkt_seq + 1); + } + } + } else { + curr_rfc2544->set_seq(pkt_seq + 1); + } + m_rx_pg_stat_payload[hw_id].add_pkts(1); + m_rx_pg_stat_payload[hw_id].add_bytes(m->pkt_len + 4); // +4 for ethernet CRC + uint64_t d = (os_get_hr_tick_64() - fsp_head->time_stamp ); + dsec_t ctime = ptime_convert_hr_dsec(d); + curr_rfc2544->add_sample(ctime); + } + } else { + hw_id = get_hw_id(ip_id); + if (hw_id < MAX_FLOW_STATS) { + m_rx_pg_stat[hw_id].add_pkts(1); + m_rx_pg_stat[hw_id].add_bytes(m->pkt_len + 4); // +4 for ethernet CRC + } + } + } + } + } +} + +void +RXLatency::reset_stats() { + for (int hw_id = 0; hw_id < MAX_FLOW_STATS; hw_id++) { + m_rx_pg_stat[hw_id].clear(); + } +} + + +void +RXLatency::get_stats(rx_per_flow_t *rx_stats, + int min, + int max, + bool reset, + TrexPlatformApi::driver_stat_cap_e type) { + + for (int hw_id = min; hw_id <= max; hw_id++) { + if (type == TrexPlatformApi::IF_STAT_PAYLOAD) { + rx_stats[hw_id - min] = m_rx_pg_stat_payload[hw_id]; + } else { + rx_stats[hw_id - min] = m_rx_pg_stat[hw_id]; + } + if (reset) { + if (type == TrexPlatformApi::IF_STAT_PAYLOAD) { + m_rx_pg_stat_payload[hw_id].clear(); + } else { + m_rx_pg_stat[hw_id].clear(); + } + } + } +} + + +Json::Value +RXLatency::to_json() const { + return Json::objectValue; +} + +/************************************** + * RX feature queue + * + *************************************/ + +RXPacketBuffer::RXPacketBuffer(uint64_t size) { + m_buffer = nullptr; + m_head = 0; + m_tail = 0; + m_size = (size + 1); // for the empty/full difference 1 slot reserved + + /* generate queue */ + m_buffer = new RXPacket*[m_size](); // zeroed +} + +RXPacketBuffer::~RXPacketBuffer() { + assert(m_buffer); + + while (!is_empty()) { + RXPacket *pkt = pop(); + delete pkt; + } + delete [] m_buffer; +} + +void +RXPacketBuffer::push(const rte_mbuf_t *m) { + /* if full - pop the oldest */ + if (is_full()) { + delete pop(); + } + + /* push packet */ + m_buffer[m_head] = new RXPacket(m); + m_head = next(m_head); +} + +RXPacket * +RXPacketBuffer::pop() { + assert(!is_empty()); + + RXPacket *pkt = m_buffer[m_tail]; + m_tail = next(m_tail); + + return pkt; +} + +uint64_t +RXPacketBuffer::get_element_count() const { + if (m_head >= m_tail) { + return (m_head - m_tail); + } else { + return ( get_capacity() - (m_tail - m_head - 1) ); + } +} + +Json::Value +RXPacketBuffer::to_json() const { + + Json::Value output = Json::arrayValue; + + int tmp = m_tail; + while (tmp != m_head) { + RXPacket *pkt = m_buffer[tmp]; + output.append(pkt->to_json()); + tmp = next(tmp); + } + + return output; +} + + +void +RXQueue::start(uint64_t size) { + if (m_pkt_buffer) { + delete m_pkt_buffer; + } + m_pkt_buffer = new RXPacketBuffer(size); +} + +void +RXQueue::stop() { + if (m_pkt_buffer) { + delete m_pkt_buffer; + m_pkt_buffer = NULL; + } +} + +const RXPacketBuffer * +RXQueue::fetch() { + + /* if no buffer or the buffer is empty - give a NULL one */ + if ( (!m_pkt_buffer) || (m_pkt_buffer->get_element_count() == 0) ) { + return nullptr; + } + + /* hold a pointer to the old one */ + RXPacketBuffer *old_buffer = m_pkt_buffer; + + /* replace the old one with a new one and freeze the old */ + m_pkt_buffer = new RXPacketBuffer(old_buffer->get_capacity()); + + return old_buffer; +} + +void +RXQueue::handle_pkt(const rte_mbuf_t *m) { + m_pkt_buffer->push(m); +} + +Json::Value +RXQueue::to_json() const { + assert(m_pkt_buffer != NULL); + + Json::Value output = Json::objectValue; + + output["size"] = Json::UInt64(m_pkt_buffer->get_capacity()); + output["count"] = Json::UInt64(m_pkt_buffer->get_element_count()); + + return output; +} + +/************************************** + * RX feature recorder + * + *************************************/ + +RXPacketRecorder::RXPacketRecorder() { + m_writer = NULL; + m_count = 0; + m_limit = 0; + m_epoch = -1; + + m_pending_flush = false; +} + +void +RXPacketRecorder::start(const std::string &pcap, uint64_t limit) { + m_writer = CCapWriterFactory::CreateWriter(LIBPCAP, (char *)pcap.c_str()); + if (m_writer == NULL) { + std::stringstream ss; + ss << "unable to create PCAP file: " << pcap; + throw TrexException(ss.str()); + } + + assert(limit > 0); + + m_limit = limit; + m_count = 0; + m_pending_flush = false; + m_pcap_filename = pcap; +} + +void +RXPacketRecorder::stop() { + if (!m_writer) { + return; + } + + delete m_writer; + m_writer = NULL; +} + +void +RXPacketRecorder::flush_to_disk() { + + if (m_writer && m_pending_flush) { + m_writer->flush_to_disk(); + m_pending_flush = false; + } +} + +void +RXPacketRecorder::handle_pkt(const rte_mbuf_t *m) { + if (!m_writer) { + return; + } + + dsec_t now = now_sec(); + if (m_epoch < 0) { + m_epoch = now; + } + + dsec_t dt = now - m_epoch; + + CPktNsecTimeStamp t_c(dt); + m_pkt.time_nsec = t_c.m_time_nsec; + m_pkt.time_sec = t_c.m_time_sec; + + const uint8_t *p = rte_pktmbuf_mtod(m, uint8_t *); + m_pkt.pkt_len = m->pkt_len; + memcpy(m_pkt.raw, p, m->pkt_len); + + m_writer->write_packet(&m_pkt); + m_count++; + m_pending_flush = true; + + if (m_count == m_limit) { + stop(); + } + +} + +Json::Value +RXPacketRecorder::to_json() const { + Json::Value output = Json::objectValue; + + output["pcap_filename"] = m_pcap_filename; + output["limit"] = Json::UInt64(m_limit); + output["count"] = Json::UInt64(m_count); + + return output; +} + +/************************************** + * Port manager + * + *************************************/ + +RXPortManager::RXPortManager() { + clear_all_features(); + m_io = NULL; + m_cpu_dp_u = NULL; +} + + +void +RXPortManager::create(CPortLatencyHWBase *io, + CRFC2544Info *rfc2544, + CRxCoreErrCntrs *err_cntrs, + CCpuUtlDp *cpu_util, + uint8_t crc_bytes_num) { + m_io = io; + m_cpu_dp_u = cpu_util; + m_num_crc_fix_bytes = crc_bytes_num; + + /* init features */ + m_latency.create(rfc2544, err_cntrs); +} + +void RXPortManager::handle_pkt(const rte_mbuf_t *m) { + + /* handle features */ + + if (is_feature_set(LATENCY)) { + m_latency.handle_pkt(m); + } + + if (is_feature_set(RECORDER)) { + m_recorder.handle_pkt(m); + } + + if (is_feature_set(QUEUE)) { + m_queue.handle_pkt(m); + } +} + + +int RXPortManager::process_all_pending_pkts(bool flush_rx) { + + rte_mbuf_t *rx_pkts[64]; + + /* try to read 64 packets clean up the queue */ + uint16_t cnt_p = m_io->rx_burst(rx_pkts, 64); + if (cnt_p == 0) { + return cnt_p; + } + + + m_cpu_dp_u->start_work1(); + + for (int j = 0; j < cnt_p; j++) { + rte_mbuf_t *m = rx_pkts[j]; + + if (!flush_rx) { + // patch relevant only for e1000 driver + if (m_num_crc_fix_bytes) { + rte_pktmbuf_trim(m, m_num_crc_fix_bytes); + } + + handle_pkt(m); + } + + rte_pktmbuf_free(m); + } + + /* commit only if there was work to do ! */ + m_cpu_dp_u->commit1(); + + + return cnt_p; +} + +void +RXPortManager::tick() { + if (is_feature_set(RECORDER)) { + m_recorder.flush_to_disk(); + } +} + +Json::Value +RXPortManager::to_json() const { + Json::Value output = Json::objectValue; + + if (is_feature_set(LATENCY)) { + output["latency"] = m_latency.to_json(); + output["latency"]["is_active"] = true; + } else { + output["latency"]["is_active"] = false; + } + + if (is_feature_set(RECORDER)) { + output["sniffer"] = m_recorder.to_json(); + output["sniffer"]["is_active"] = true; + } else { + output["sniffer"]["is_active"] = false; + } + + if (is_feature_set(QUEUE)) { + output["queue"] = m_queue.to_json(); + output["queue"]["is_active"] = true; + } else { + output["queue"]["is_active"] = false; + } + + return output; +} diff --git a/src/stateless/rx/trex_stateless_rx_port_mngr.h b/src/stateless/rx/trex_stateless_rx_port_mngr.h new file mode 100644 index 00000000..c049cb56 --- /dev/null +++ b/src/stateless/rx/trex_stateless_rx_port_mngr.h @@ -0,0 +1,416 @@ +/* + Itay Marom + 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 __TREX_STATELESS_RX_PORT_MNGR_H__ +#define __TREX_STATELESS_RX_PORT_MNGR_H__ + +#include <stdint.h> +#include "common/base64.h" + +#include "common/captureFile.h" + + +class CPortLatencyHWBase; +class CRFC2544Info; +class CRxCoreErrCntrs; + +/************************************** + * RX feature latency + * + *************************************/ +class RXLatency { +public: + + RXLatency(); + + void create(CRFC2544Info *rfc2544, CRxCoreErrCntrs *err_cntrs); + + void handle_pkt(const rte_mbuf_t *m); + + Json::Value to_json() const; + + void get_stats(rx_per_flow_t *rx_stats, + int min, + int max, + bool reset, + TrexPlatformApi::driver_stat_cap_e type); + + void reset_stats(); + +private: + bool is_flow_stat_id(uint32_t id) { + if ((id & 0x000fff00) == IP_ID_RESERVE_BASE) return true; + return false; + } + + bool is_flow_stat_payload_id(uint32_t id) { + if (id == FLOW_STAT_PAYLOAD_IP_ID) return true; + return false; + } + + uint16_t get_hw_id(uint16_t id) { + return (0x00ff & id); +} + +public: + + rx_per_flow_t m_rx_pg_stat[MAX_FLOW_STATS]; + rx_per_flow_t m_rx_pg_stat_payload[MAX_FLOW_STATS_PAYLOAD]; + + bool m_rcv_all; + CRFC2544Info *m_rfc2544; + CRxCoreErrCntrs *m_err_cntrs; +}; + +/** + * describes a single saved RX packet + * + */ +class RXPacket { +public: + + RXPacket(const rte_mbuf_t *m) { + /* assume single part packet */ + assert(m->nb_segs == 1); + + m_size = m->pkt_len; + const uint8_t *p = rte_pktmbuf_mtod(m, uint8_t *); + + m_raw = new uint8_t[m_size]; + memcpy(m_raw, p, m_size); + + /* generate a packet timestamp */ + m_timestamp = now_sec(); + } + + /* slow path and also RVO - pass by value is ok */ + Json::Value to_json() { + Json::Value output; + output["ts"] = m_timestamp; + output["binary"] = base64_encode(m_raw, m_size); + return output; + } + + ~RXPacket() { + if (m_raw) { + delete [] m_raw; + } + } + +private: + + uint8_t *m_raw; + uint16_t m_size; + dsec_t m_timestamp; +}; + + +/************************************** + * RX feature queue + * + *************************************/ + +class RXPacketBuffer { +public: + + RXPacketBuffer(uint64_t size); + ~RXPacketBuffer(); + + /** + * push a packet to the buffer + * + */ + void push(const rte_mbuf_t *m); + + /** + * generate a JSON output of the queue + * + */ + Json::Value to_json() const; + + + bool is_empty() const { + return (m_head == m_tail); + } + + bool is_full() const { + return ( next(m_head) == m_tail); + } + + /** + * return the total amount of space possible + */ + uint64_t get_capacity() const { + /* one slot is used for diff between full/empty */ + return (m_size - 1); + } + + /** + * returns how many elements are in the queue + */ + uint64_t get_element_count() const; + +private: + int next(int v) const { + return ( (v + 1) % m_size ); + } + + /* pop in case of full queue - internal usage */ + RXPacket * pop(); + + int m_head; + int m_tail; + int m_size; + RXPacket **m_buffer; +}; + + +class RXQueue { +public: + RXQueue() { + m_pkt_buffer = nullptr; + } + + ~RXQueue() { + stop(); + } + + /** + * start RX queue + * + */ + void start(uint64_t size); + + /** + * fetch the current buffer + * return NULL if no packets + */ + const RXPacketBuffer * fetch(); + + /** + * stop RX queue + * + */ + void stop(); + + void handle_pkt(const rte_mbuf_t *m); + + Json::Value to_json() const; + +private: + RXPacketBuffer *m_pkt_buffer; +}; + +/************************************** + * RX feature PCAP recorder + * + *************************************/ + +class RXPacketRecorder { +public: + RXPacketRecorder(); + + ~RXPacketRecorder() { + stop(); + } + + void start(const std::string &pcap, uint64_t limit); + void stop(); + void handle_pkt(const rte_mbuf_t *m); + + /** + * flush any cached packets to disk + * + */ + void flush_to_disk(); + + Json::Value to_json() const; + +private: + CFileWriterBase *m_writer; + std::string m_pcap_filename; + CCapPktRaw m_pkt; + dsec_t m_epoch; + uint64_t m_limit; + uint64_t m_count; + bool m_pending_flush; +}; + + +/************************ manager ***************************/ + +/** + * per port RX features manager + * + * @author imarom (10/30/2016) + */ +class RXPortManager { +public: + enum feature_t { + NO_FEATURES = 0x0, + LATENCY = 0x1, + RECORDER = 0x2, + QUEUE = 0x4 + }; + + RXPortManager(); + + void create(CPortLatencyHWBase *io, + CRFC2544Info *rfc2544, + CRxCoreErrCntrs *err_cntrs, + CCpuUtlDp *cpu_util, + uint8_t crc_bytes_num); + + void clear_stats() { + m_latency.reset_stats(); + } + + void get_latency_stats(rx_per_flow_t *rx_stats, + int min, + int max, + bool reset, + TrexPlatformApi::driver_stat_cap_e type) { + + return m_latency.get_stats(rx_stats, min, max, reset, type); + } + + RXLatency & get_latency() { + return m_latency; + } + + /* latency */ + void enable_latency() { + set_feature(LATENCY); + } + + void disable_latency() { + unset_feature(LATENCY); + } + + /* recorder */ + void start_recorder(const std::string &pcap, uint64_t limit_pkts) { + m_recorder.start(pcap, limit_pkts); + set_feature(RECORDER); + } + + void stop_recorder() { + m_recorder.stop(); + unset_feature(RECORDER); + } + + /* queue */ + void start_queue(uint32_t size) { + m_queue.start(size); + set_feature(QUEUE); + } + + void stop_queue() { + m_queue.stop(); + unset_feature(QUEUE); + } + + const RXPacketBuffer *get_pkt_buffer() { + if (!is_feature_set(QUEUE)) { + return nullptr; + } + + return m_queue.fetch(); + } + + + + /** + * fetch and process all packets + * + */ + int process_all_pending_pkts(bool flush_rx = false); + + + /** + * flush all pending packets without processing them + * + */ + void flush_all_pending_pkts() { + process_all_pending_pkts(true); + } + + + /** + * handle a single packet + * + */ + void handle_pkt(const rte_mbuf_t *m); + + /** + * maintenance + * + * @author imarom (11/24/2016) + */ + void tick(); + + bool has_features_set() { + return (m_features != NO_FEATURES); + } + + + bool no_features_set() { + return (!has_features_set()); + } + + /** + * write the status to a JSON format + */ + Json::Value to_json() const; + +private: + + void clear_all_features() { + m_features = NO_FEATURES; + } + + void set_feature(feature_t feature) { + m_features |= feature; + } + + void unset_feature(feature_t feature) { + m_features &= (~feature); + } + + bool is_feature_set(feature_t feature) const { + return ( (m_features & feature) == feature ); + } + + uint32_t m_features; + + RXLatency m_latency; + RXPacketRecorder m_recorder; + RXQueue m_queue; + // compensate for the fact that hardware send us packets without Ethernet CRC, and we report with it + uint8_t m_num_crc_fix_bytes; + + CCpuUtlDp *m_cpu_dp_u; + CPortLatencyHWBase *m_io; +}; + + + +#endif /* __TREX_STATELESS_RX_PORT_MNGR_H__ */ + diff --git a/src/trex_port_attr.cpp b/src/trex_port_attr.cpp new file mode 100644 index 00000000..61e88585 --- /dev/null +++ b/src/trex_port_attr.cpp @@ -0,0 +1,145 @@ +/* +Copyright (c) 2015-2015 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 "trex_port_attr.h" +#include "bp_sim.h" + +DestAttr::DestAttr(uint8_t port_id) { + m_port_id = port_id; + + m_mac = CGlobalInfo::m_options.m_mac_addr[port_id].u.m_mac.dest; + m_type = DEST_TYPE_MAC; + + /* save the default */ + memcpy(m_default_mac, m_mac, 6); +} + + +/** + * set dest as an IPv4 unresolved + */ +void +DestAttr::set_dest(uint32_t ipv4) { + assert(ipv4 != 0); + + m_ipv4 = ipv4; + memset(m_mac, 0, 6); // just to be on the safe side + m_type = DEST_TYPE_IPV4_UNRESOLVED; +} + +/** + * set dest as a resolved IPv4 + */ +void +DestAttr::set_dest(uint32_t ipv4, const uint8_t *mac) { + assert(ipv4 != 0); + + m_ipv4 = ipv4; + + /* source might be the same as dest (this shadows the datapath memory) */ + memmove(m_mac, mac, 6); + m_type = DEST_TYPE_IPV4; +} + +/** + * dest dest as MAC + * + */ +void +DestAttr::set_dest(const uint8_t *mac) { + + m_ipv4 = 0; + + /* source might be the same as dest (this shadows the datapath memory) */ + memmove(m_mac, mac, 6); + m_type = DEST_TYPE_MAC; +} + +void +DestAttr::to_json(Json::Value &output) const { + switch (m_type) { + + case DEST_TYPE_IPV4: + output["type"] = "ipv4"; + output["ipv4"] = utl_uint32_to_ipv4(m_ipv4); + output["arp"] = utl_macaddr_to_str(m_mac); + break; + + case DEST_TYPE_IPV4_UNRESOLVED: + output["type"] = "ipv4_u"; + output["ipv4"] = utl_uint32_to_ipv4(m_ipv4); + break; + + case DEST_TYPE_MAC: + output["type"] = "mac"; + output["mac"] = utl_macaddr_to_str(m_mac); + break; + + default: + assert(0); + } + +} + +const uint8_t * +TRexPortAttr::get_src_mac() const { + return CGlobalInfo::m_options.get_src_mac_addr(m_port_id); +} + + +std::string +TRexPortAttr::get_rx_filter_mode() const { + switch (m_rx_filter_mode) { + case RX_FILTER_MODE_ALL: + return "all"; + case RX_FILTER_MODE_HW: + return "hw"; + default: + assert(0); + } +} + + +void +TRexPortAttr::to_json(Json::Value &output) { + + output["src_mac"] = utl_macaddr_to_str(get_src_mac()); + output["promiscuous"]["enabled"] = get_promiscuous(); + output["link"]["up"] = is_link_up(); + output["speed"] = get_link_speed() / 1000; // make sure we have no cards of less than 1 Gbps + output["rx_filter_mode"] = get_rx_filter_mode(); + + if (get_src_ipv4() != 0) { + output["src_ipv4"] = utl_uint32_to_ipv4(get_src_ipv4()); + } else { + output["src_ipv4"] = Json::nullValue; + } + + + int mode; + get_flow_ctrl(mode); + output["fc"]["mode"] = mode; + + m_dest.to_json(output["dest"]); + +} + +void +TRexPortAttr::update_src_dst_mac(uint8_t *raw_pkt) { + memcpy(raw_pkt, get_dest().get_dest_mac(), 6); + memcpy(raw_pkt + 6, get_src_mac(), 6); +} + diff --git a/src/trex_port_attr.h b/src/trex_port_attr.h index 9231e263..3cb9beff 100755 --- a/src/trex_port_attr.h +++ b/src/trex_port_attr.h @@ -21,10 +21,93 @@ limitations under the License. #include <vector> #include "rte_ethdev_includes.h" #include "trex_defs.h" +#include "common/basic_utils.h" +#include <json/json.h> +#include "trex_stateless_rx_defs.h" +#include <string.h> + +/** + * destination port attribute + * + */ +class DestAttr { + +public: + + DestAttr(uint8_t port_id); + + /** + * dest can be either MAC IPv4, or IPv4 unresolved + */ + enum dest_type_e { + DEST_TYPE_IPV4 = 1, + DEST_TYPE_IPV4_UNRESOLVED, + DEST_TYPE_MAC, + }; + + /** + * set dest as an IPv4 unresolved + */ + void set_dest(uint32_t ipv4); + + /** + * set dest as a resolved IPv4 + */ + void set_dest(uint32_t ipv4, const uint8_t *mac); + + /** + * set dest as a plain MAC + */ + void set_dest(const uint8_t *mac); + + + /** + * return true if destination is resolved + */ + bool is_resolved() const { + return (m_type != DEST_TYPE_IPV4_UNRESOLVED); + } + + /** + * get the dest mac + * if the dest is not resolved + * it will return the default MAC + */ + const uint8_t *get_dest_mac() { + return m_mac; + } + + /** + * when link gets down - this should be called + * + */ + void on_link_down() { + if (m_type == DEST_TYPE_IPV4) { + /* reset the IPv4 dest with no resolution */ + set_dest(m_ipv4); + } + } + + void to_json(Json::Value &output) const; + +private: + uint32_t m_ipv4; + uint8_t *m_mac; + dest_type_e m_type; + uint8_t m_port_id; + +private: + uint8_t m_default_mac[6]; +}; class TRexPortAttr { public: + + TRexPortAttr(uint8_t port_id) : m_dest(port_id) { + m_src_ipv4 = 0; + } + virtual ~TRexPortAttr(){} /* UPDATES */ @@ -33,10 +116,10 @@ public: virtual void update_device_info() = 0; virtual void reset_xstats() = 0; virtual void update_description() = 0; - + /* GETTERS */ virtual bool get_promiscuous() = 0; - virtual void macaddr_get(struct ether_addr *mac_addr) = 0; + virtual void get_hw_src_mac(struct ether_addr *mac_addr) = 0; virtual uint32_t get_link_speed() { return m_link.link_speed; } // L1 Mbps virtual bool is_link_duplex() { return (m_link.link_duplex ? true : false); } virtual bool is_link_autoneg() { return (m_link.link_autoneg ? true : false); } @@ -51,24 +134,50 @@ public: virtual void get_description(std::string &description) { description = intf_info_st.description; } virtual void get_supported_speeds(supp_speeds_t &supp_speeds) = 0; + uint32_t get_src_ipv4() {return m_src_ipv4;} + DestAttr & get_dest() {return m_dest;} + + const uint8_t *get_src_mac() const; + std::string get_rx_filter_mode() const; + + /* for a raw packet, write the src/dst MACs */ + void update_src_dst_mac(uint8_t *raw_pkt); + /* SETTERS */ virtual int set_promiscuous(bool enabled) = 0; virtual int add_mac(char * mac) = 0; virtual int set_link_up(bool up) = 0; virtual int set_flow_ctrl(int mode) = 0; virtual int set_led(bool on) = 0; - -/* DUMPS */ + virtual int set_rx_filter_mode(rx_filter_mode_e mode) = 0; + + void set_src_ipv4(uint32_t addr) { + m_src_ipv4 = addr; + } + + /* DUMPS */ virtual void dump_link(FILE *fd) = 0; + /* dump object status to JSON */ + void to_json(Json::Value &output); + + protected: - uint8_t m_port_id; - rte_eth_link m_link; - struct rte_eth_dev_info dev_info; - bool flag_is_virtual; - bool flag_is_fc_change_supported; - bool flag_is_led_change_supported; - bool flag_is_link_change_supported; + + uint8_t m_port_id; + rte_eth_link m_link; + uint32_t m_src_ipv4; + DestAttr m_dest; + + struct rte_eth_dev_info dev_info; + + rx_filter_mode_e m_rx_filter_mode; + + bool flag_is_virtual; + bool flag_is_fc_change_supported; + bool flag_is_led_change_supported; + bool flag_is_link_change_supported; + struct intf_info_st { std::string pci_addr; @@ -81,8 +190,11 @@ protected: class DpdkTRexPortAttr : public TRexPortAttr { public: - DpdkTRexPortAttr(uint8_t port_id, bool is_virtual, bool fc_change_allowed) { + DpdkTRexPortAttr(uint8_t port_id, bool is_virtual, bool fc_change_allowed) : TRexPortAttr(port_id) { + m_port_id = port_id; + m_rx_filter_mode = RX_FILTER_MODE_HW; + flag_is_virtual = is_virtual; int tmp; flag_is_fc_change_supported = fc_change_allowed && (get_flow_ctrl(tmp) != -ENOTSUP); @@ -101,7 +213,7 @@ public: /* GETTERS */ virtual bool get_promiscuous(); - virtual void macaddr_get(struct ether_addr *mac_addr); + virtual void get_hw_src_mac(struct ether_addr *mac_addr); virtual int get_xstats_values(xstats_values_t &xstats_values); virtual int get_xstats_names(xstats_names_t &xstats_names); virtual int get_flow_ctrl(int &mode); @@ -114,6 +226,7 @@ public: virtual int set_flow_ctrl(int mode); virtual int set_led(bool on); + virtual int set_rx_filter_mode(rx_filter_mode_e mode); /* DUMPS */ virtual void dump_link(FILE *fd); @@ -128,7 +241,7 @@ private: class SimTRexPortAttr : public TRexPortAttr { public: - SimTRexPortAttr() { + SimTRexPortAttr() : TRexPortAttr(0) { m_link.link_speed = 10000; m_link.link_duplex = 1; m_link.link_autoneg = 0; @@ -146,7 +259,7 @@ public: void reset_xstats() {} void update_description() {} bool get_promiscuous() { return false; } - void macaddr_get(struct ether_addr *mac_addr) {} + void get_hw_src_mac(struct ether_addr *mac_addr) {} int get_xstats_values(xstats_values_t &xstats_values) { return -ENOTSUP; } int get_xstats_names(xstats_names_t &xstats_names) { return -ENOTSUP; } int get_flow_ctrl(int &mode) { return -ENOTSUP; } @@ -158,6 +271,7 @@ public: int set_flow_ctrl(int mode) { return -ENOTSUP; } int set_led(bool on) { return -ENOTSUP; } void dump_link(FILE *fd) {} + int set_rx_filter_mode(rx_filter_mode_e mode) { return -ENOTSUP; } }; diff --git a/src/utl_ip.h b/src/utl_ip.h index 95db588a..27bb6c81 100644 --- a/src/utl_ip.h +++ b/src/utl_ip.h @@ -105,6 +105,7 @@ class COneIPInfo { } COneIPInfo_ip_types; public: + virtual ~COneIPInfo() {} virtual void get_mac(uint8_t *mac) const { m_mac.copyToArray(mac); } @@ -154,6 +155,7 @@ class COneIPv4Info : public COneIPInfo { COneIPv4Info(uint32_t ip, uint16_t vlan, MacAddress mac, uint8_t port) : COneIPInfo(vlan, mac, port) { m_ip = ip; } + ~COneIPv4Info() {}; uint32_t get_ip() {return m_ip;} virtual uint8_t ip_ver() const {return IP4_VER;} virtual uint32_t get_arp_req_len() const {return 60;} @@ -194,6 +196,7 @@ class COneIPv6Info : public COneIPInfo { COneIPv6Info(uint16_t ip[8], uint16_t vlan, MacAddress mac, uint8_t port) : COneIPInfo(vlan, mac, port) { memcpy(m_ip, ip, sizeof(m_ip)); } + ~COneIPv6Info() {} const uint8_t *get_ipv6() {return (uint8_t *)m_ip;} virtual uint8_t ip_ver() const {return IP6_VER;} diff --git a/src/utl_term_io.cpp b/src/utl_term_io.cpp index 8e561188..e45aeebd 100755 --- a/src/utl_term_io.cpp +++ b/src/utl_term_io.cpp @@ -78,6 +78,13 @@ int utl_termio_init(){ atexit(exit_handler1); save_termio(); set_conio_terminal_mode(); + + /* stdout is non-blocking */ + int fd = fileno(stdout); + int f = fcntl(fd, F_GETFL, 0); + f |= O_NONBLOCK; + fcntl(fd, F_SETFL, f); + return (0); } |