From 6c7274ac25da5e7a5e162b071cfc805991498408 Mon Sep 17 00:00:00 2001 From: elliotthenne Date: Sun, 25 Jul 2021 14:05:01 -0700 Subject: [PATCH 1/5] adds test_performance directory to examples directory --- examples/test_performance/REAME.md | 10 ++++ examples/test_performance/change_nfs.py | 33 ++++++++++++ examples/test_performance/clear_files.py | 27 ++++++++++ examples/test_performance/data.txt | 4 ++ examples/test_performance/make_data.py | 47 ++++++++++++++++ examples/test_performance/make_graph.py | 51 ++++++++++++++++++ .../test_performance/performance_graph.png | Bin 0 -> 27177 bytes examples/test_performance/test_performance.sh | 36 +++++++++++++ 8 files changed, 208 insertions(+) create mode 100644 examples/test_performance/REAME.md create mode 100644 examples/test_performance/change_nfs.py create mode 100644 examples/test_performance/clear_files.py create mode 100644 examples/test_performance/data.txt create mode 100644 examples/test_performance/make_data.py create mode 100644 examples/test_performance/make_graph.py create mode 100644 examples/test_performance/performance_graph.png create mode 100644 examples/test_performance/test_performance.sh diff --git a/examples/test_performance/REAME.md b/examples/test_performance/REAME.md new file mode 100644 index 000000000..a2ba7c016 --- /dev/null +++ b/examples/test_performance/REAME.md @@ -0,0 +1,10 @@ +TEST PERFORMANCE +== +Script that generates a graph of how latency and throughput of an NF chain changes based on its length + +To be determined +-fix graph, what axes and measurments to use +-specify before running, what # NFs to use (user will input as a flag) + +cd examples/test_performance +./test_performance.sh ###SPECIFY NUMBER OF NFS \ No newline at end of file diff --git a/examples/test_performance/change_nfs.py b/examples/test_performance/change_nfs.py new file mode 100644 index 000000000..b0e2c94f5 --- /dev/null +++ b/examples/test_performance/change_nfs.py @@ -0,0 +1,33 @@ +###This script modifies the example_nf_deploy.json file so that it adds an additional simple_forward nf + +import json + +#num_nfs = input("Enter the number of nfs: ") + +with open("../example_nf_deploy.json", "r+") as file: + json_data = file.read() +data = json.loads(json_data) + +### finds the last NF, deletes it, replaces it with modified parameters, adds another NF +nf_index_to_change = len(data["simple_forward"]) - 1 + +simple_forward_json = data["simple_forward"][nf_index_to_change]["parameters"] + +start_nf = simple_forward_json[:-1] +start_nf = start_nf + str((int(start_nf[0]) +1 ) ) #makes new destination by concatenating the ID of the last nf (to be added) + +end_nf = start_nf[-1] + " -d 1" # causes the last NF to route back to the speed_tester + +start_nf_dict = {"parameters":start_nf} +end_nf_dict = {"parameters":end_nf} + + +del data["simple_forward"][nf_index_to_change] + +data["simple_forward"].append(start_nf_dict) +data["simple_forward"].append(end_nf_dict) + +new_data = json.dumps(data, indent=2) + +with open("../example_nf_deploy.json", "w") as file: + json.dump(data, file, indent=8) diff --git a/examples/test_performance/clear_files.py b/examples/test_performance/clear_files.py new file mode 100644 index 000000000..b1418e0cb --- /dev/null +++ b/examples/test_performance/clear_files.py @@ -0,0 +1,27 @@ +#this script resets the data.txt and example_nf_deploy scripts and returns them to their original state + +import json + +with open("data.txt", 'r+') as f: + f.truncate(0) + +with open("../example_nf_deploy.json", "w") as f: + data = {"globals": [ + { + "TTL": 1 + }, + { + "directory": "output_files" + } + ], + "simple_forward": [ + { + "parameters": "2 -d 1" + } + ], + "speed_tester": [ + { + "parameters": "1 -d 2 -l -c 16000" + } + ]} + json.dump(data, f, indent=8) \ No newline at end of file diff --git a/examples/test_performance/data.txt b/examples/test_performance/data.txt new file mode 100644 index 000000000..11b37ab4a --- /dev/null +++ b/examples/test_performance/data.txt @@ -0,0 +1,4 @@ +2,23727358,662157, +3,26727358,702157, +4,30727358,742157, +5,33727358,792157, \ No newline at end of file diff --git a/examples/test_performance/make_data.py b/examples/test_performance/make_data.py new file mode 100644 index 000000000..1a349df67 --- /dev/null +++ b/examples/test_performance/make_data.py @@ -0,0 +1,47 @@ +#This script creates the data necessary to make a graph of how +#the latency and throughput of an NF chain changes based on length +#outputs 3 numbers to 'data.txt': [#nfs, TX average, and latency average] + +num_nfs = input("Enter the number of nfs: ") + +file1 = open("/users/elliott1/openNetVM/examples/output_files/log-speed_tester-1.txt", "r") + + +TX_data = [] +latency_data = [] + +### adds the correct values from the log-speed_tester file +for line in file1.readlines(): + if ('TX pkts per second: ' in line): + index = line.index(': ') + 3 #spacing is weird in the data file + TX_data.append(line[index:]) + if ('Avg latency nanoseconds: ' in line): + index = line.index(': ') + 2 + latency_data.append(line[index:]) + +file1.close() + +### find the averages of the data + +TX_average = 0 + +for x in TX_data: + TX_average+=int(x) + +TX_average /= len(TX_data) + +latency_average = 0 + +for x in latency_data: + latency_average+=int(x) +latency_average /= len(latency_data) + +##writes the data out as 3 values, all seperated by commas, to the data.txt file + +f = open("data.txt", "a") +f.write(num_nfs) +f.write(',') +f.write(str(int(TX_average))) +f.write(',') +f.write(str(int(latency_average))) +f.write(',\n') \ No newline at end of file diff --git a/examples/test_performance/make_graph.py b/examples/test_performance/make_graph.py new file mode 100644 index 000000000..501019123 --- /dev/null +++ b/examples/test_performance/make_graph.py @@ -0,0 +1,51 @@ +"""This script allows a user to create a graph measuring +how the latency and throughput of an NF chain changes based on length""" + +from matplotlib import pyplot as plt + +### we need to make a 2nd y-axis, so we use the twinx() fnx +ax = plt.subplot() +twin1 = ax.twinx() + +### create empty lists to store in future data +x_axis = [] + +y_axis_latency = [] + +y_axis_throughput = [] + +###reads in data from file, adds them to empty variables +file = open("data.txt", "r") + +for line in file.readlines(): + linedata = line.split(",") + x_axis.append(linedata[0]) + y_axis_latency.append(linedata[1]) + y_axis_throughput.append(linedata[2]) + +#ax.set_ylim(30000, 100000) +#twin1.set_ylim(15000000, 40000000) + +### plot the data +for x in y_axis_latency: + print(x) +p1, = ax.plot(x_axis, y_axis_latency,"-b", label="Latency (ns)") +ax.set_xlabel("NF Chain Length") +ax.set_ylabel("Latency (ns)") + +p2, = twin1.plot(x_axis, y_axis_throughput, "-r", label="TX pkts per second") +twin1.set_ylabel("TX pkts per second") + +###creates the legend and sets colors +ax.yaxis.label.set_color(p1.get_color()) +twin1.yaxis.label.set_color(p2.get_color()) + +tkw = dict(size=4, width=1.5) +ax.tick_params(axis='y', colors=p1.get_color(), **tkw) +twin1.tick_params(axis='y', colors=p2.get_color(), **tkw) +ax.tick_params(axis='x', **tkw) + +ax.legend(handles=[p1, p2]) + +### saves the .png file in test_performance directory +plt.savefig('performance_graph.png') \ No newline at end of file diff --git a/examples/test_performance/performance_graph.png b/examples/test_performance/performance_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b1e13674cf2eba7bb026a1f62c646d53360584 GIT binary patch literal 27177 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 literal 0 HcmV?d00001 diff --git a/examples/test_performance/test_performance.sh b/examples/test_performance/test_performance.sh new file mode 100644 index 000000000..05b9199e6 --- /dev/null +++ b/examples/test_performance/test_performance.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +#assume 5 NFs + +NUM_NFS=2 + +MAX_NFS=6 #tbd + +while [ $NUM_NFS != $MAX_NFS ] +do + echo "Running onvm mgr" + cd ../../ + ./onvm/go.sh -k 1 -n 0xF8 -s stdout -t 30 & + + + sleep 15s + + echo "Running $NUM_NFS NFs" + cd examples + python3 ./run_group.py example_nf_deploy.json #starts running the set NFs for 30sec, enough time to collect ample data + + sleep 30s + + cd test_performance + echo "Making data" + python3 ./make_data.py <<< $NUM_NFS #adds the data and averages it + + NUM_NFS=$((NUM_NFS+1)) + + echo "Changing NFs" + python3 ./change_nfs.py #adds an additional simple_forward NF for the next loop iteration +done + +python3 ./make_graph.py # makes the graph + +python3 ./clear_files.py #resets data files \ No newline at end of file From 2dc9e8d15acb5047f910e62bdbe5cf45fbd99ade Mon Sep 17 00:00:00 2001 From: elliotthenne Date: Sun, 25 Jul 2021 14:05:40 -0700 Subject: [PATCH 2/5] adds test_performance directory to examples directory --- examples/test_performance/data.txt | 4 ---- examples/test_performance/performance_graph.png | Bin 27177 -> 0 bytes 2 files changed, 4 deletions(-) delete mode 100644 examples/test_performance/performance_graph.png diff --git a/examples/test_performance/data.txt b/examples/test_performance/data.txt index 11b37ab4a..e69de29bb 100644 --- a/examples/test_performance/data.txt +++ b/examples/test_performance/data.txt @@ -1,4 +0,0 @@ -2,23727358,662157, -3,26727358,702157, -4,30727358,742157, -5,33727358,792157, \ No newline at end of file diff --git a/examples/test_performance/performance_graph.png b/examples/test_performance/performance_graph.png deleted file mode 100644 index c9b1e13674cf2eba7bb026a1f62c646d53360584..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27177 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 From 3ded1771e976b41b7b525b8b630b3245f6135d2d Mon Sep 17 00:00:00 2001 From: elliott1 Date: Mon, 9 Aug 2021 10:29:41 -0500 Subject: [PATCH 3/5] Updated test_performance directory, added new features --- examples/test_performance/REAME.md | 33 ++++- examples/test_performance/change_nfs.py | 66 +++++++--- examples/test_performance/clear_files.py | 11 +- examples/test_performance/data-copy.txt | 0 examples/test_performance/make_data.py | 34 +++-- examples/test_performance/make_graph.py | 121 +++++++++++++----- examples/test_performance/test_performance.sh | 120 +++++++++++++---- 7 files changed, 294 insertions(+), 91 deletions(-) create mode 100644 examples/test_performance/data-copy.txt diff --git a/examples/test_performance/REAME.md b/examples/test_performance/REAME.md index a2ba7c016..4d053e0e1 100644 --- a/examples/test_performance/REAME.md +++ b/examples/test_performance/REAME.md @@ -1,10 +1,33 @@ TEST PERFORMANCE == -Script that generates a graph of how latency and throughput of an NF chain changes based on its length +This directory contains a script that generates a graph of how the latency and throughput of an NF chain changes based on its length -To be determined --fix graph, what axes and measurments to use --specify before running, what # NFs to use (user will input as a flag) +Optional Libraries +-- +matplotlib library is required for graph generation. +If libpcap is not installed run (might need to update/upgrade): +``` +sudo apt-get install python3-matplotlib +``` +Compilation and Execution +-- +``` cd examples/test_performance -./test_performance.sh ###SPECIFY NUMBER OF NFS \ No newline at end of file + ./test_performanmce.sh -s [specific lengths of NFs] + + OR + + ./test_performanmce.sh -i [one int specifying the max # of NFs] + + ``` + +SUMMARY: + +test_performance.sh -- a script that generates data based on a changing # of NFs + +make_data.py -- parses through NF data and outputs it to a data.txt + +make_graph.py -- uses the appropriate data in the data.txt file to make a graph + +clear_files.py -- resets data.txt file so that the script can run again diff --git a/examples/test_performance/change_nfs.py b/examples/test_performance/change_nfs.py index b0e2c94f5..6ce11e31d 100644 --- a/examples/test_performance/change_nfs.py +++ b/examples/test_performance/change_nfs.py @@ -1,33 +1,59 @@ -###This script modifies the example_nf_deploy.json file so that it adds an additional simple_forward nf +#!/usr/bin/python + +### This script modifies the example_nf_deploy.json file so that it runs (input - 1) simple_forward NFs +### It runs (input -1) simple_forward NFs, 1 speed tester, or (input) total nfs import json +import sys + +num_nfs = 1 + +if len(sys.argv) == 2: + num_nfs = int(sys.argv[1]) -#num_nfs = input("Enter the number of nfs: ") +print(f"did it work-change nfs: {num_nfs}") with open("../example_nf_deploy.json", "r+") as file: json_data = file.read() data = json.loads(json_data) ### finds the last NF, deletes it, replaces it with modified parameters, adds another NF -nf_index_to_change = len(data["simple_forward"]) - 1 - -simple_forward_json = data["simple_forward"][nf_index_to_change]["parameters"] - -start_nf = simple_forward_json[:-1] -start_nf = start_nf + str((int(start_nf[0]) +1 ) ) #makes new destination by concatenating the ID of the last nf (to be added) - -end_nf = start_nf[-1] + " -d 1" # causes the last NF to route back to the speed_tester - -start_nf_dict = {"parameters":start_nf} -end_nf_dict = {"parameters":end_nf} - - -del data["simple_forward"][nf_index_to_change] - -data["simple_forward"].append(start_nf_dict) -data["simple_forward"].append(end_nf_dict) +while(num_nfs - 1 > len(data["simple_forward"])): + nf_index_to_change = len(data["simple_forward"]) - 1 + + simple_forward_json = data["simple_forward"][nf_index_to_change]["parameters"] + # print(len(data["simple_forward"])) + # print(f"simple_forward_json: {simple_forward_json}") + if (len(data["simple_forward"]) < 8): + start_nf = simple_forward_json[:-1] + else: + start_nf = simple_forward_json[:-1] + # print(f"start_nf: {start_nf}") + # print(f"start_nf[0]: {start_nf[0]} is type {type(start_nf[0])}") + + # print(f"str((int(start_nf[0]) +1 ) : {(int(start_nf[0]) +1 )}" ) + if (len(data["simple_forward"]) < 9): + start_nf = start_nf + str((int(start_nf[0]) +1 ) ) #makes new destination by concatenating the ID of the last nf (to be added) + else: + start_nf = start_nf + str((int(start_nf[0:2]) +1 ) ) + # print(f"start nf after concatenation: {start_nf}") + + + if (len(data["simple_forward"]) < 8): + end_nf = start_nf[-1] + " -d 1" # causes the last NF to route back to the speed_tester + else: + end_nf = start_nf[-2:] + " -d 1" # causes the last NF to route back to the speed_tester + + # print(f"end_nf: {end_nf}") + start_nf_dict = {"parameters":start_nf} + end_nf_dict = {"parameters":end_nf} + + del data["simple_forward"][nf_index_to_change] + + data["simple_forward"].append(start_nf_dict) + data["simple_forward"].append(end_nf_dict) new_data = json.dumps(data, indent=2) -with open("../example_nf_deploy.json", "w") as file: +with open("../example_nf_deploy_test_p_template.json", "w") as file: json.dump(data, file, indent=8) diff --git a/examples/test_performance/clear_files.py b/examples/test_performance/clear_files.py index b1418e0cb..52f372769 100644 --- a/examples/test_performance/clear_files.py +++ b/examples/test_performance/clear_files.py @@ -1,11 +1,14 @@ -#this script resets the data.txt and example_nf_deploy scripts and returns them to their original state +#!/usr/bin/python +### This script resets the data.txt and example_nf_deploy scripts and returns them to their original state import json with open("data.txt", 'r+') as f: - f.truncate(0) + with open("data-copy.txt", "w") as file: + file.write(f.read()) + f.truncate(0) -with open("../example_nf_deploy.json", "w") as f: +with open("../example_nf_deploy_test_p_template.json", "w") as f: data = {"globals": [ { "TTL": 1 @@ -24,4 +27,4 @@ "parameters": "1 -d 2 -l -c 16000" } ]} - json.dump(data, f, indent=8) \ No newline at end of file + json.dump(data, f, indent=8) diff --git a/examples/test_performance/data-copy.txt b/examples/test_performance/data-copy.txt new file mode 100644 index 000000000..e69de29bb diff --git a/examples/test_performance/make_data.py b/examples/test_performance/make_data.py index 1a349df67..4f0b8cc29 100644 --- a/examples/test_performance/make_data.py +++ b/examples/test_performance/make_data.py @@ -1,11 +1,19 @@ -#This script creates the data necessary to make a graph of how -#the latency and throughput of an NF chain changes based on length -#outputs 3 numbers to 'data.txt': [#nfs, TX average, and latency average] +#!/usr/bin/python -num_nfs = input("Enter the number of nfs: ") +### This script creates the data necessary to make a graph of how +### the latency and throughput of an NF chain changes based on length +### outputs 3 numbers to 'data.txt': [#nfs, TX average, and latency average] -file1 = open("/users/elliott1/openNetVM/examples/output_files/log-speed_tester-1.txt", "r") +import sys +num_nfs = 1 + +if len(sys.argv) == 2: + num_nfs = sys.argv[1] + +print(f"Did it work? (make_data.py): {num_nfs}") + +file1 = open("../output_files/log-speed_tester-1.txt", "r") TX_data = [] latency_data = [] @@ -13,11 +21,21 @@ ### adds the correct values from the log-speed_tester file for line in file1.readlines(): if ('TX pkts per second: ' in line): - index = line.index(': ') + 3 #spacing is weird in the data file + index = line.index(': ') + 3 # spacing is weird in the data file TX_data.append(line[index:]) if ('Avg latency nanoseconds: ' in line): index = line.index(': ') + 2 latency_data.append(line[index:]) + # if ('TX pkts per second: ' in line): + # datapoint = line.split(" ") + # print(f"datapoint[5] is {datapoint[5]} with type {type(datapoint[5])}") + # TX_data.append(datapoint[5]) + # if ('Avg latency nanoseconds: ' in line): + # datapoint = line.split(" ") + # latency_data.append(datapoint[3]) + + ### alternative way of adding data that uses exact spacing + file1.close() @@ -39,9 +57,9 @@ ##writes the data out as 3 values, all seperated by commas, to the data.txt file f = open("data.txt", "a") -f.write(num_nfs) +f.write(str(num_nfs)) f.write(',') f.write(str(int(TX_average))) f.write(',') f.write(str(int(latency_average))) -f.write(',\n') \ No newline at end of file +f.write(',\n') diff --git a/examples/test_performance/make_graph.py b/examples/test_performance/make_graph.py index 501019123..b6bc1c60c 100644 --- a/examples/test_performance/make_graph.py +++ b/examples/test_performance/make_graph.py @@ -1,51 +1,110 @@ -"""This script allows a user to create a graph measuring -how the latency and throughput of an NF chain changes based on length""" +#!/usr/bin/python -from matplotlib import pyplot as plt +### This script creates a graph, using data from the "data.txt" file, measuring +### how the latency and throughput of an NF chain changes based on length -### we need to make a 2nd y-axis, so we use the twinx() fnx -ax = plt.subplot() -twin1 = ax.twinx() +from matplotlib import pyplot as plt -### create empty lists to store in future data x_axis = [] y_axis_latency = [] y_axis_throughput = [] -###reads in data from file, adds them to empty variables file = open("data.txt", "r") for line in file.readlines(): linedata = line.split(",") - x_axis.append(linedata[0]) - y_axis_latency.append(linedata[1]) - y_axis_throughput.append(linedata[2]) + x_axis.append(int(linedata[0])) + y_axis_latency.append(int(linedata[1])) + y_axis_throughput.append(int(linedata[2])) + + +# x_axis.sort() +# y_axis_latency.sort() +# y_axis_throughput.sort() + +fig,ax = plt.subplots() +# make a plot +ax.plot(x_axis, y_axis_latency, color="red", marker="o") +# set x-axis label +ax.set_xlabel("NFs",fontsize=14) +# set y-axis label +ax.set_ylabel("Latency (ns)",color="red",fontsize=14) + +ax2=ax.twinx() +# make a plot with different y-axis using second axis object +ax2.plot(x_axis, y_axis_throughput,color="blue",marker="o") +ax2.set_ylabel("TX pkts per second",color="blue",fontsize=14) +plt.show() +# save the plot as a file +fig.savefig('graph.png', + format='png', + dpi=100, + bbox_inches='tight') + + + + + + + +# from matplotlib import pyplot as plt + +# ### we need to make a 2nd y-axis, so we use the twinx() fnx +# ax = plt.subplot() +# twin1 = ax.twinx() + +# ### create empty lists to store in future data +# x_axis = [] + +# y_axis_latency = [] + +# y_axis_throughput = [] + +# ###reads in data from file, adds them to empty variables +# file = open("data.txt", "r") + +# for line in file.readlines(): +# linedata = line.split(",") +# x_axis.append(int(linedata[0])) +# y_axis_latency.append(int(linedata[1])) +# y_axis_throughput.append(int(linedata[2])) + +# # for datapoint in x_axis: +# # print(f"x value: {datapoint}") + +# # for datapoint in y_axis_latency: +# # print(f"y value: {datapoint}") + +# # for datapoint in y_axis_throughput: +# # print(f"y value: {datapoint}") + +# #ax.set_ylim(30000, 100000) +# #twin1.set_ylim(15000000, 40000000) + +# ### plot the data +# p1, = ax.plot(x_axis, y_axis_latency,"-b", label="Latency (ns)") +# ax.set_xlabel("NF Chain Length") +# ax.set_ylabel("Latency (ns)") + +# ax.autoscale(enable=False, axis='both', tight=None) -#ax.set_ylim(30000, 100000) -#twin1.set_ylim(15000000, 40000000) +# p2, = twin1.plot(x_axis, y_axis_throughput, "-r", label="TX pkts per second") +# twin1.set_ylabel("TX pkts per second") -### plot the data -for x in y_axis_latency: - print(x) -p1, = ax.plot(x_axis, y_axis_latency,"-b", label="Latency (ns)") -ax.set_xlabel("NF Chain Length") -ax.set_ylabel("Latency (ns)") +# ###creates the legend and sets colors +# ax.yaxis.label.set_color(p1.get_color()) +# twin1.yaxis.label.set_color(p2.get_color()) -p2, = twin1.plot(x_axis, y_axis_throughput, "-r", label="TX pkts per second") -twin1.set_ylabel("TX pkts per second") +# tkw = dict(size=4, width=1.5) +# ax.tick_params(axis='y', colors=p1.get_color(), **tkw) +# twin1.tick_params(axis='y', colors=p2.get_color(), **tkw) +# ax.tick_params(axis='x', **tkw) -###creates the legend and sets colors -ax.yaxis.label.set_color(p1.get_color()) -twin1.yaxis.label.set_color(p2.get_color()) +# ax.legend(handles=[p1, p2]) -tkw = dict(size=4, width=1.5) -ax.tick_params(axis='y', colors=p1.get_color(), **tkw) -twin1.tick_params(axis='y', colors=p2.get_color(), **tkw) -ax.tick_params(axis='x', **tkw) -ax.legend(handles=[p1, p2]) -### saves the .png file in test_performance directory -plt.savefig('performance_graph.png') \ No newline at end of file +# ### saves the .png file in test_performance directory +# plt.savefig('performance_graph.png') diff --git a/examples/test_performance/test_performance.sh b/examples/test_performance/test_performance.sh index 05b9199e6..bbe6b74aa 100644 --- a/examples/test_performance/test_performance.sh +++ b/examples/test_performance/test_performance.sh @@ -1,36 +1,110 @@ #!/bin/bash -#assume 5 NFs +echo "Type of script (iterative (i) or selective (s) ) = $1" -NUM_NFS=2 +if [[ $1 = "-s" ]] +then + echo "Running selective version" + NUM_NFS_LIST=( "$@" ) -MAX_NFS=6 #tbd + # check that inputs are valid (positive) integers -while [ $NUM_NFS != $MAX_NFS ] -do - echo "Running onvm mgr" - cd ../../ - ./onvm/go.sh -k 1 -n 0xF8 -s stdout -t 30 & - + VALID_INPUT=true - sleep 15s + for (( i=1; i<${#NUM_NFS_LIST[@]}; i++)); + do + if [[ ${NUM_NFS_LIST[$i]} =~ ^[+]?[0-9]+$ ]]; + then + : + else + VALID_INPUT=false + fi + done - echo "Running $NUM_NFS NFs" - cd examples - python3 ./run_group.py example_nf_deploy.json #starts running the set NFs for 30sec, enough time to collect ample data + if [[ "$VALID_INPUT" != true ]]; + then + echo "Error: your inputs (desired chain lengths) must be positive integers" + exit 0 + fi - sleep 30s + # run manager at each chain length + for (( i=1; i<${#NUM_NFS_LIST[@]}; i++)); + do + echo "Running ${NUM_NFS_LIST[$i]} NFs" + python3 change_nfs.py ${NUM_NFS_LIST[$i]} # adds an additional simple_forward NF for the next loop iteration - cd test_performance - echo "Making data" - python3 ./make_data.py <<< $NUM_NFS #adds the data and averages it + echo "Running onvm mgr" + cd ../../ + ./onvm/go.sh -k 1 -n 0xFFFFF -s stdout -t 30 & + + sleep 15s - NUM_NFS=$((NUM_NFS+1)) + echo "Running ${NUM_NFS_LIST[$i]} NFs" + cd examples + python3 run_group.py example_nf_deploy_test_p_template.json # starts running the set NFs for 30sec, enough time to collect ample data - echo "Changing NFs" - python3 ./change_nfs.py #adds an additional simple_forward NF for the next loop iteration -done + wait -python3 ./make_graph.py # makes the graph + cd test_performance + echo "Making data" + python3 make_data.py ${NUM_NFS_LIST[$i]} # adds the data and averages it -python3 ./clear_files.py #resets data files \ No newline at end of file + done + + python3 ./make_graph.py # makes the graph + + python3 ./clear_files.py # resets data files and nf_deploy json files + +elif [[ $1 = "-i" ]] +then + echo "Running iterative version" + NUM_NFS=2 + + # check if the input is valid + VALID_INPUT=true + + if [[ $2 =~ ^[+]?[0-9]+$ ]]; + then + : + else + VALID_INPUT=false + fi + + if [[ "$VALID_INPUT" != true ]]; + then + echo "Error: your input (max NFs) must be a positive integer" + exit 0 + fi + + # iterate through each version of the NF + while [ $NUM_NFS -le $2 ] + do + echo "Running $NUM_NFS NFs" + echo "Running onvm mgr" + cd ../../ + ./onvm/go.sh -k 1 -n 0xFFFFF -s stdout -t 30 & + + sleep 15s + + echo "Running $NUM_NFS NFs" + cd examples + python3 ./run_group.py ./example_nf_deploy_test_p_template.json # starts running the set NFs for 30sec, enough time to collect ample data + + sleep 30s + + cd test_performance + echo "Making data" + python3 ./make_data.py $NUM_NFS # adds the data and averages it + + NUM_NFS=$((NUM_NFS+1)) + + echo "Changing NFs" + python3 ./change_nfs.py $NUM_NFS # adds an additional simple_forward NF for the next loop iteration + done + + python3 ./make_graph.py # makes the graph + + python3 ./clear_files.py # resets data files +else + echo "Error: incorrect 1st input; must be either \"-i\" or \"-s\"" +fi From f0eb1d1f8899943074184b53fb01c156c8ddb5d0 Mon Sep 17 00:00:00 2001 From: elliott1 Date: Mon, 9 Aug 2021 10:30:39 -0500 Subject: [PATCH 4/5] Updated test_performance directory, added new features --- examples/test_performance/change_nfs.py | 0 examples/test_performance/clear_files.py | 0 examples/test_performance/make_data.py | 0 examples/test_performance/make_graph.py | 0 examples/test_performance/test_performance.sh | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 examples/test_performance/change_nfs.py mode change 100644 => 100755 examples/test_performance/clear_files.py mode change 100644 => 100755 examples/test_performance/make_data.py mode change 100644 => 100755 examples/test_performance/make_graph.py mode change 100644 => 100755 examples/test_performance/test_performance.sh diff --git a/examples/test_performance/change_nfs.py b/examples/test_performance/change_nfs.py old mode 100644 new mode 100755 diff --git a/examples/test_performance/clear_files.py b/examples/test_performance/clear_files.py old mode 100644 new mode 100755 diff --git a/examples/test_performance/make_data.py b/examples/test_performance/make_data.py old mode 100644 new mode 100755 diff --git a/examples/test_performance/make_graph.py b/examples/test_performance/make_graph.py old mode 100644 new mode 100755 diff --git a/examples/test_performance/test_performance.sh b/examples/test_performance/test_performance.sh old mode 100644 new mode 100755 From a3fe2b9e675bee844cea933ae3701019209997de Mon Sep 17 00:00:00 2001 From: elliotthenne Date: Mon, 23 Aug 2021 10:14:05 -0400 Subject: [PATCH 5/5] added .json files and other small upddates --- examples/.gitignore | 1 + examples/Makefile | 2 +- .../DNSAmplificationMitigation.cpp | 2 +- .../NFD/dns_amplification_mitigation/go.sh | 18 +- examples/NFD/go.sh | 2 +- examples/NFD/heavy_hitter_detection/HHD.cpp | 2 +- examples/NFD/heavy_hitter_detection/go.sh | 18 +- examples/NFD/napt/NAPT.cpp | 2 +- examples/NFD/napt/go.sh | 18 +- examples/NFD/stateful_firewall/go.sh | 18 +- .../stateful_firewall/stateful_firewall.cpp | 2 +- examples/NFD/stateless_firewall/go.sh | 18 +- .../stateless_firewall/stateless_firewall.cpp | 2 +- examples/NFD/super_spreader_detection/SSD.cpp | 2 +- examples/NFD/super_spreader_detection/go.sh | 18 +- .../syn_flood_detection/SYNFloodDetection.cpp | 2 +- examples/NFD/syn_flood_detection/go.sh | 18 +- .../UDPFloodMitagation.cpp | 2 +- examples/NFD/udp_flood_mitigation/go.sh | 18 +- examples/aes_decrypt/aesdecrypt.c | 12 +- examples/aes_decrypt/go.sh | 21 +- examples/aes_encrypt/aesencrypt.c | 12 +- examples/aes_encrypt/go.sh | 21 +- examples/arp_response/arp_response.c | 47 +-- examples/arp_response/go.sh | 21 +- examples/basic_monitor/go.sh | 21 +- examples/basic_monitor/monitor.c | 2 +- examples/bridge/bridge.c | 2 +- examples/bridge/go.sh | 21 +- examples/example_nf_deploy.json | 19 + .../example_nf_deploy_test_p_template.json | 20 + examples/firewall/firewall.c | 2 +- examples/firewall/go.sh | 21 +- examples/flow_table/flow_table.c | 2 +- examples/flow_table/go.sh | 21 +- examples/flow_tracker/flow_tracker.c | 2 + examples/flow_tracker/go.sh | 21 +- examples/go.sh | 8 +- examples/l2fwd/Makefile | 62 +++ examples/l2fwd/README.md | 38 ++ examples/l2fwd/go.sh | 20 + examples/l2fwd/l2fwd.c | 353 +++++++++++++++++ examples/load_balancer/go.sh | 21 +- examples/load_balancer/load_balancer.c | 36 +- examples/load_generator/go.sh | 21 +- examples/load_generator/load_generator.c | 29 +- examples/ndpi_stats/go.sh | 21 +- examples/nf_router/README.md | 3 +- examples/nf_router/go.sh | 21 +- examples/nf_router/nf_router.c | 12 +- examples/payload_scan/go.sh | 21 +- examples/payload_scan/payload_scan.c | 8 +- examples/run_group.py | 218 +++++++++++ examples/scaling_example/go.sh | 21 +- examples/scaling_example/scaling.c | 32 +- examples/simple_forward/forward.c | 2 +- examples/simple_forward/go.sh | 21 +- examples/simple_fwd_tb/.gitignore | 2 + examples/simple_fwd_tb/Makefile | 68 ++++ examples/simple_fwd_tb/README.md | 36 ++ examples/simple_fwd_tb/forward_tb.c | 368 ++++++++++++++++++ examples/simple_fwd_tb/go.sh | 20 + examples/speed_tester/go.sh | 21 +- examples/speed_tester/speed_tester.c | 21 +- examples/start_nf.sh | 14 +- examples/test_flow_dir/go.sh | 21 +- examples/test_flow_dir/test_flow_dir.c | 2 +- examples/test_performance/clear_files.py | 38 +- examples/test_performance/data-copy.txt | 3 + examples/test_performance/graph.png | Bin 0 -> 38972 bytes examples/test_performance/test_performance.sh | 2 + 71 files changed, 1865 insertions(+), 172 deletions(-) create mode 100644 examples/.gitignore create mode 100644 examples/example_nf_deploy.json create mode 100644 examples/example_nf_deploy_test_p_template.json create mode 100644 examples/l2fwd/Makefile create mode 100644 examples/l2fwd/README.md create mode 100644 examples/l2fwd/go.sh create mode 100644 examples/l2fwd/l2fwd.c create mode 100644 examples/run_group.py create mode 100644 examples/simple_fwd_tb/.gitignore create mode 100644 examples/simple_fwd_tb/Makefile create mode 100644 examples/simple_fwd_tb/README.md create mode 100644 examples/simple_fwd_tb/forward_tb.c create mode 100644 examples/simple_fwd_tb/go.sh create mode 100644 examples/test_performance/graph.png diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000..4fb4c9612 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +**/*.csv diff --git a/examples/Makefile b/examples/Makefile index b3f0adb62..3ce592312 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -40,7 +40,7 @@ $(error "Please define RTE_SDK environment variable") endif # To add new examples, append the directory name to this variable -examples = bridge basic_monitor simple_forward speed_tester flow_table test_flow_dir aes_encrypt aes_decrypt flow_tracker load_balancer arp_response nf_router scaling_example load_generator payload_scan firewall +examples = bridge basic_monitor simple_forward speed_tester flow_table test_flow_dir aes_encrypt aes_decrypt flow_tracker load_balancer arp_response nf_router scaling_example load_generator payload_scan firewall simple_fwd_tb l2fwd ifeq ($(NDPI_HOME),) $(warning "Skipping ndpi_stats NF as NDPI_HOME is not set") diff --git a/examples/NFD/dns_amplification_mitigation/DNSAmplificationMitigation.cpp b/examples/NFD/dns_amplification_mitigation/DNSAmplificationMitigation.cpp index a3cdaa8c7..1c2ca5373 100644 --- a/examples/NFD/dns_amplification_mitigation/DNSAmplificationMitigation.cpp +++ b/examples/NFD/dns_amplification_mitigation/DNSAmplificationMitigation.cpp @@ -253,7 +253,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/dns_amplification_mitigation/go.sh b/examples/NFD/dns_amplification_mitigation/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/dns_amplification_mitigation/go.sh +++ b/examples/NFD/dns_amplification_mitigation/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/go.sh b/examples/NFD/go.sh index 5bce8c94e..d2e286550 100755 --- a/examples/NFD/go.sh +++ b/examples/NFD/go.sh @@ -14,4 +14,4 @@ if [ ! -f ../../start_nf.sh ]; then exit 1 fi -../../start_nf.sh $NF_DIR "$@" +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/heavy_hitter_detection/HHD.cpp b/examples/NFD/heavy_hitter_detection/HHD.cpp index c97a5567a..4cc584702 100644 --- a/examples/NFD/heavy_hitter_detection/HHD.cpp +++ b/examples/NFD/heavy_hitter_detection/HHD.cpp @@ -260,7 +260,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/heavy_hitter_detection/go.sh b/examples/NFD/heavy_hitter_detection/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/heavy_hitter_detection/go.sh +++ b/examples/NFD/heavy_hitter_detection/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/napt/NAPT.cpp b/examples/NFD/napt/NAPT.cpp index 521a111db..bb2f4b44f 100644 --- a/examples/NFD/napt/NAPT.cpp +++ b/examples/NFD/napt/NAPT.cpp @@ -267,7 +267,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/napt/go.sh b/examples/NFD/napt/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/napt/go.sh +++ b/examples/NFD/napt/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/stateful_firewall/go.sh b/examples/NFD/stateful_firewall/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/stateful_firewall/go.sh +++ b/examples/NFD/stateful_firewall/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/stateful_firewall/stateful_firewall.cpp b/examples/NFD/stateful_firewall/stateful_firewall.cpp index 64a665cd0..1dcba3ac4 100644 --- a/examples/NFD/stateful_firewall/stateful_firewall.cpp +++ b/examples/NFD/stateful_firewall/stateful_firewall.cpp @@ -234,7 +234,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/stateless_firewall/go.sh b/examples/NFD/stateless_firewall/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/stateless_firewall/go.sh +++ b/examples/NFD/stateless_firewall/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/stateless_firewall/stateless_firewall.cpp b/examples/NFD/stateless_firewall/stateless_firewall.cpp index d58188e64..29ddf587d 100644 --- a/examples/NFD/stateless_firewall/stateless_firewall.cpp +++ b/examples/NFD/stateless_firewall/stateless_firewall.cpp @@ -227,7 +227,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/super_spreader_detection/SSD.cpp b/examples/NFD/super_spreader_detection/SSD.cpp index 359c94a1e..8b564e41a 100644 --- a/examples/NFD/super_spreader_detection/SSD.cpp +++ b/examples/NFD/super_spreader_detection/SSD.cpp @@ -266,7 +266,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/super_spreader_detection/go.sh b/examples/NFD/super_spreader_detection/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/super_spreader_detection/go.sh +++ b/examples/NFD/super_spreader_detection/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/syn_flood_detection/SYNFloodDetection.cpp b/examples/NFD/syn_flood_detection/SYNFloodDetection.cpp index 15fc1873a..9112c7c85 100644 --- a/examples/NFD/syn_flood_detection/SYNFloodDetection.cpp +++ b/examples/NFD/syn_flood_detection/SYNFloodDetection.cpp @@ -264,7 +264,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/syn_flood_detection/go.sh b/examples/NFD/syn_flood_detection/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/syn_flood_detection/go.sh +++ b/examples/NFD/syn_flood_detection/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/NFD/udp_flood_mitigation/UDPFloodMitagation.cpp b/examples/NFD/udp_flood_mitigation/UDPFloodMitagation.cpp index a08188fc5..6c82710f0 100644 --- a/examples/NFD/udp_flood_mitigation/UDPFloodMitagation.cpp +++ b/examples/NFD/udp_flood_mitigation/UDPFloodMitagation.cpp @@ -259,7 +259,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/NFD/udp_flood_mitigation/go.sh b/examples/NFD/udp_flood_mitigation/go.sh index 40babd806..d2e286550 120000 --- a/examples/NFD/udp_flood_mitigation/go.sh +++ b/examples/NFD/udp_flood_mitigation/go.sh @@ -1 +1,17 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +NF_DIR=NFD/${NF_DIR} + +# echo ${NF_DIR} + +if [ ! -f ../../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +../../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/aes_decrypt/aesdecrypt.c b/examples/aes_decrypt/aesdecrypt.c index 3ada41e5f..1810f9375 100644 --- a/examples/aes_decrypt/aesdecrypt.c +++ b/examples/aes_decrypt/aesdecrypt.c @@ -138,7 +138,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; @@ -154,14 +154,14 @@ do_stats_display(struct rte_mbuf *pkt) { ip = onvm_pkt_ipv4_hdr(pkt); if (ip != NULL) { - struct udp_hdr *udp; + struct rte_udp_hdr *udp; onvm_pkt_print(pkt); /* Check if we have a valid UDP packet */ udp = onvm_pkt_udp_hdr(pkt); if (udp != NULL) { uint8_t *pkt_data; - pkt_data = ((uint8_t *)udp) + sizeof(struct udp_hdr); + pkt_data = ((uint8_t *)udp) + sizeof(struct rte_udp_hdr); printf("Payload : %.32s\n", pkt_data); } } else { @@ -172,7 +172,7 @@ do_stats_display(struct rte_mbuf *pkt) { static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { - struct udp_hdr *udp; + struct rte_udp_hdr *udp; static uint32_t counter = 0; /* Check if we have a valid UDP packet */ @@ -184,7 +184,7 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, uint16_t hlen; /* Get at the payload */ - pkt_data = ((uint8_t *)udp) + sizeof(struct udp_hdr); + pkt_data = ((uint8_t *)udp) + sizeof(struct rte_udp_hdr); /* Calculate length */ eth = rte_pktmbuf_mtod(pkt, uint8_t *); hlen = pkt_data - eth; @@ -196,7 +196,7 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, aes_decrypt_ctr(pkt_data, plen, pkt_data, key_schedule, 256, iv[0]); if (counter == 0) { printf("Decrypted %d bytes at offset %d (%ld)\n", plen, hlen, - sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr) + sizeof(struct udp_hdr)); + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr)); } } diff --git a/examples/aes_decrypt/go.sh b/examples/aes_decrypt/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/aes_decrypt/go.sh +++ b/examples/aes_decrypt/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/aes_encrypt/aesencrypt.c b/examples/aes_encrypt/aesencrypt.c index 653977679..6b8241cfe 100644 --- a/examples/aes_encrypt/aesencrypt.c +++ b/examples/aes_encrypt/aesencrypt.c @@ -138,7 +138,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; @@ -154,14 +154,14 @@ do_stats_display(struct rte_mbuf *pkt) { ip = onvm_pkt_ipv4_hdr(pkt); if (ip != NULL) { - struct udp_hdr *udp; + struct rte_udp_hdr *udp; onvm_pkt_print(pkt); /* Check if we have a valid UDP packet */ udp = onvm_pkt_udp_hdr(pkt); if (udp != NULL) { uint8_t *pkt_data; - pkt_data = ((uint8_t *)udp) + sizeof(struct udp_hdr); + pkt_data = ((uint8_t *)udp) + sizeof(struct rte_udp_hdr); printf("Payload : %.32s\n", pkt_data); } } else { @@ -172,7 +172,7 @@ do_stats_display(struct rte_mbuf *pkt) { static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { - struct udp_hdr *udp; + struct rte_udp_hdr *udp; static uint32_t counter = 0; if (++counter == print_delay) { @@ -189,7 +189,7 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, uint16_t hlen; /* Get at the payload */ - pkt_data = ((uint8_t *)udp) + sizeof(struct udp_hdr); + pkt_data = ((uint8_t *)udp) + sizeof(struct rte_udp_hdr); /* Calculate length */ eth = rte_pktmbuf_mtod(pkt, uint8_t *); hlen = pkt_data - eth; @@ -201,7 +201,7 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, aes_encrypt_ctr(pkt_data, plen, pkt_data, key_schedule, 256, iv[0]); if (counter == 0) { printf("Encrypted %d bytes at offset %d (%ld)\n", plen, hlen, - sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr) + sizeof(struct udp_hdr)); + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr)); } } diff --git a/examples/aes_encrypt/go.sh b/examples/aes_encrypt/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/aes_encrypt/go.sh +++ b/examples/aes_encrypt/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/arp_response/arp_response.c b/examples/arp_response/arp_response.c index de74c8661..f7f4b7b71 100644 --- a/examples/arp_response/arp_response.c +++ b/examples/arp_response/arp_response.c @@ -211,11 +211,11 @@ parse_app_args(int argc, char *argv[], const char *progname) { * For RFC about ARP, see https://tools.ietf.org/html/rfc826 * RETURNS 0 if success, -1 otherwise */ static int -send_arp_reply(int port, struct ether_addr *tha, uint32_t tip, struct onvm_nf *nf) { +send_arp_reply(int port, struct rte_ether_addr *tha, uint32_t tip, struct onvm_nf *nf) { struct rte_mbuf *out_pkt = NULL; struct onvm_pkt_meta *pmeta = NULL; - struct ether_hdr *eth_hdr = NULL; - struct arp_hdr *out_arp_hdr = NULL; + struct rte_ether_hdr *eth_hdr = NULL; + struct rte_arp_hdr *out_arp_hdr = NULL; size_t pkt_size = 0; @@ -229,30 +229,30 @@ send_arp_reply(int port, struct ether_addr *tha, uint32_t tip, struct onvm_nf *n return -1; } - pkt_size = sizeof(struct ether_hdr) + sizeof(struct arp_hdr); + pkt_size = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr); out_pkt->data_len = pkt_size; out_pkt->pkt_len = pkt_size; // SET ETHER HEADER INFO eth_hdr = onvm_pkt_ether_hdr(out_pkt); - ether_addr_copy(&ports->mac[port], ð_hdr->s_addr); - eth_hdr->ether_type = rte_cpu_to_be_16(ETHER_TYPE_ARP); - ether_addr_copy(tha, ð_hdr->d_addr); + rte_ether_addr_copy(&ports->mac[port], ð_hdr->s_addr); + eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP); + rte_ether_addr_copy(tha, ð_hdr->d_addr); // SET ARP HDR INFO - out_arp_hdr = rte_pktmbuf_mtod_offset(out_pkt, struct arp_hdr *, sizeof(struct ether_hdr)); + out_arp_hdr = rte_pktmbuf_mtod_offset(out_pkt, struct rte_arp_hdr *, sizeof(struct rte_ether_hdr)); - out_arp_hdr->arp_hrd = rte_cpu_to_be_16(ARP_HRD_ETHER); - out_arp_hdr->arp_pro = rte_cpu_to_be_16(ETHER_TYPE_IPv4); - out_arp_hdr->arp_hln = 6; - out_arp_hdr->arp_pln = sizeof(uint32_t); - out_arp_hdr->arp_op = rte_cpu_to_be_16(ARP_OP_REPLY); + out_arp_hdr->arp_hardware = rte_cpu_to_be_16(RTE_ARP_HRD_ETHER); + out_arp_hdr->arp_protocol = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); + out_arp_hdr->arp_hlen = 6; + out_arp_hdr->arp_plen = sizeof(uint32_t); + out_arp_hdr->arp_opcode = rte_cpu_to_be_16(RTE_ARP_OP_REPLY); - ether_addr_copy(&ports->mac[port], &out_arp_hdr->arp_data.arp_sha); + rte_ether_addr_copy(&ports->mac[port], &out_arp_hdr->arp_data.arp_sha); out_arp_hdr->arp_data.arp_sip = state_info->source_ips[ports->id[port]]; out_arp_hdr->arp_data.arp_tip = tip; - ether_addr_copy(tha, &out_arp_hdr->arp_data.arp_tha); + rte_ether_addr_copy(tha, &out_arp_hdr->arp_data.arp_tha); // SEND PACKET OUT/SET METAINFO pmeta = onvm_get_pkt_meta(out_pkt); @@ -265,8 +265,8 @@ send_arp_reply(int port, struct ether_addr *tha, uint32_t tip, struct onvm_nf *n static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { - struct ether_hdr *eth_hdr = onvm_pkt_ether_hdr(pkt); - struct arp_hdr *in_arp_hdr = NULL; + struct rte_ether_hdr *eth_hdr = onvm_pkt_ether_hdr(pkt); + struct rte_arp_hdr *in_arp_hdr = NULL; int result = -1; /* @@ -276,10 +276,10 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, * If its an ARP REPLY send to dest * Ignore (fwd to dest) other opcodes */ - if (rte_cpu_to_be_16(eth_hdr->ether_type) == ETHER_TYPE_ARP) { - in_arp_hdr = rte_pktmbuf_mtod_offset(pkt, struct arp_hdr *, sizeof(struct ether_hdr)); - switch (rte_cpu_to_be_16(in_arp_hdr->arp_op)) { - case ARP_OP_REQUEST: + if (rte_cpu_to_be_16(eth_hdr->ether_type) == RTE_ETHER_TYPE_ARP) { + in_arp_hdr = rte_pktmbuf_mtod_offset(pkt, struct rte_arp_hdr *, sizeof(struct rte_ether_hdr)); + switch (rte_cpu_to_be_16(in_arp_hdr->arp_opcode)) { + case RTE_ARP_OP_REQUEST: if (rte_be_to_cpu_32(in_arp_hdr->arp_data.arp_tip) == state_info->source_ips[ports->id[pkt->port]]) { result = send_arp_reply(pkt->port, ð_hdr->s_addr, @@ -292,13 +292,14 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, return 0; } break; - case ARP_OP_REPLY: + case RTE_ARP_OP_REPLY: /* Here we can potentially save the information */ break; default: if (state_info->print_flag) { printf("ARP with opcode %d, port %d (ID %d) DROPPED\n", - rte_cpu_to_be_16(in_arp_hdr->arp_op), pkt->port, ports->id[pkt->port]); + rte_cpu_to_be_16(in_arp_hdr->arp_opcode), + pkt->port, ports->id[pkt->port]); } } } diff --git a/examples/arp_response/go.sh b/examples/arp_response/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/arp_response/go.sh +++ b/examples/arp_response/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/basic_monitor/go.sh b/examples/basic_monitor/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/basic_monitor/go.sh +++ b/examples/basic_monitor/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/basic_monitor/monitor.c b/examples/basic_monitor/monitor.c index 1c2ec33f6..8b6a2bc7a 100644 --- a/examples/basic_monitor/monitor.c +++ b/examples/basic_monitor/monitor.c @@ -122,7 +122,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/bridge/bridge.c b/examples/bridge/bridge.c index cef48a7ca..dbd07c438 100644 --- a/examples/bridge/bridge.c +++ b/examples/bridge/bridge.c @@ -114,7 +114,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/bridge/go.sh b/examples/bridge/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/bridge/go.sh +++ b/examples/bridge/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/example_nf_deploy.json b/examples/example_nf_deploy.json new file mode 100644 index 000000000..1620f6799 --- /dev/null +++ b/examples/example_nf_deploy.json @@ -0,0 +1,19 @@ +{ + "globals": [ + { + "TTL": 1 + }, + { + "directory": "output_files" + } + ], + "simple_forward": [ + { + "parameters": "2 -d 1" + } + ], + "speed_tester": [ + { + "parameters": "1 -d 2 -l -c 16000" + } + ]} \ No newline at end of file diff --git a/examples/example_nf_deploy_test_p_template.json b/examples/example_nf_deploy_test_p_template.json new file mode 100644 index 000000000..f0872f7b7 --- /dev/null +++ b/examples/example_nf_deploy_test_p_template.json @@ -0,0 +1,20 @@ +{ + "globals": [ + { + "TTL": 1 + }, + { + "directory": "output_files" + } + ], + "simple_forward": [ + { + "parameters": "2 -d 1" + } + ], + "speed_tester": [ + { + "parameters": "1 -d 2 -l -c 16000" + } + ] +} \ No newline at end of file diff --git a/examples/firewall/firewall.c b/examples/firewall/firewall.c index 75cb4d48e..599d62993 100755 --- a/examples/firewall/firewall.c +++ b/examples/firewall/firewall.c @@ -197,7 +197,7 @@ do_stats_display(void) { static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { - struct ipv4_hdr *ipv4_hdr; + struct rte_ipv4_hdr *ipv4_hdr; static uint32_t counter = 0; int ret; uint32_t rule = 0; diff --git a/examples/firewall/go.sh b/examples/firewall/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/firewall/go.sh +++ b/examples/firewall/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/flow_table/flow_table.c b/examples/flow_table/flow_table.c index 43fc26cf2..21a7cb448 100644 --- a/examples/flow_table/flow_table.c +++ b/examples/flow_table/flow_table.c @@ -168,7 +168,7 @@ do_stats_display(struct rte_mbuf *pkt, int32_t tbl_index) { printf("\n\n"); #ifdef DEBUG_PRINT - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; ip = onvm_pkt_ipv4_hdr(pkt); if (ip != NULL) { onvm_pkt_print(pkt); diff --git a/examples/flow_table/go.sh b/examples/flow_table/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/flow_table/go.sh +++ b/examples/flow_table/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/flow_tracker/flow_tracker.c b/examples/flow_tracker/flow_tracker.c index 9437bc990..e9f15c6c6 100644 --- a/examples/flow_tracker/flow_tracker.c +++ b/examples/flow_tracker/flow_tracker.c @@ -360,6 +360,8 @@ main(int argc, char *argv[]) { onvm_nflib_run(nf_local_ctx); onvm_nflib_stop(nf_local_ctx); + onvm_ft_free(state_info->ft); + rte_free(state_info); printf("If we reach here, program is ending!\n"); return 0; } diff --git a/examples/flow_tracker/go.sh b/examples/flow_tracker/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/flow_tracker/go.sh +++ b/examples/flow_tracker/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/go.sh b/examples/go.sh index 8bcb99221..03fc3bb36 100755 --- a/examples/go.sh +++ b/examples/go.sh @@ -10,13 +10,11 @@ if [ ! -f ../start_nf.sh ]; then exit 1 fi -SCRIPT=$(readlink -f "$0") -MANAGER_PATH=$(dirname $(dirname "$SCRIPT")) - -if [[ -z $(ps ww -u root | grep "$MANAGER_PATH/onvm/onvm_mgr/$RTE_TARGET/onvm_mgr") ]] +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup then echo "NF cannot start without a running manager" exit 1 fi -../start_nf.sh $NF_DIR "$@" +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/l2fwd/Makefile b/examples/l2fwd/Makefile new file mode 100644 index 000000000..d79041bd3 --- /dev/null +++ b/examples/l2fwd/Makefile @@ -0,0 +1,62 @@ +# openNetVM +# https://github.com/sdnfv/openNetVM +# +# BSD LICENSE +# +# Copyright(c) +# 2015-2020 George Washington University +# 2015-2020 University of California Riverside +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# The name of the author may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +ifeq ($(RTE_SDK),) +$(error "Please define RTE_SDK environment variable") +endif + +RTE_TARGET ?= $(RTE_TARGET) + +# Default target, can be overriden by command line or environment +include $(RTE_SDK)/mk/rte.vars.mk + +# binary name +APP = l2fwd + +# all source are stored in SRCS-y +SRCS-y := l2fwd.c + +# OpenNetVM path +ONVM= $(SRCDIR)/../../onvm + +CFLAGS += -O3 $(USER_FLAGS) + +CFLAGS += -I$(ONVM)/onvm_nflib +CFLAGS += -I$(ONVM)/lib +LDFLAGS += $(ONVM)/onvm_nflib/$(RTE_TARGET)/libonvm.a +LDFLAGS += $(ONVM)/lib/$(RTE_TARGET)/lib/libonvmhelper.a -lm + +include $(RTE_SDK)/mk/rte.extapp.mk diff --git a/examples/l2fwd/README.md b/examples/l2fwd/README.md new file mode 100644 index 000000000..a32202e5e --- /dev/null +++ b/examples/l2fwd/README.md @@ -0,0 +1,38 @@ +Layer 2 Switch +== +l2switch is an NF based on the dpdk [l2fwd example](https://doc.dpdk.org/guides/sample_app_ug/l2_forward_real_virtual.html) that sends packets out the adjacent port. The destination port is the adjacent port from the enabled portmask, that is, if the first four ports are enabled (portmask 0xf), +ports 1 and 2 forward into each other, and ports 3 and 4 forward into each other. Individual packets destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID. + +Compilation and Execution +-- +``` +cd examples +make +cd l2switch +./go.sh SERVICE_ID [PRINT_DELAY] + +OR + +./go.sh -F CONFIG_FILE -- -- [-p PRINT_DELAY] + +OR + +sudo ./build/bridge -l CORELIST -n 3 --proc-type=secondary -- -r SERVICE_ID -- [-p PRINT_DELAY] +``` + +App Specific Arguments +-- + - `-p `: number of packets between each print, e.g. `-p 1` prints every packets. Default is every 1000000 packets. + - `-n` : Disables mac updating. + - `-m` : Enables printing updated mac address. Prints the updated mac address of every packet. + +For example: ./go.sh 1 -p 1 -m + +Will print every packet with mac address information. + +Config File Support +-- +This NF supports the NF generating arguments from a config file. For +additional reading, see [Examples.md](../../docs/Examples.md) + +See `../example_config.json` for all possible options that can be set. diff --git a/examples/l2fwd/go.sh b/examples/l2fwd/go.sh new file mode 100644 index 000000000..03fc3bb36 --- /dev/null +++ b/examples/l2fwd/go.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/l2fwd/l2fwd.c b/examples/l2fwd/l2fwd.c new file mode 100644 index 000000000..4cf3c8280 --- /dev/null +++ b/examples/l2fwd/l2fwd.c @@ -0,0 +1,353 @@ +/********************************************************************* + * openNetVM + * https://sdnfv.github.io + * + * BSD LICENSE + * + * Copyright(c) + * 2015-2020 George Washington University + * 2015-2020 University of California Riverside + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * l2switch.c - send all packets from one port out the adjacent port. + ********************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "onvm_nflib.h" +#include "onvm_pkt_helper.h" + +#define NF_TAG "l2switch" + +/* Shared data structure containing host port info. */ +extern struct port_info *ports; + +/* Per-port statistics struct. */ +struct l2fwd_port_statistics { + uint64_t tx; + uint64_t rx; + uint64_t dropped; +} __rte_cache_aligned; + +/*Struct that holds all NF state information */ +struct state_info { + /* Number of package between each print. */ + uint32_t print_delay; + /* List of enabled ports. */ + uint32_t l2fwd_dst_ports[RTE_MAX_ETHPORTS]; + /* Ethernet addresses of ports. */ + struct rte_ether_addr l2fwd_ports_eth_addr[RTE_MAX_ETHPORTS]; + struct l2fwd_port_statistics port_statistics[RTE_MAX_ETHPORTS]; + /* MAC updating enabled by default */ + int mac_updating; + /* Print mac address disabled by default */ + int print_mac; +}; + +/* + * Print a usage message + */ +static void +usage(const char *progname) { + printf("Usage:\n"); + printf("%s [EAL args] -- [NF_LIB args] -- -p \n", progname); + printf("%s -F [EAL args] -- [NF_LIB args] -- [NF args]\n\n", progname); + printf("Flags:\n"); + printf(" - `-p `: number of packets between each print, e.g. `-p 1` prints every packets.\n"); + printf(" - `-n : Disables mac updating. \n"); + printf(" - `-m : Enables printing updated mac address. \n"); +} + +/* + * Parse the application arguments. + */ +static int +parse_app_args(int argc, char *argv[], const char *progname, struct state_info *stats) { + int c; + + while ((c = getopt(argc, argv, "p:nm")) != -1) { + switch (c) { + case 'p': + stats->print_delay = strtoul(optarg, NULL, 10); + break; + case 'n': + /* Disable MAC updating. */ + stats->mac_updating = 0; + break; + case 'm': + /* Enable printing of MAC address.*/ + stats->print_mac = 1; + break; + case '?': + usage(progname); + if (optopt == 'p') + RTE_LOG(INFO, APP, "Option -%c requires an argument.\n", optopt); + else if (isprint(optopt)) + RTE_LOG(INFO, APP, "Unknown option `-%c'.\n", optopt); + else + RTE_LOG(INFO, APP, "Unknown option character `\\x%x'.\n", optopt); + return -1; + default: + usage(progname); + return -1; + } + } + return optind; +} + +/* + * This function displays stats. It uses ANSI terminal codes to clear + * screen when called. It is called from a single non-master + * thread in the server process, when the process is run with more + * than one lcore enabled. + */ + +static void +print_stats(struct state_info *stats) { + uint64_t total_packets_dropped, total_packets_tx, total_packets_rx; + unsigned i; + + total_packets_dropped = 0; + total_packets_tx = 0; + total_packets_rx = 0; + + const char clr[] = { 27, '[', '2', 'J', '\0' }; + const char topLeft[] = { 27, '[', '1', ';', '1', 'H','\0' }; + + /* Clear screen and move to top left */ + printf("%s%s", clr, topLeft); + + printf("\nPort statistics ===================================="); + + for (i = 0; i < ports->num_ports; i++) { + printf("\nStatistics for port %u ------------------------------" + "\nPackets sent: %24"PRIu64 + "\nPackets received: %20"PRIu64 + "\nForwarding to port: %u", + ports->id[i], + stats->port_statistics[ports->id[i]].tx, + stats->port_statistics[ports->id[i]].rx, + stats->l2fwd_dst_ports[ports->id[i]]); + + total_packets_tx += stats->port_statistics[ports->id[i]].tx; + total_packets_rx += stats->port_statistics[ports->id[i]].rx; + } + printf("\nAggregate statistics ===============================" + "\nTotal packets sent: %18"PRIu64 + "\nTotal packets received: %14"PRIu64, + total_packets_tx, + total_packets_rx); + printf("\n====================================================\n"); +} +/* + * This function displays the ethernet addressof each initialized port. + * It saves the ethernet addresses in the struct ether_addr array. + */ +static void +l2fwd_initialize_ports(struct state_info *stats) { + uint16_t i; + for (i = 0; i < ports->num_ports; i++) { + rte_eth_macaddr_get(ports->id[i], &stats->l2fwd_ports_eth_addr[ports->id[i]]); + printf("Port %u, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n\n", + ports->id[i], + stats->l2fwd_ports_eth_addr[ports->id[i]].addr_bytes[0], + stats->l2fwd_ports_eth_addr[ports->id[i]].addr_bytes[1], + stats->l2fwd_ports_eth_addr[ports->id[i]].addr_bytes[2], + stats->l2fwd_ports_eth_addr[ports->id[i]].addr_bytes[3], + stats->l2fwd_ports_eth_addr[ports->id[i]].addr_bytes[4], + stats->l2fwd_ports_eth_addr[ports->id[i]].addr_bytes[5]); + } +} + +/* The source MAC address is replaced by the TX_PORT MAC address */ +/* The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID */ +static void +l2fwd_mac_updating(struct rte_mbuf *pkt, unsigned dest_portid, struct state_info *stats) { + struct rte_ether_hdr *eth; + void *tmp; + eth = rte_pktmbuf_mtod(pkt, struct rte_ether_hdr *); + + /* 02:00:00:00:00:xx */ + tmp = ð->d_addr.addr_bytes[0]; + *((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dest_portid << 40); + rte_ether_addr_copy(tmp, ð->s_addr); + + if (stats->print_mac) { + printf("Packet updated MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n\n", + eth->s_addr.addr_bytes[0], + eth->s_addr.addr_bytes[1], + eth->s_addr.addr_bytes[2], + eth->s_addr.addr_bytes[3], + eth->s_addr.addr_bytes[4], + eth->s_addr.addr_bytes[5]); + } +} + +/* The destination port is the adjacent port from the enabled portmask, that is, + * if the first four ports are enabled (portmask 0xf), + * ports 1 and 2 forward into each other, and ports 3 and 4 forward into each other. +*/ +static void +l2fwd_set_dest_ports(struct state_info *stats) { + int i; + unsigned nb_ports_in_mask = 0; + int last_port = 0; + for (i = 0; i < ports->num_ports; i++) { + if (nb_ports_in_mask % 2) { + stats->l2fwd_dst_ports[ports->id[i]] = last_port; + stats->l2fwd_dst_ports[last_port] = ports->id[i]; + } else { + last_port = ports->id[i]; + } + nb_ports_in_mask++; + } + if (nb_ports_in_mask % 2) { + printf("Notice: odd number of ports in portmask.\n"); + stats->l2fwd_dst_ports[last_port] = last_port; + } + +} + +static int +packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, + __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { + static uint32_t counter = 0; + struct onvm_nf *nf = nf_local_ctx->nf; + struct state_info *stats = (struct state_info *)nf->data; + if (++counter == stats->print_delay) { + print_stats(stats); + counter = 0; + } + if (pkt->port > RTE_MAX_ETHPORTS) { + RTE_LOG(INFO, APP, "Packet source port greater than MAX ethernet ports allowed. \n"); + meta->action = ONVM_NF_ACTION_DROP; + return 0; + } + /* Update stats packet received on port. */ + stats->port_statistics[pkt->port].rx += 1; + unsigned dst_port = stats->l2fwd_dst_ports[pkt->port]; + + /* If mac_updating enabled update source and destination mac address of packet. */ + if (stats->mac_updating) + l2fwd_mac_updating(pkt, dst_port, stats); + + /* Set destination port of packet. */ + meta->destination = dst_port; + /* Update stats packet sent from source port. */ + stats->port_statistics[dst_port].tx += 1; + meta->action = ONVM_NF_ACTION_OUT; + return 0; +} + +void +nf_setup(struct onvm_nf_local_ctx *nf_local_ctx) { + struct onvm_nf *nf = nf_local_ctx->nf; + struct state_info *stats = (struct state_info *)nf->data; + + /* Initialize port stats. */ + memset(&stats->port_statistics, 0, sizeof(stats->port_statistics)); + + /* Set destination port for each port. */ + l2fwd_set_dest_ports(stats); + + /* Get mac address for each port. */ + l2fwd_initialize_ports(stats); + +} + +int +main(int argc, char *argv[]) { + int arg_offset; + struct onvm_nf_local_ctx *nf_local_ctx; + struct onvm_nf_function_table *nf_function_table; + const char *progname = argv[0]; + + nf_local_ctx = onvm_nflib_init_nf_local_ctx(); + onvm_nflib_start_signal_handler(nf_local_ctx, NULL); + + nf_function_table = onvm_nflib_init_nf_function_table(); + nf_function_table->pkt_handler = &packet_handler; + nf_function_table->setup = &nf_setup; + + if ((arg_offset = onvm_nflib_init(argc, argv, NF_TAG, nf_local_ctx, nf_function_table)) < 0) { + onvm_nflib_stop(nf_local_ctx); + if (arg_offset == ONVM_SIGNAL_TERMINATION) { + printf("Exiting due to user termination\n"); + return 0; + } else { + rte_exit(EXIT_FAILURE, "Failed ONVM init\n"); + } + } + if (ports->num_ports == 0) { + onvm_nflib_stop(nf_local_ctx); + rte_exit(EXIT_FAILURE, "No Ethernet ports. Ensure ports binded to dpdk. - bye\n"); + } + argc -= arg_offset; + argv += arg_offset; + + struct onvm_nf *nf = nf_local_ctx->nf; + struct state_info *stats = rte_calloc("state", 1, sizeof(struct state_info), 0); + if (stats == NULL) { + onvm_nflib_stop(nf_local_ctx); + rte_exit(EXIT_FAILURE, "Unable to initialize NF stats."); + } + /* MAC updating enabled by default */ + stats->mac_updating = 1; + /* Print mac address disabled by default */ + stats->print_mac = 0; + stats->print_delay = 1000000; + nf->data = (void *)stats; + + if (parse_app_args(argc, argv, progname, stats) < 0) { + onvm_nflib_stop(nf_local_ctx); + rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n"); + } + RTE_LOG(INFO, APP, "MAC updating %s\n", stats->mac_updating ? "enabled" : "disabled"); + + onvm_nflib_run(nf_local_ctx); + + onvm_nflib_stop(nf_local_ctx); + printf("If we reach here, program is ending\n"); + return 0; +} diff --git a/examples/load_balancer/go.sh b/examples/load_balancer/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/load_balancer/go.sh +++ b/examples/load_balancer/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/load_balancer/load_balancer.c b/examples/load_balancer/load_balancer.c index 09d0e0d65..651891541 100644 --- a/examples/load_balancer/load_balancer.c +++ b/examples/load_balancer/load_balancer.c @@ -101,14 +101,14 @@ struct loadbalance { /* Struct for backend servers */ struct backend_server { - uint8_t d_addr_bytes[ETHER_ADDR_LEN]; + uint8_t d_addr_bytes[RTE_ETHER_ADDR_LEN]; uint32_t d_ip; }; /* Struct for flow info */ struct flow_info { uint8_t dest; - uint8_t s_addr_bytes[ETHER_ADDR_LEN]; + uint8_t s_addr_bytes[RTE_ETHER_ADDR_LEN]; uint64_t last_pkt_cycles; int is_active; }; @@ -268,7 +268,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; @@ -308,8 +308,8 @@ static void get_iface_inf(void) { int fd, i; struct ifreq ifr; - uint8_t client_addr_bytes[ETHER_ADDR_LEN]; - uint8_t server_addr_bytes[ETHER_ADDR_LEN]; + uint8_t client_addr_bytes[RTE_ETHER_ADDR_LEN]; + uint8_t server_addr_bytes[RTE_ETHER_ADDR_LEN]; fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); ifr.ifr_addr.sa_family = AF_INET; @@ -321,7 +321,7 @@ get_iface_inf(void) { lb->ip_lb_server = *(uint32_t *)(&((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); ioctl(fd, SIOCGIFHWADDR, &ifr); - for (i = 0; i < ETHER_ADDR_LEN; i++) + for (i = 0; i < RTE_ETHER_ADDR_LEN; i++) server_addr_bytes[i] = ifr.ifr_hwaddr.sa_data[i]; /* Parse client interface */ @@ -331,11 +331,11 @@ get_iface_inf(void) { lb->ip_lb_client = *(uint32_t *)(&((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr); ioctl(fd, SIOCGIFHWADDR, &ifr); - for (i = 0; i < ETHER_ADDR_LEN; i++) + for (i = 0; i < RTE_ETHER_ADDR_LEN; i++) client_addr_bytes[i] = ifr.ifr_hwaddr.sa_data[i]; /* Compare the interfaces to onvm_mgr ports by hwaddr and assign port id accordingly */ - if (memcmp(&client_addr_bytes, &ports->mac[0], ETHER_ADDR_LEN) == 0) { + if (memcmp(&client_addr_bytes, &ports->mac[0], RTE_ETHER_ADDR_LEN) == 0) { lb->client_port = ports->id[0]; lb->server_port = ports->id[1]; } else { @@ -488,8 +488,8 @@ static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { static uint32_t counter = 0; - struct ipv4_hdr *ip; - struct ether_hdr *ehdr; + struct rte_ipv4_hdr *ip; + struct rte_ether_hdr *ehdr; struct flow_info *flow_info; int i, ret; @@ -525,22 +525,26 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, /* If the flow entry is new, save the client information */ if (flow_info->is_active == 0) { flow_info->is_active = 1; - for (i = 0; i < ETHER_ADDR_LEN; i++) { + for (i = 0; i < RTE_ETHER_ADDR_LEN; i++) { flow_info->s_addr_bytes[i] = ehdr->s_addr.addr_bytes[i]; } } if (pkt->port == lb->server_port) { - rte_eth_macaddr_get(lb->client_port, &ehdr->s_addr); - for (i = 0; i < ETHER_ADDR_LEN; i++) { + if (onvm_get_macaddr(lb->client_port, &ehdr->s_addr) == -1) { + rte_exit(EXIT_FAILURE, "Failed to obtain MAC address\n"); + } + for (i = 0; i < RTE_ETHER_ADDR_LEN; i++) { ehdr->d_addr.addr_bytes[i] = flow_info->s_addr_bytes[i]; } ip->src_addr = lb->ip_lb_client; meta->destination = lb->client_port; } else { - rte_eth_macaddr_get(lb->server_port, &ehdr->s_addr); - for (i = 0; i < ETHER_ADDR_LEN; i++) { + if (onvm_get_macaddr(lb->server_port, &ehdr->s_addr) == -1) { + rte_exit(EXIT_FAILURE, "Failed to obtain MAC address\n"); + } + for (i = 0; i < RTE_ETHER_ADDR_LEN; i++) { ehdr->d_addr.addr_bytes[i] = lb->server[flow_info->dest].d_addr_bytes[i]; } @@ -613,6 +617,8 @@ main(int argc, char *argv[]) { onvm_nflib_run(nf_local_ctx); onvm_nflib_stop(nf_local_ctx); + onvm_ft_free(lb->ft); + rte_free(lb); printf("If we reach here, program is ending\n"); return 0; } diff --git a/examples/load_generator/go.sh b/examples/load_generator/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/load_generator/go.sh +++ b/examples/load_generator/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/load_generator/load_generator.c b/examples/load_generator/load_generator.c index c59d6f507..a662a0463 100644 --- a/examples/load_generator/load_generator.c +++ b/examples/load_generator/load_generator.c @@ -83,8 +83,8 @@ static double total_latency_since_update = 0; struct rte_mempool *pktmbuf_pool; -static uint16_t packet_size = ETHER_HDR_LEN; -static uint8_t d_addr_bytes[ETHER_ADDR_LEN]; +static uint16_t packet_size = RTE_ETHER_HDR_LEN; +static uint8_t d_addr_bytes[RTE_ETHER_ADDR_LEN]; /* number of seconds between each print */ static double print_delay = 0.1; @@ -94,7 +94,7 @@ static uint16_t destination; static uint8_t action_out = 0; -static struct ether_hdr *ehdr; +static struct rte_ether_hdr *ehdr; /* Sets up variables for the load generator */ void @@ -133,7 +133,7 @@ usage(const char *progname) { static int parse_app_args(int argc, char *argv[], const char *progname) { int c, i, count, dst_flag = 0; - int values[ETHER_ADDR_LEN]; + int values[RTE_ETHER_ADDR_LEN]; while ((c = getopt(argc, argv, "d:p:t:m:s:o")) != -1) { switch (c) { case 'd': @@ -150,8 +150,8 @@ parse_app_args(int argc, char *argv[], const char *progname) { case 'm': count = sscanf(optarg, "%x:%x:%x:%x:%x:%x", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]); - if (count == ETHER_ADDR_LEN) { - for (i = 0; i < ETHER_ADDR_LEN; ++i) { + if (count == RTE_ETHER_ADDR_LEN) { + for (i = 0; i < RTE_ETHER_ADDR_LEN; ++i) { d_addr_bytes[i] = (uint8_t)values[i]; } } else { @@ -161,7 +161,7 @@ parse_app_args(int argc, char *argv[], const char *progname) { break; case 's': packet_size = strtoul(optarg, NULL, 10); - if (packet_size >= ETHER_HDR_LEN) { + if (packet_size >= RTE_ETHER_HDR_LEN) { break; } else { RTE_LOG(INFO, APP, @@ -261,7 +261,7 @@ callback_handler(__attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) batch_size = (packets_to_send <= BATCH_LIMIT) ? (int)packets_to_send : BATCH_LIMIT; struct rte_mbuf *pkts[batch_size]; for (i = 0; i < batch_size; i++) { - struct ether_hdr *pkt_ehdr; + struct rte_ether_hdr *pkt_ehdr; struct rte_mbuf *pkt = rte_pktmbuf_alloc(pktmbuf_pool); uint64_t *timestamp; @@ -271,8 +271,8 @@ callback_handler(__attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) } /* Append and copy ether header */ - pkt_ehdr = (struct ether_hdr *)rte_pktmbuf_append(pkt, packet_size); - rte_memcpy(pkt_ehdr, ehdr, sizeof(struct ether_hdr)); + pkt_ehdr = (struct rte_ether_hdr *)rte_pktmbuf_append(pkt, packet_size); + rte_memcpy(pkt_ehdr, ehdr, sizeof(struct rte_ether_hdr)); struct onvm_pkt_meta *pmeta = onvm_get_pkt_meta(pkt); pmeta->destination = destination; @@ -322,13 +322,16 @@ nf_setup(struct onvm_nf_local_ctx *nf_local_ctx) { rte_exit(EXIT_FAILURE, "Cannot find mbuf pool!\n"); } - ehdr = (struct ether_hdr *)malloc(sizeof(struct ether_hdr)); + ehdr = (struct rte_ether_hdr *)malloc(sizeof(struct rte_ether_hdr)); if (ehdr == NULL) { rte_exit(EXIT_FAILURE, "Failed to allocate common ehdr\n"); } - rte_eth_macaddr_get(0, &ehdr->s_addr); - for (j = 0; j < ETHER_ADDR_LEN; ++j) { + if (onvm_get_macaddr(0, &ehdr->s_addr) == -1) { + RTE_LOG(INFO, APP, "Using fake MAC address\n"); + onvm_get_fake_macaddr(&ehdr->s_addr); + } + for (j = 0; j < RTE_ETHER_ADDR_LEN; ++j) { ehdr->d_addr.addr_bytes[j] = d_addr_bytes[j]; } ehdr->ether_type = rte_cpu_to_be_16(LOCAL_EXPERIMENTAL_ETHER); diff --git a/examples/ndpi_stats/go.sh b/examples/ndpi_stats/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/ndpi_stats/go.sh +++ b/examples/ndpi_stats/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/nf_router/README.md b/examples/nf_router/README.md index c82c6f051..e894f51f4 100644 --- a/examples/nf_router/README.md +++ b/examples/nf_router/README.md @@ -26,7 +26,7 @@ sudo ./build/nf_router -l CORELIST -n 3 --proc-type=secondary -- -r SERVICE_ID - App Specific Arguments -- - - `-f `: router configuration, has a list of (IPs, dest) tuples + - `-f `: router configuration, has a list of destination IPs and IDs of NF you want to forward the packet to in form of tuples - `-p `: number of packets between each print, e.g. `-p 1` prints every packets. Config File Support @@ -35,4 +35,3 @@ This NF supports the NF generating arguments from a config file. For additional reading, see [Examples.md](../../docs/Examples.md) See `../example_config.json` for all possible options that can be set. - diff --git a/examples/nf_router/go.sh b/examples/nf_router/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/nf_router/go.sh +++ b/examples/nf_router/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/nf_router/nf_router.c b/examples/nf_router/nf_router.c index d2fb1c5f9..d5ffde61a 100644 --- a/examples/nf_router/nf_router.c +++ b/examples/nf_router/nf_router.c @@ -186,7 +186,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; @@ -212,9 +212,9 @@ static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { static uint32_t counter = 0; - struct ether_hdr *eth_hdr; - struct arp_hdr *in_arp_hdr; - struct ipv4_hdr *ip; + struct rte_ether_hdr *eth_hdr; + struct rte_arp_hdr *in_arp_hdr; + struct rte_ipv4_hdr *ip; int i; ip = onvm_pkt_ipv4_hdr(pkt); @@ -222,8 +222,8 @@ packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, /* If the packet doesn't have an IP header check if its an ARP, if so fwd it to the matched NF */ if (ip == NULL) { eth_hdr = onvm_pkt_ether_hdr(pkt); - if (rte_cpu_to_be_16(eth_hdr->ether_type) == ETHER_TYPE_ARP) { - in_arp_hdr = rte_pktmbuf_mtod_offset(pkt, struct arp_hdr *, sizeof(struct ether_hdr)); + if (rte_cpu_to_be_16(eth_hdr->ether_type) == RTE_ETHER_TYPE_ARP) { + in_arp_hdr = rte_pktmbuf_mtod_offset(pkt, struct rte_arp_hdr *, sizeof(struct rte_ether_hdr)); for (i = 0; i < nf_count; i++) { if (rte_be_to_cpu_32(in_arp_hdr->arp_data.arp_tip) == fwd_nf[i].ip) { meta->destination = fwd_nf[i].dest; diff --git a/examples/payload_scan/go.sh b/examples/payload_scan/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/payload_scan/go.sh +++ b/examples/payload_scan/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/payload_scan/payload_scan.c b/examples/payload_scan/payload_scan.c index c2c4f6ae7..ff98c56dc 100644 --- a/examples/payload_scan/payload_scan.c +++ b/examples/payload_scan/payload_scan.c @@ -203,11 +203,11 @@ static int packet_handler(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, stru } if (udp_pkt) { - pkt_data = rte_pktmbuf_mtod_offset(pkt, uint8_t * , sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr) + - sizeof(struct udp_hdr)); + pkt_data = rte_pktmbuf_mtod_offset(pkt, uint8_t * , sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr)); } else { - pkt_data = rte_pktmbuf_mtod_offset(pkt, uint8_t * , sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr) + - sizeof(struct tcp_hdr)); + pkt_data = rte_pktmbuf_mtod_offset(pkt, uint8_t * , sizeof(struct rte_ether_hdr) + + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_tcp_hdr)); } search_match = strstr((const char *) pkt_data, search_term) != NULL; diff --git a/examples/run_group.py b/examples/run_group.py new file mode 100644 index 000000000..22f4e29fa --- /dev/null +++ b/examples/run_group.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 + +# openNetVM +# https://sdnfv.github.io +# +# OpenNetVM is distributed under the following BSD LICENSE: +# +# Copyright(c) +# 2015-2018 George Washington University +# 2015-2018 University of California Riverside +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * The name of the author may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""This script allows a user to launch a chain of NFs from a JSON +config file passed in as an argument when running the script""" + +import sys +import json +import os +import shlex +import time +from signal import signal, SIGINT +from subprocess import Popen +from datetime import datetime + +def handler(signal_received, frame): + """Handles cleanup on user shutdown""" + # pylint: disable=unused-argument + for lf in log_files: + lf.close() + print("\nExiting...") + sys.exit(0) + +def get_config(): + """Gets path to JSON config file""" + if len(sys.argv) < 2: + print("Error: Specify config file") + sys.exit(0) + + file_name = sys.argv[1] + if os.path.exists(file_name): + return file_name + print("Error: No config file found") + sys.exit(0) + +def on_failure(): + """Handles shutdown on error""" + for lf in log_files: + lf.close() + try: + script_pid = os.getpid() + pid_list = os.popen("ps -ef | awk '{if ($3 == " + str(script_pid) + ") print $2 " " $3}'") + pid_list = pid_list.read().split("\n")[:-2] + for pd in pid_list: + pd = pd.replace(str(script_pid), "") + temp = os.popen("ps -ef | awk '{if($3 == " + pd + ") print $2 " " $3}'") + temp = temp.read().replace(str(pd), "").replace("\n", "") + if temp != "": + _pid_for_nf = os.popen("ps -ef | awk '{if($3 == " + temp + ") print $2 " " $3}'") + _pid_for_nf = _pid_for_nf.read().replace(temp, "").replace("\n", "") + os.system("sudo kill " + _pid_for_nf) + except: + pass + print("Error occurred. Exiting...") + sys.exit(1) + +def on_timeout(): + """Handles shutdown on timeout""" + for lf in log_files: + lf.close() + try: + script_pid = os.getpid() + pid_list = os.popen("ps -ef | awk '{if ($3 == " + str(script_pid) + ") print $2 " " $3}'") + pid_list = pid_list.read().split("\n")[:-2] + for pd in pid_list: + pd = pd.replace(str(script_pid), "") + temp = os.popen("ps -ef | awk '{if($3 == " + pd + ") print $2 " " $3}'") + temp = temp.read().replace(str(pd), "").replace("\n", "") + if temp != "": + _pid_for_nf = os.popen("ps -ef | awk '{if($3 == " + temp + ") print $2 " " $3}'") + _pid_for_nf = _pid_for_nf.read().replace(temp, "").replace("\n", "") + os.system("sudo kill " + _pid_for_nf) + except: + pass + print("Exiting on timeout...") + sys.exit(0) + +def running_services(): + """Checks running NFs""" + if timeout != 0: + start = time.time() + time.clock() + elapsed = 0 + while elapsed < timeout: + elapsed = time.time() - start + for pl in procs_list: + ret_code = pl.poll() + if ret_code is not None: + on_failure() + break + time.sleep(.1) + on_timeout() + else: + while 1: + for pl in procs_list: + ret_code = pl.poll() + if ret_code is not None: + on_failure() + break + time.sleep(.1) + +procs_list = [] +nf_list = [] +cmds_list = [] +log_files = [] +timeout = 0 +if __name__ == '__main__': + signal(SIGINT, handler) + if os.path.basename(os.getcwd()) != "examples": + print("Error: Run script from within /examples folder") + sys.exit(1) + + config_file = get_config() + + with open(config_file) as f: + try: + data = json.load(f) + except: + print("Cannot load config file. Check JSON syntax") + sys.exit(1) + + is_dir = 0 + prefix = "" + for k, v in data.items(): + if k == "globals": + for item in v: + if "directory" in item: + if is_dir == 0: + log_dir = item["directory"] + if os.path.isdir(log_dir) is False: + try: + os.mkdir(log_dir) + print("Creating directory %s" % (log_dir)) + is_dir = 1 + except OSError: + print("Creation of directory %s failed" % (log_dir)) + on_failure() + else: + print("Outputting log files to %s" % (log_dir)) + is_dir = 1 + if "directory-prefix" in item: + prefix = item['directory-prefix'] + if "TTL" in item: + timeout = item["TTL"] + + else: + for item in v: + nf_list.append(k) + cmds_list.append("./go.sh " + item['parameters']) + + if is_dir == 0: + time_obj = datetime.now().time() + log_dir = prefix + time_obj.strftime("%H:%M:%S") + os.mkdir(log_dir) + print("Creating directory %s" % (log_dir)) + is_dir = 1 + + for cmd, nf in zip(cmds_list, nf_list): + service_name = nf + instance_id = cmd.split() + log_file = "log-" + nf + "-" + instance_id[1] + ".txt" + log_files.append(open(os.path.join(log_dir, log_file), 'w')) + + i = 0 + cwd = os.getcwd() + for cmd, nf in zip(cmds_list, nf_list): + try: + os.chdir(nf) + except OSError: + print("Error: Unable to access NF directory %s." \ + " Check syntax in your configuration file" % (nf)) + sys.exit(1) + try: + p = Popen(shlex.split(cmd), stdout=(log_files[i]), stderr=log_files[i], \ + universal_newlines=True) + procs_list.append(p) + print("Starting %s %s" % (nf, cmd), flush=True) + i += 1 + except OSError: + pass + os.chdir(cwd) + + running_services() diff --git a/examples/scaling_example/go.sh b/examples/scaling_example/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/scaling_example/go.sh +++ b/examples/scaling_example/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/scaling_example/scaling.c b/examples/scaling_example/scaling.c index 45a296a7a..31b889815 100644 --- a/examples/scaling_example/scaling.c +++ b/examples/scaling_example/scaling.c @@ -79,8 +79,8 @@ static uint16_t num_children = DEFAULT_NUM_CHILDREN; static uint8_t use_advanced_rings = 0; static uint8_t use_shared_core_allocation = 0; -static uint8_t d_addr_bytes[ETHER_ADDR_LEN]; -static uint16_t packet_size = ETHER_HDR_LEN; +static uint8_t d_addr_bytes[RTE_ETHER_ADDR_LEN]; +static uint16_t packet_size = RTE_ETHER_HDR_LEN; static uint32_t packet_number = DEFAULT_PKT_NUM; /* For advanced rings scaling */ @@ -173,7 +173,7 @@ nf_setup(__attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { for (i = 0; i < packet_number; ++i) { struct onvm_pkt_meta *pmeta; - struct ether_hdr *ehdr; + struct rte_ether_hdr *ehdr; int j; struct rte_mbuf *pkt = rte_pktmbuf_alloc(pktmbuf_pool); @@ -181,11 +181,13 @@ nf_setup(__attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { break; /* set up ether header and set new packet size */ - ehdr = (struct ether_hdr *)rte_pktmbuf_append(pkt, packet_size); + ehdr = (struct rte_ether_hdr *)rte_pktmbuf_append(pkt, packet_size); /* Using manager mac addr for source*/ - rte_eth_macaddr_get(0, &ehdr->s_addr); - for (j = 0; j < ETHER_ADDR_LEN; ++j) { + if (onvm_get_macaddr(0, &ehdr->s_addr) == -1) { + onvm_get_fake_macaddr(&ehdr->s_addr); + } + for (j = 0; j < RTE_ETHER_ADDR_LEN; ++j) { ehdr->d_addr.addr_bytes[j] = d_addr_bytes[j]; } ehdr->ether_type = LOCAL_EXPERIMENTAL_ETHER; @@ -316,11 +318,10 @@ int thread_main_loop(struct onvm_nf_local_ctx *nf_local_ctx) { void *pkts[PKT_READ_SIZE]; struct onvm_pkt_meta *meta; - uint16_t i, j, nb_pkts; - void *pktsTX[PKT_READ_SIZE]; + uint16_t i, nb_pkts; + struct rte_mbuf *pktsTX[PKT_READ_SIZE]; int tx_batch_size; struct rte_ring *rx_ring; - struct rte_ring *tx_ring; struct rte_ring *msg_q; struct onvm_nf *nf; struct onvm_nf_msg *msg; @@ -333,7 +334,6 @@ thread_main_loop(struct onvm_nf_local_ctx *nf_local_ctx) { /* Get rings from nflib */ rx_ring = nf->rx_q; - tx_ring = nf->tx_q; msg_q = nf->msg_q; nf_msg_pool = rte_mempool_lookup(_NF_MSG_POOL_NAME); @@ -371,14 +371,10 @@ thread_main_loop(struct onvm_nf_local_ctx *nf_local_ctx) { packet_handler_fwd((struct rte_mbuf *)pkts[i], meta, nf_local_ctx); pktsTX[tx_batch_size++] = pkts[i]; } - - if (unlikely(tx_batch_size > 0 && rte_ring_enqueue_bulk(tx_ring, pktsTX, tx_batch_size, NULL) == 0)) { - nf->stats.tx_drop += tx_batch_size; - for (j = 0; j < tx_batch_size; j++) { - rte_pktmbuf_free(pktsTX[j]); - } - } else { - nf->stats.tx += tx_batch_size; + /* Process all packet actions */ + onvm_pkt_process_tx_batch(nf->nf_tx_mgr, pktsTX, tx_batch_size, nf); + if (tx_batch_size < PACKET_READ_SIZE) { + onvm_pkt_flush_all_nfs(nf->nf_tx_mgr, nf); } } return 0; diff --git a/examples/simple_forward/forward.c b/examples/simple_forward/forward.c index 2fc321336..7b857e5ef 100644 --- a/examples/simple_forward/forward.c +++ b/examples/simple_forward/forward.c @@ -128,7 +128,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/simple_forward/go.sh b/examples/simple_forward/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/simple_forward/go.sh +++ b/examples/simple_forward/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/simple_fwd_tb/.gitignore b/examples/simple_fwd_tb/.gitignore new file mode 100644 index 000000000..97f95f1b9 --- /dev/null +++ b/examples/simple_fwd_tb/.gitignore @@ -0,0 +1,2 @@ +simple_fwd_tb/ +build/ \ No newline at end of file diff --git a/examples/simple_fwd_tb/Makefile b/examples/simple_fwd_tb/Makefile new file mode 100644 index 000000000..d89e1a6e9 --- /dev/null +++ b/examples/simple_fwd_tb/Makefile @@ -0,0 +1,68 @@ +# openNetVM +# https://github.com/sdnfv/openNetVM +# +# BSD LICENSE +# +# Copyright(c) +# 2015-2017 George Washington University +# 2015-2017 University of California Riverside +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# The name of the author may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +ifeq ($(RTE_SDK),) +$(error "Please define RTE_SDK environment variable") +endif + +RTE_TARGET ?= x86_64-native-linuxapp-gcc + +# Default target, can be overriden by command line or environment +include $(RTE_SDK)/mk/rte.vars.mk + +# binary name +APP = simple_fwd_tb + +# all source are stored in SRCS-y +SRCS-y := forward_tb.c + +# OpenNetVM path +ONVM= $(SRCDIR)/../../onvm + +CFLAGS += $(WERROR_FLAGS) -O3 $(USER_FLAGS) + +CFLAGS += -I$(ONVM)/onvm_nflib +CFLAGS += -I$(ONVM)/lib +LDFLAGS += $(ONVM)/onvm_nflib/$(RTE_TARGET)/libonvm.a +LDFLAGS += $(ONVM)/lib/$(RTE_TARGET)/lib/libonvmhelper.a -lm + +# workaround for a gcc bug with noreturn attribute +# http://gcc.gnu.org/bugzilla/show_bug.cgi?id=12603 +ifeq ($(CONFIG_RTE_TOOLCHAIN_GCC),y) +CFLAGS_main.o += -Wno-return-type +endif + +include $(RTE_SDK)/mk/rte.extapp.mk diff --git a/examples/simple_fwd_tb/README.md b/examples/simple_fwd_tb/README.md new file mode 100644 index 000000000..47f69ebe8 --- /dev/null +++ b/examples/simple_fwd_tb/README.md @@ -0,0 +1,36 @@ +Simple Forward with Token Bucket +== +Example NF that simulates a queue with a token bucket and forwards packets to a specific destination. + +Compilation and Execution +-- + +``` +cd examples +make +cd simple_fwd_tb +``` +``` +./go.sh SERVICE_ID -d DST [-p PRINT_DELAY] [-D TOKEN_BUCKET_DEPTH] [-R TOKEN_BUCKET_RATE] + +OR + +./go.sh -F CONFIG_FILE -- -- -d DST [-p PRINT_DELAY] [-D TOKEN_BUCKET_DEPTH] [-R TOKEN_BUCKET_RATE] + +OR + +sudo ./build/simple_fwd_tb -l CORELIST -n 3 --proc-type=secondary -- -r SERVICE_ID -- -d DST [-p PRINT_DELAY] [-D TOKEN_BUCKET_DEPTH] [-R TOKEN_BUCKET_RATE] +``` + +App Specific Arguments +-- + - `-d `: destination service ID to foward to + - `-p `: number of packets between each print, e.g. `-p 1` prints every packets. + - `-D `: depth of token bucket (in bytes) + - `-R `: rate of token regeneration (in MBps) + +Config File Support +-- +This NF supports the NF generating arguments from a config file. For additional reading, see [Examples.md](../../docs/Examples.md) + +See `../example_config.json` for all possible options that can be set. diff --git a/examples/simple_fwd_tb/forward_tb.c b/examples/simple_fwd_tb/forward_tb.c new file mode 100644 index 000000000..ed353ca20 --- /dev/null +++ b/examples/simple_fwd_tb/forward_tb.c @@ -0,0 +1,368 @@ +/********************************************************************* + * openNetVM + * https://sdnfv.github.io + * + * BSD LICENSE + * + * Copyright(c) + * 2020 National Institute of Technology Karnataka, Surathkal + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * forward_tb.c - an example using onvm. Simulates a queue with a Token Bucket + * and forwards packets to a DST NF. + ********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "onvm_nflib.h" +#include "onvm_pkt_helper.h" + +#define NF_TAG "simple_fwd_tb" + +#define PKT_READ_SIZE ((uint16_t)32) + +/* number of packets between each print */ +static uint32_t print_delay = 1000000; + +static uint32_t destination; + +/* Token Bucket */ +struct tb_config { + uint64_t tb_rate; // rate at which tokens are generated (in MBps) + uint64_t tb_depth; // depth of the token bucket (in bytes) + uint64_t tb_tokens; // number of the tokens in the bucket at any given time (in bytes) + + uint64_t last_cycle; + uint64_t cur_cycles; +}; + +/* For advanced rings scaling */ +rte_atomic16_t signal_exit_flag; + +void +sig_handler(int sig); + +/* + * Print a usage message + */ +static void +usage(const char *progname) { + printf("Usage:\n"); + printf("%s [EAL args] -- [NF_LIB args] -- -d -p -D -R \n", + progname); + printf("%s -F [EAL args] -- [NF_LIB args] -- [NF args]\n\n", progname); + printf("Flags:\n"); + printf(" - `-d `: destination service ID to foward to\n"); + printf(" - `-p `: number of packets between each print, e.g. `-p 1` prints every packets.\n"); + printf(" - `-D `: depth of token bucket (in bytes)\n"); + printf(" - `-R `: rate of token regeneration (in MBps) \n"); +} + +/* + * Parse the application arguments. + */ +static int +parse_app_args(int argc, char *argv[], const char *progname, struct onvm_nf *nf) { + int c, dst_flag = 0; + struct tb_config *tb_params; + + tb_params = (struct tb_config *)rte_malloc(NULL, sizeof(struct tb_config), 0); + /* Assigning default values */ + tb_params->tb_rate = 1000; + tb_params->tb_depth = 10000; + tb_params->tb_tokens = 10000; + tb_params->last_cycle = rte_get_tsc_cycles(); + nf->data = (void *)tb_params; + + while ((c = getopt(argc, argv, "d:p:D:R:")) != -1) { + switch (c) { + case 'd': + destination = strtoul(optarg, NULL, 10); + dst_flag = 1; + break; + case 'p': + print_delay = strtoul(optarg, NULL, 10); + break; + case 'D': + tb_params->tb_depth = strtoul(optarg, NULL, 10); + tb_params->tb_tokens = tb_params->tb_depth; + if (tb_params->tb_depth < 10000) { + RTE_LOG(INFO, APP, + "WARNING: Small values of depth could lead to packet drops.\n"); + } + break; + case 'R': + tb_params->tb_rate = strtoul(optarg, NULL, 10); + break; + case '?': + usage(progname); + if (optopt == 'd') + RTE_LOG(INFO, APP, "Option -%c requires an argument.\n", optopt); + else if (optopt == 'p') + RTE_LOG(INFO, APP, "Option -%c requires an argument.\n", optopt); + else if (isprint(optopt)) + RTE_LOG(INFO, APP, "Unknown option `-%c'.\n", optopt); + else + RTE_LOG(INFO, APP, "Unknown option character `\\x%x'.\n", optopt); + return -1; + default: + usage(progname); + return -1; + } + } + + if (!dst_flag) { + RTE_LOG(INFO, APP, "Simple Forward with Token Bucket NF requires destination flag -d.\n"); + return -1; + } + + return optind; +} + +/* + * This function displays stats. It uses ANSI terminal codes to clear + * screen when called. It is called from a single non-master + * thread in the server process, when the process is run with more + * than one lcore enabled. + */ +static void +do_stats_display(struct rte_mbuf *pkt) { + const char clr[] = {27, '[', '2', 'J', '\0'}; + const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; + static uint64_t pkt_process = 0; + struct rte_ipv4_hdr *ip; + + pkt_process += print_delay; + + /* Clear screen and move to top left */ + printf("%s%s", clr, topLeft); + + printf("PACKETS\n"); + printf("-----\n"); + printf("Port : %d\n", pkt->port); + printf("Size : %d\n", pkt->pkt_len); + printf("N° : %" PRIu64 "\n", pkt_process); + printf("\n\n"); + + ip = onvm_pkt_ipv4_hdr(pkt); + if (ip != NULL) { + onvm_pkt_print(pkt); + } else { + printf("No IP4 header found\n"); + } +} + +static int +packet_handler_tb(struct rte_mbuf *pkt, struct onvm_pkt_meta *meta, + __attribute__((unused)) struct onvm_nf_local_ctx *nf_local_ctx) { + struct onvm_nf *nf; + struct tb_config *tb_params; + static uint32_t counter = 0; + uint64_t tokens_produced; + uint64_t tb_rate; + uint64_t tb_depth; + uint64_t tb_tokens; + uint64_t last_cycle; + uint64_t cur_cycles; + + nf = nf_local_ctx->nf; + tb_params = (struct tb_config *)nf->data; + tb_rate = tb_params->tb_rate; + tb_depth = tb_params->tb_depth; + tb_tokens = tb_params->tb_tokens; + last_cycle = tb_params->last_cycle; + cur_cycles = tb_params->cur_cycles; + + /* Check if length of packet is greater than depth */ + if (unlikely(pkt->pkt_len > tb_depth)) { + meta->action = ONVM_NF_ACTION_DROP; + } else { + /* Generate tokens only if `tb_tokens` is insufficient */ + if (tb_tokens < pkt->pkt_len) { + cur_cycles = rte_get_tsc_cycles(); + /* Wait until sufficient tokens are available */ + while ((((cur_cycles - last_cycle) * tb_rate * 1000000) / rte_get_timer_hz()) + tb_tokens < + pkt->pkt_len) { + cur_cycles = rte_get_tsc_cycles(); + } + tokens_produced = (((cur_cycles - last_cycle) * tb_rate * 1000000) / rte_get_timer_hz()); + /* Update tokens to a max of tb_depth */ + if (tokens_produced + tb_tokens > tb_depth) { + tb_tokens = tb_depth; + } else { + tb_tokens += tokens_produced; + } + + last_cycle = cur_cycles; + } + + tb_tokens -= pkt->pkt_len; + + meta->action = ONVM_NF_ACTION_TONF; + meta->destination = destination; + } + + if (++counter == print_delay) { + do_stats_display(pkt); + counter = 0; + } + + tb_params->tb_tokens = tb_tokens; + tb_params->last_cycle = last_cycle; + tb_params->cur_cycles = cur_cycles; + + return 0; +} + +void +sig_handler(int sig) { + if (sig != SIGINT && sig != SIGTERM) + return; + + /* Will stop the processing for all spawned threads in advanced rings mode */ + rte_atomic16_set(&signal_exit_flag, 1); +} + +static int +thread_main_loop(struct onvm_nf_local_ctx *nf_local_ctx) { + void *pkts[PKT_READ_SIZE]; + struct onvm_pkt_meta *meta; + uint16_t i, nb_pkts; + struct rte_mbuf *pktsTX[PKT_READ_SIZE]; + int tx_batch_size; + struct rte_ring *rx_ring; + struct rte_ring *msg_q; + struct onvm_nf *nf; + struct onvm_nf_msg *msg; + struct rte_mempool *nf_msg_pool; + + nf = nf_local_ctx->nf; + + onvm_nflib_nf_ready(nf); + + /* Get rings from nflib */ + rx_ring = nf->rx_q; + msg_q = nf->msg_q; + nf_msg_pool = rte_mempool_lookup(_NF_MSG_POOL_NAME); + + printf("Process %d handling packets using advanced rings\n", nf->instance_id); + if (onvm_threading_core_affinitize(nf->thread_info.core) < 0) + rte_exit(EXIT_FAILURE, "Failed to affinitize to core %d\n", nf->thread_info.core); + + ((struct tb_config *)nf->data)->last_cycle = rte_get_tsc_cycles(); + + while (!rte_atomic16_read(&signal_exit_flag)) { + /* Check for a stop message from the manager */ + if (unlikely(rte_ring_count(msg_q) > 0)) { + msg = NULL; + rte_ring_dequeue(msg_q, (void **)(&msg)); + if (msg->msg_type == MSG_STOP) { + rte_atomic16_set(&signal_exit_flag, 1); + } else { + printf("Received message %d, ignoring", msg->msg_type); + } + rte_mempool_put(nf_msg_pool, (void *)msg); + } + + tx_batch_size = 0; + nb_pkts = rte_ring_dequeue_burst(rx_ring, pkts, PKT_READ_SIZE, NULL); + + /* Process all the dequeued packets */ + for (i = 0; i < nb_pkts; i++) { + meta = onvm_get_pkt_meta((struct rte_mbuf *)pkts[i]); + packet_handler_tb((struct rte_mbuf *)pkts[i], meta, nf_local_ctx); + pktsTX[tx_batch_size++] = pkts[i]; + } + + onvm_pkt_process_tx_batch(nf->nf_tx_mgr, pktsTX, tx_batch_size, nf); + if (tx_batch_size < PACKET_READ_SIZE) { + onvm_pkt_flush_all_nfs(nf->nf_tx_mgr, nf); + } + } + return 0; +} + +int +main(int argc, char *argv[]) { + struct onvm_nf_local_ctx *nf_local_ctx; + struct onvm_nf_function_table *nf_function_table; + struct onvm_nf *nf; + int arg_offset; + + const char *progname = argv[0]; + + nf_local_ctx = onvm_nflib_init_nf_local_ctx(); + /* If we're using advanced rings also pass a custom cleanup function, + * this can be used to handle NF specific (non onvm) cleanup logic */ + rte_atomic16_init(&signal_exit_flag); + rte_atomic16_set(&signal_exit_flag, 0); + onvm_nflib_start_signal_handler(nf_local_ctx, sig_handler); + /* No need to define a function table as adv rings won't run onvm_nflib_run */ + nf_function_table = NULL; + + if ((arg_offset = onvm_nflib_init(argc, argv, NF_TAG, nf_local_ctx, nf_function_table)) < 0) { + onvm_nflib_stop(nf_local_ctx); + if (arg_offset == ONVM_SIGNAL_TERMINATION) { + printf("Exiting due to user termination\n"); + return 0; + } else { + rte_exit(EXIT_FAILURE, "Failed ONVM init\n"); + } + } + + argc -= arg_offset; + argv += arg_offset; + + nf = nf_local_ctx->nf; + if (parse_app_args(argc, argv, progname, nf) < 0) { + onvm_nflib_stop(nf_local_ctx); + rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n"); + } + + thread_main_loop(nf_local_ctx); + onvm_nflib_stop(nf_local_ctx); + + printf("If we reach here, program is ending\n"); + return 0; +} diff --git a/examples/simple_fwd_tb/go.sh b/examples/simple_fwd_tb/go.sh new file mode 100644 index 000000000..03fc3bb36 --- /dev/null +++ b/examples/simple_fwd_tb/go.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/speed_tester/go.sh b/examples/speed_tester/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/speed_tester/go.sh +++ b/examples/speed_tester/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/speed_tester/speed_tester.c b/examples/speed_tester/speed_tester.c index f8c563cfe..4b487d30f 100644 --- a/examples/speed_tester/speed_tester.c +++ b/examples/speed_tester/speed_tester.c @@ -85,8 +85,8 @@ static uint16_t destination; /*user defined packet size and destination mac address *size defaults to ethernet header length */ -static uint16_t packet_size = ETHER_HDR_LEN; -static uint8_t d_addr_bytes[ETHER_ADDR_LEN]; +static uint16_t packet_size = RTE_ETHER_HDR_LEN; +static uint8_t d_addr_bytes[RTE_ETHER_ADDR_LEN]; /* track the -c option to see if it has been filled */ static uint8_t use_custom_pkt_count = 0; @@ -142,7 +142,7 @@ usage(const char *progname) { static int parse_app_args(int argc, char *argv[], const char *progname) { int c, i, count, dst_flag = 0; - int values[ETHER_ADDR_LEN]; + int values[RTE_ETHER_ADDR_LEN]; while ((c = getopt(argc, argv, "d:p:s:m:o:c:l")) != -1) { switch (c) { @@ -159,8 +159,8 @@ parse_app_args(int argc, char *argv[], const char *progname) { case 'm': count = sscanf(optarg, "%x:%x:%x:%x:%x:%x", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]); - if (count == ETHER_ADDR_LEN) { - for (i = 0; i < ETHER_ADDR_LEN; ++i) { + if (count == RTE_ETHER_ADDR_LEN) { + for (i = 0; i < RTE_ETHER_ADDR_LEN; ++i) { d_addr_bytes[i] = (uint8_t)values[i]; } } else { @@ -363,7 +363,7 @@ nf_setup(struct onvm_nf_local_ctx *nf_local_ctx) { for (i = 0; i < packet_number; ++i) { struct onvm_pkt_meta *pmeta; - struct ether_hdr *ehdr; + struct rte_ether_hdr *ehdr; int j; struct rte_mbuf *pkt = rte_pktmbuf_alloc(pktmbuf_pool); @@ -373,13 +373,16 @@ nf_setup(struct onvm_nf_local_ctx *nf_local_ctx) { } /*set up ether header and set new packet size*/ - ehdr = (struct ether_hdr *)rte_pktmbuf_append(pkt, packet_size); + ehdr = (struct rte_ether_hdr *)rte_pktmbuf_append(pkt, packet_size); /*using manager mac addr for source *using input string for dest addr */ - rte_eth_macaddr_get(0, &ehdr->s_addr); - for (j = 0; j < ETHER_ADDR_LEN; ++j) { + if (onvm_get_macaddr(0, &ehdr->s_addr) == -1) { + RTE_LOG(INFO, APP, "Using fake MAC address\n"); + onvm_get_fake_macaddr(&ehdr->s_addr); + } + for (j = 0; j < RTE_ETHER_ADDR_LEN; ++j) { ehdr->d_addr.addr_bytes[j] = d_addr_bytes[j]; } ehdr->ether_type = LOCAL_EXPERIMENTAL_ETHER; diff --git a/examples/start_nf.sh b/examples/start_nf.sh index 5d8cc7458..85078392a 100755 --- a/examples/start_nf.sh +++ b/examples/start_nf.sh @@ -24,14 +24,14 @@ SCRIPT=$(readlink -f "$0") SCRIPTPATH=$(dirname "$SCRIPT") NF_NAME=$1 NF_PATH=$SCRIPTPATH/$NF_NAME -# For NFD NF +# For NFD NF NF_NAME=${NF_PATH##*/} BINARY=$NF_PATH/build/app/$NF_NAME DPDK_BASE_ARGS="-n 3 --proc-type=secondary" # For simple mode, only used for initial dpdk startup DEFAULT_CORE_ID=0 -if [ ! -f $BINARY ]; then +if [ ! -f "$BINARY" ]; then echo "ERROR: NF executable not found, $BINARY doesn't exist" echo "Please verify NF binary name and run script from the NF folder" exit 1 @@ -44,7 +44,7 @@ if [ "$1" = "-F" ] then config=$2 shift 2 - exec sudo $BINARY -F $config "$@" + exec sudo "$BINARY" -F "$config" "$@" fi # Check if -- is present, if so parse dpdk/onvm specific args @@ -61,8 +61,8 @@ done # Spaces before $@ are required otherwise it swallows the first arg for some reason if [[ $dash_dash_cnt -ge 2 ]]; then - DPDK_ARGS="$DPDK_BASE_ARGS $(echo " ""$@" | awk -F "--" '{print $1;}')" - ONVM_ARGS="$(echo " ""$@" | awk -F "--" '{print $2;}')" + DPDK_ARGS="$DPDK_BASE_ARGS $(echo " ""$*" | awk -F "--" '{print $1;}')" + ONVM_ARGS="$(echo " ""$*" | awk -F "--" '{print $2;}')" # Move to NF arguments shift ${non_nf_arg_cnt} if [[ $DPDK_ARGS =~ "-l" && ! $ONVM_ARGS =~ "-m" ]]; then @@ -82,4 +82,6 @@ elif [[ $dash_dash_cnt -eq 1 ]]; then exit 1 fi -exec sudo $BINARY $DPDK_ARGS -- $ONVM_ARGS -- "$@" +# don't mess with variable expansion +# shellcheck disable=SC2086 +exec sudo "$BINARY" $DPDK_ARGS -- $ONVM_ARGS -- "$@" diff --git a/examples/test_flow_dir/go.sh b/examples/test_flow_dir/go.sh index 40babd806..03fc3bb36 120000 --- a/examples/test_flow_dir/go.sh +++ b/examples/test_flow_dir/go.sh @@ -1 +1,20 @@ -../go.sh \ No newline at end of file +#!/bin/bash + +#The go.sh script is a convinient way to run start_nf.sh without specifying NF_NAME + +NF_DIR=${PWD##*/} + +if [ ! -f ../start_nf.sh ]; then + echo "ERROR: The ./go.sh script can only be used from the NF folder" + echo "If running from other directory use examples/start_nf.sh" + exit 1 +fi + +# only check for running manager if not in Docker +if [[ -z $(pgrep -u root -f "/onvm/onvm_mgr/.*/onvm_mgr") ]] && ! grep -q "docker" /proc/1/cgroup +then + echo "NF cannot start without a running manager" + exit 1 +fi + +../start_nf.sh "$NF_DIR" "$@" diff --git a/examples/test_flow_dir/test_flow_dir.c b/examples/test_flow_dir/test_flow_dir.c index e9b61876d..5d196314d 100644 --- a/examples/test_flow_dir/test_flow_dir.c +++ b/examples/test_flow_dir/test_flow_dir.c @@ -129,7 +129,7 @@ do_stats_display(struct rte_mbuf *pkt) { const char clr[] = {27, '[', '2', 'J', '\0'}; const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'}; static uint64_t pkt_process = 0; - struct ipv4_hdr *ip; + struct rte_ipv4_hdr *ip; pkt_process += print_delay; diff --git a/examples/test_performance/clear_files.py b/examples/test_performance/clear_files.py index 52f372769..cc48523b1 100755 --- a/examples/test_performance/clear_files.py +++ b/examples/test_performance/clear_files.py @@ -1,30 +1,32 @@ #!/usr/bin/python -### This script resets the data.txt and example_nf_deploy scripts and returns them to their original state +# This script resets the data.txt and example_nf_deploy scripts and returns them to their original state import json with open("data.txt", 'r+') as f: - with open("data-copy.txt", "w") as file: - file.write(f.read()) - f.truncate(0) + with open("data-copy.txt", "w") as file: + file.write(f.read()) + f.truncate(0) with open("../example_nf_deploy_test_p_template.json", "w") as f: - data = {"globals": [ - { - "TTL": 1 - }, - { - "directory": "output_files" - } + f.write("") + data = { + "globals": [ + { + "TTL": 1 + }, + { + "directory": "output_files" + } ], "simple_forward": [ - { - "parameters": "2 -d 1" - } + { + "parameters": "2 -d 1" + } ], "speed_tester": [ - { - "parameters": "1 -d 2 -l -c 16000" - } + { + "parameters": "1 -d 2 -l -c 16000" + } ]} - json.dump(data, f, indent=8) + json.dump(data, f, indent=8) diff --git a/examples/test_performance/data-copy.txt b/examples/test_performance/data-copy.txt index e69de29bb..a85bcdc95 100644 --- a/examples/test_performance/data-copy.txt +++ b/examples/test_performance/data-copy.txt @@ -0,0 +1,3 @@ +5,8254222,1997083, +10,4521436,3337751, +15,2104461,6486186, diff --git a/examples/test_performance/graph.png b/examples/test_performance/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..132064b5aee20928249269e260d669da9de4e39f GIT binary patch literal 38972 zcmce;1z1&W*Dktf6(l941Ob&2P`Z=_qLe7z0)ljhGzKg}5D@`MK|;E_l$H{Z?(Xj1 zW4+(`|NVb^?{lubuXC=muJ=`0>tU`rpZPpv+~dCQF$10|KDv5^<_ZFVxcXQY`3!-; zY(O9|iZ9{9JLmP&Ztxej!^6iam*C}b$ruGcU$&LibU+}8UZDTSNS8=6gEs{or8OL% z+n6}Iys&?XuzKNWYiZ+XX=cdi{LW~(*k)WK|!h;H*WlE0Qt|kketwq_vmx{KRD_C+pDsB7AL~agq-3$9O$AZP`nkzuD_Hr<&%Zkw_H<*IVC7 zb$F*jzHs4K*N$z`ArQR0yZb3}A6kRp_rJpLn;Je?wGIs0o5^b#I*1tMe_hZ-LPFB_ zQ1sd}O%Bvp$H7Fxncbm0lcd(yfBQhe#ANQDH}3vlBNc3I|A#k1=_}s%*Uyade4sI- zB_UDfymk%SH9ieh�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 NIjKR;Jbvls{{Y{yeLDaE literal 0 HcmV?d00001 diff --git a/examples/test_performance/test_performance.sh b/examples/test_performance/test_performance.sh index bbe6b74aa..c21c8aad6 100755 --- a/examples/test_performance/test_performance.sh +++ b/examples/test_performance/test_performance.sh @@ -2,6 +2,8 @@ echo "Type of script (iterative (i) or selective (s) ) = $1" +python3 ./clear_files.py + if [[ $1 = "-s" ]] then echo "Running selective version"