diff --git a/docs/concepts/conf.md b/docs/concepts/conf.md new file mode 100644 index 000000000..af42438f6 --- /dev/null +++ b/docs/concepts/conf.md @@ -0,0 +1,112 @@ +# Config file and command-line arguments +dp-service supports a custom configuration file that reflects its custom command-line arguments, not EAL ones. + +In order to make changes to these easier, along with creating a documentation, argument definition for `getopt` and config file parser is delegated to a python script `hack/dp_conf_generate.py`. You only need to provide a command-line options specification via a JSON file, e.g. `hack/dp_conf.json` and then run `hack/dp_conf_generate.py -s hack/dp_conf.json` to update all needed definitions. + +This document will try to explain most of it, but it can be easier to just look at the generated files if you already have experience with `getopt_long()`. + +## Compiling the parser +After running `hack/dp_conf_generate.py`, you need to include the generated C source file into your own source code. While highly irregular, this removes unnecessary linking problems and hides implementation. + +The generated header file is to be included where needed, i.e. either directly in a single source file, or in a guarded custom header file as usual. + +## Calling the parser +The only thing needed is to pass `argc` and `argv` to `dp_conf_parse_args()`: +``` +switch (dp_conf_parse_args(argc, argv)) { +case DP_CONF_RUNMODE_ERROR: + return EXIT_FAILURE; +case DP_CONF_RUNMODE_EXIT: + return EXIT_SUCCESS; +case DP_CONF_RUNMODE_NORMAL: + break; +} +``` +The return value is documented in the generated header file. + +## JSON Specification +First, you need to specify the output to generate into: +```json + "header": "../include/dp_conf_opts.h", + "source": "../src/dp_conf_opts.c", + "markdown": "../docs/deployment/help_dpservice-bin.md", +``` +And provide a list of options that your program needs to use, e.g. for `--my-option xxx` that takes a string argument and is only used when `#define EXTRA_OPTIONS` is present: +```json + "options": [ + { + "shopt": "m", + "lgopt": "my-option", + "arg": "MY_ARGUMENT", + "help": "this is an example option -m, --my-option MY_ARGUMENT", + "var": "storage_variable", + "type": "char", + "array_size": "DEFINED_STRBUF_LEN", + "ifdef": "EXTRA_OPTIONS" + }, + ... + ] +``` +You can also use `int` arguments: +```json + "options": [ + { + "lgopt": "timeout", + "arg": "SECONDS", + "help": "this is an example option --timeout SECONDS", + "var": "timeout", + "type": "int", + "min": 1, + "max": 120, + "default": 60 + }, + ... + ] +``` +Or even `enum`s: +```json + "options": [ + { + "shopt": "l", + "arg": "LOGLEVEL", + "help": "this is an example option -l LOGLEVEL", + "var": "log_level", + "type": "enum", + "choices": [ "err", "warn", "info" ], + "default": "warn" + }, + ... + ] +``` +There are also `bool` options, the default value dictates, what the option will do, i.e. using the option will switch the value to the negative of the default value: +```json + "options": [ + { + "shopt": "i", + "lgopt": "inverse", + "help": "invert something", + "var": "inverted", + "type": "bool", + "default": "false" + }, + ... + ] +``` +All these options are parsed automatically. If you however need a special combination of settings, `dp_conf_generate.py` will create a static function signature that forces you (via a compilation error) to implement the parser yourself: +```json + "options": [ + { + "shopt": "x", + "arg": "SPECIAL", + "help": "this does something complicated" + }, + ... + ] +``` +This will cause a following error: +``` +In file included from main.c:19: +opts.c:60:13: error: ‘dp_argparse_opt_x’ used but never defined [-Werror] + 19 | static void dp_argparse_x(const char *arg); +``` +Simply implement the function above in the file that included the generated source code. diff --git a/docs/deployment/commandline.md b/docs/deployment/commandline.md index 045c6c230..5dcf8e029 100644 --- a/docs/deployment/commandline.md +++ b/docs/deployment/commandline.md @@ -1,35 +1,11 @@ # Dataplane Service Command-line Options -> This file has been generated by dp_conf_generate.py. As such it should fully reflect the current `dp_service` argument parser. - `dp_service` accepts two sets of options separated by `--`. The first set contains DPDK options, the second `dp_service` options proper. Both sets support `--help` ## EAL Options For more information on EAL options, please see [the official docs](https://doc.dpdk.org/guides/linux_gsg/linux_eal_parameters.html) ## Dataplane Service Options -| Option | Argument | Description | Choices | -|--------|----------|-------------|---------| -| -h, --help | None | display this help and exit | | -| -v, --version | None | display version and exit | | -| --pf0 | IFNAME | first physical interface (e.g. eth0) | | -| --pf1 | IFNAME | second physical interface (e.g. eth1) | | -| --ipv6 | ADDR6 | IPv6 underlay address | | -| --vf-pattern | PATTERN | virtual interface name pattern (e.g. 'eth1vf') | | -| --dhcp-mtu | SIZE | set the mtu field in DHCP responses (68 - 1500) | | -| --dhcp-dns | IPv4 | set the domain name server field in DHCP responses (can be used multiple times) | | -| --udp-virtsvc | IPv4,port,IPv6,port | map a VM-accessible IPv4 endpoint to an outside IPv6 UDP service | | -| --tcp-virtsvc | IPv4,port,IPv6,port | map a VM-accessible IPv4 endpoint to an outside IPv6 TCP service | | -| --wcmp-fraction | FRACTION | weighted-cost-multipath coefficient for pf0 (0.0 - 1.0) | | -| --nic-type | NICTYPE | NIC type to use | 'hw' (default) or 'tap' | -| --no-stats | None | do not print periodic statistics to stdout | | -| --no-conntrack | None | disable connection tracking | | -| --enable-ipv6-overlay | None | enable IPv6 overlay addresses | | -| --no-offload | None | disable traffic offloading | | -| --graphtrace-loglevel | LEVEL | verbosity level of packet traversing the graph framework | | -| --color | MODE | output colorization mode | 'never' (default), 'always' or 'auto' | -| --log-format | FORMAT | set the format of individual log lines (on standard output) | 'text' (default) or 'json' | -| --grpc-port | PORT | listen for gRPC clients on this port | | -| --flow-timeout | SECONDS | inactive flow timeout (except TCP established flows) | | +All options are described in `dpservice-bin --help`, see [the markdown version of it](help_dpservice-bin.md) ## Configuration file Unless an environment variable `DP_CONF` is set to override the path, `dp_service` uses `/tmp/dp_service.conf` to read configuration before processing any arguments. diff --git a/docs/deployment/dpservice-dump.md b/docs/deployment/dpservice-dump.md new file mode 100644 index 000000000..910cbfa21 --- /dev/null +++ b/docs/deployment/dpservice-dump.md @@ -0,0 +1,16 @@ +# Dataplane Service Packet Dump Utility +`dpservice-dump` is a tool to see packets going through dp-service packet processing. + +## Command-line Options +All options are described in `dpservice-dump --help`, see [the markdown version of it](help_dpservice-dump.md) + +## Disclaimer +As this tool attaches to a live packet-processing dp-service, it can cause performance degradation in packet-processing. + +Always make sure that the tool detaches cleanly (i.e. prints out `Graphtrace successfully disabled in dp-service`. If this does not happen (or the user is unable to verify), make sure to call `dpservice-dump --stop` to perform a manual clean-up. + +## Examples +`dpservice-dump` prints all ingress/egress packets processed by dp-service. +`dpservice-dump --drops` also prints dropped packets. +`dpservice-dump --nodes` also prints packets as they are [going through the graph](../concepts/graphtrace.md) + diff --git a/docs/deployment/help_dpservice-bin.md b/docs/deployment/help_dpservice-bin.md new file mode 100644 index 000000000..32c1af879 --- /dev/null +++ b/docs/deployment/help_dpservice-bin.md @@ -0,0 +1,28 @@ +# Command-line Options + +| Option | Argument | Description | Choices | +|--------|----------|-------------|---------| +| -h, --help | None | display this help and exit | | +| -v, --version | None | display version and exit | | +| --pf0 | IFNAME | first physical interface (e.g. eth0) | | +| --pf1 | IFNAME | second physical interface (e.g. eth1) | | +| --ipv6 | ADDR6 | IPv6 underlay address | | +| --vf-pattern | PATTERN | virtual interface name pattern (e.g. 'eth1vf') | | +| --dhcp-mtu | SIZE | set the mtu field in DHCP responses (68 - 1500) | | +| --dhcp-dns | IPv4 | set the domain name server field in DHCP responses (can be used multiple times) | | +| --udp-virtsvc | IPv4,port,IPv6,port | map a VM-accessible IPv4 endpoint to an outside IPv6 UDP service | | +| --tcp-virtsvc | IPv4,port,IPv6,port | map a VM-accessible IPv4 endpoint to an outside IPv6 TCP service | | +| --wcmp | PERCENTAGE | weighted-cost-multipath percentage for pf0 (0 - 100) | | +| --nic-type | NICTYPE | NIC type to use | 'hw' (default) or 'tap' | +| --no-stats | None | do not print periodic statistics to stdout | | +| --no-conntrack | None | disable connection tracking | | +| --enable-ipv6-overlay | None | enable IPv6 overlay addresses | | +| --no-offload | None | disable traffic offloading | | +| --graphtrace-loglevel | LEVEL | verbosity level of packet traversing the graph framework | | +| --color | MODE | output colorization mode | 'never' (default), 'always' or 'auto' | +| --log-format | FORMAT | set the format of individual log lines (on standard output) | 'text' (default) or 'json' | +| --grpc-port | PORT | listen for gRPC clients on this port | | +| --flow-timeout | SECONDS | inactive flow timeout (except TCP established flows) | | + +> This file has been generated by dp_conf_generate.py. As such it should fully reflect the output of `--help`. + diff --git a/docs/deployment/help_dpservice-dump.md b/docs/deployment/help_dpservice-dump.md new file mode 100644 index 000000000..93319672a --- /dev/null +++ b/docs/deployment/help_dpservice-dump.md @@ -0,0 +1,13 @@ +# Command-line Options + +| Option | Argument | Description | Choices | +|--------|----------|-------------|---------| +| -h, --help | None | display this help and exit | | +| -v, --version | None | display version and exit | | +| --drops | None | show dropped packets | | +| --nodes | None | show graph node traversal | | +| --hw | None | capture offloaded packets (only outgoing VF->PF packets supported) | | +| --stop | None | do nothing, only make sure tracing is disabled in dp-service | | + +> This file has been generated by dp_conf_generate.py. As such it should fully reflect the output of `--help`. + diff --git a/docs/development/conf.md b/docs/development/conf.md deleted file mode 100644 index 24421a3c2..000000000 --- a/docs/development/conf.md +++ /dev/null @@ -1,8 +0,0 @@ -# Config file and command-line arguments -dp-service supports a custom configuration file that reflects its custom command-line arguments, not EAL ones. - -In order to make changes to these easier, along with creating a documentation, argument definition for `getopt` and config file parser is delegated to a python script. You only need to edit `hack/dp_conf.json` and then run `hack/dp_conf_generate.py` to update all needed definitions. - -The last thing to do is to actually parse the argument itself, i.e. do something with the string given to you in `src/dp_conf.c:parse_opt()`. If employing memory allocation, also edit `src/dp_conf.c:dp_conf_free()`. - -Almost every usage case is already covered by `dp_conf.json` so the best way to understand the possibilities is to look at that file first. diff --git a/hack/dp_conf.json b/hack/dp_conf.json index 5300bbd7b..199668200 100644 --- a/hack/dp_conf.json +++ b/hack/dp_conf.json @@ -1,158 +1,160 @@ -[ - { - "shopt": "h", - "lgopt": "help", - "help": "display this help and exit" - }, - { - "shopt": "v", - "lgopt": "version", - "help": "display version and exit" - }, - { - "lgopt": "pf0", - "arg": "IFNAME", - "help": "first physical interface (e.g. eth0)", - "var": "pf0_name", - "type": "char", - "array_size": "IFNAMSIZ" - }, - { - "lgopt": "pf1", - "arg": "IFNAME", - "help": "second physical interface (e.g. eth1)", - "var": "pf1_name", - "type": "char", - "array_size": "IFNAMSIZ" - }, - { - "lgopt": "ipv6", - "arg": "ADDR6", - "help": "IPv6 underlay address", - "var": "underlay_ip", - "type": "uint8_t", - "array_size": "16" - }, - { - "lgopt": "vf-pattern", - "arg": "PATTERN", - "help": "virtual interface name pattern (e.g. 'eth1vf')", - "var": "vf_pattern", - "type": "char", - "array_size": "IFNAMSIZ" - }, - { - "lgopt": "dhcp-mtu", - "arg": "SIZE", - "help": "set the mtu field in DHCP responses (68 - 1500)", - "var": "dhcp_mtu", - "type": "int", - "default": 1500 - }, - { - "lgopt": "dhcp-dns", - "arg": "IPv4", - "help": "set the domain name server field in DHCP responses (can be used multiple times)" - }, - { - "lgopt": "udp-virtsvc", - "arg": "IPv4,port,IPv6,port", - "help": "map a VM-accessible IPv4 endpoint to an outside IPv6 UDP service", - "ifdef": "ENABLE_VIRTSVC" - }, - { - "lgopt": "tcp-virtsvc", - "arg": "IPv4,port,IPv6,port", - "help": "map a VM-accessible IPv4 endpoint to an outside IPv6 TCP service", - "ifdef": "ENABLE_VIRTSVC" - }, - { - "lgopt": "wcmp-fraction", - "arg": "FRACTION", - "help": "weighted-cost-multipath coefficient for pf0 (0.0 - 1.0)", - "var": "wcmp_frac", - "type": "double", - "default": "1.0" - }, - { - "lgopt": "nic-type", - "arg": "NICTYPE", - "help": "NIC type to use", - "var": "nic_type", - "type": "enum", - "choices": [ "hw", "tap" ], - "default": "hw" - }, - { - "lgopt": "no-stats", - "help": "do not print periodic statistics to stdout", - "var": "stats_enabled", - "type": "bool", - "default": "true" - }, - { - "lgopt": "no-conntrack", - "help": "disable connection tracking", - "var": "conntrack_enabled", - "type": "bool", - "default": "true" - }, - { - "lgopt": "enable-ipv6-overlay", - "help": "enable IPv6 overlay addresses", - "var": "ipv6_overlay_enabled", - "type": "bool", - "default": "false" - }, - { - "lgopt": "no-offload", - "help": "disable traffic offloading", - "var": "offload_enabled", - "type": "bool", - "default": "true" - }, - { - "lgopt": "graphtrace-loglevel", - "arg": "LEVEL", - "help": "verbosity level of packet traversing the graph framework", - "var": "graphtrace_loglevel", - "type": "int", - "default": "0", - "ifdef": [ "ENABLE_PYTEST" ] - }, - { - "lgopt": "color", - "arg": "MODE", - "help": "output colorization mode", - "var": "color", - "type": "enum", - "choices": [ "never", "always", "auto" ], - "default": "never" - }, - { - "lgopt": "log-format", - "arg": "FORMAT", - "help": "set the format of individual log lines (on standard output)", - "var": "log_format", - "type": "enum", - "choices": [ "text", "json" ], - "default": "text" - }, - { - "lgopt": "grpc-port", - "arg": "PORT", - "help": "listen for gRPC clients on this port", - "var": "grpc_port", - "type": "int", - "default": 1337 - }, - { - "lgopt": "flow-timeout", - "arg": "SECONDS", - "help": "inactive flow timeout (except TCP established flows)", - "var": "flow_timeout", - "type": "int", - "default": "DP_FLOW_DEFAULT_TIMEOUT", - "ifdef": "ENABLE_PYTEST" - } -] +{ + "header": "../include/dp_conf_opts.h", + "source": "../src/dp_conf_opts.c", + "markdown": "../docs/deployment/help_dpservice-bin.md", + "options": [ + { + "lgopt": "pf0", + "arg": "IFNAME", + "help": "first physical interface (e.g. eth0)", + "var": "pf0_name", + "type": "char", + "array_size": "IF_NAMESIZE" + }, + { + "lgopt": "pf1", + "arg": "IFNAME", + "help": "second physical interface (e.g. eth1)", + "var": "pf1_name", + "type": "char", + "array_size": "IF_NAMESIZE" + }, + { + "lgopt": "ipv6", + "arg": "ADDR6", + "help": "IPv6 underlay address" + }, + { + "lgopt": "vf-pattern", + "arg": "PATTERN", + "help": "virtual interface name pattern (e.g. 'eth1vf')", + "var": "vf_pattern", + "type": "char", + "array_size": "IF_NAMESIZE" + }, + { + "lgopt": "dhcp-mtu", + "arg": "SIZE", + "help": "set the mtu field in DHCP responses (68 - 1500)", + "var": "dhcp_mtu", + "type": "int", + "min": 68, + "max": 1500, + "default": 1500 + }, + { + "lgopt": "dhcp-dns", + "arg": "IPv4", + "help": "set the domain name server field in DHCP responses (can be used multiple times)" + }, + { + "lgopt": "udp-virtsvc", + "arg": "IPv4,port,IPv6,port", + "help": "map a VM-accessible IPv4 endpoint to an outside IPv6 UDP service", + "ifdef": "ENABLE_VIRTSVC" + }, + { + "lgopt": "tcp-virtsvc", + "arg": "IPv4,port,IPv6,port", + "help": "map a VM-accessible IPv4 endpoint to an outside IPv6 TCP service", + "ifdef": "ENABLE_VIRTSVC" + }, + { + "lgopt": "wcmp", + "arg": "PERCENTAGE", + "help": "weighted-cost-multipath percentage for pf0 (0 - 100)", + "var": "wcmp_perc", + "type": "int", + "min": 0, + "max": 100, + "default": 100 + }, + { + "lgopt": "nic-type", + "arg": "NICTYPE", + "help": "NIC type to use", + "var": "nic_type", + "type": "enum", + "choices": [ "hw", "tap" ], + "default": "hw" + }, + { + "lgopt": "no-stats", + "help": "do not print periodic statistics to stdout", + "var": "stats_enabled", + "type": "bool", + "default": "true" + }, + { + "lgopt": "no-conntrack", + "help": "disable connection tracking", + "var": "conntrack_enabled", + "type": "bool", + "default": "true" + }, + { + "lgopt": "enable-ipv6-overlay", + "help": "enable IPv6 overlay addresses", + "var": "ipv6_overlay_enabled", + "type": "bool", + "default": "false" + }, + { + "lgopt": "no-offload", + "help": "disable traffic offloading", + "var": "offload_enabled", + "type": "bool", + "default": "true" + }, + { + "lgopt": "graphtrace-loglevel", + "arg": "LEVEL", + "help": "verbosity level of packet traversing the graph framework", + "var": "graphtrace_loglevel", + "type": "int", + "min": 0, + "max": "DP_GRAPHTRACE_LOGLEVEL_MAX", + "default": 0, + "ifdef": [ "ENABLE_PYTEST" ] + }, + { + "lgopt": "color", + "arg": "MODE", + "help": "output colorization mode", + "var": "color", + "type": "enum", + "choices": [ "never", "always", "auto" ], + "default": "never" + }, + { + "lgopt": "log-format", + "arg": "FORMAT", + "help": "set the format of individual log lines (on standard output)", + "var": "log_format", + "type": "enum", + "choices": [ "text", "json" ], + "default": "text" + }, + { + "lgopt": "grpc-port", + "arg": "PORT", + "help": "listen for gRPC clients on this port", + "var": "grpc_port", + "type": "int", + "min": 1024, + "max": 65535, + "default": 1337 + }, + { + "lgopt": "flow-timeout", + "arg": "SECONDS", + "help": "inactive flow timeout (except TCP established flows)", + "var": "flow_timeout", + "type": "int", + "min": 1, + "max": 300, + "default": "DP_FLOW_DEFAULT_TIMEOUT", + "ifdef": "ENABLE_PYTEST" + } + ] +} diff --git a/hack/dp_conf_generate.py b/hack/dp_conf_generate.py index caf8f0b00..f652a9784 100755 --- a/hack/dp_conf_generate.py +++ b/hack/dp_conf_generate.py @@ -8,7 +8,7 @@ re_shopt = re.compile("^[a-zA-Z]$") -re_lgopt = re.compile("^[a-zA-Z][a-zA-Z0-9_\-]") # TODO remove underscore? +re_lgopt = re.compile("^[a-zA-Z][a-zA-Z0-9\-]") class Option: def __init__(self, spec): @@ -22,6 +22,8 @@ def __init__(self, spec): self.array_size = spec.get('array_size') self.ifdef = spec.get('ifdef') self.choices = spec.get('choices') + self.min_val = spec.get('min') + self.max_val = spec.get('max') if self.varname and not self.ctype: raise KeyError(f"Missing 'type' for {spec}") @@ -38,6 +40,12 @@ def __init__(self, spec): if self.lgopt and not re_lgopt.match(self.lgopt): raise ValueError(f"Invalid long option '{self.lgopt}' in {spec}") + if self.min_val == None: + self.min_val = "INT_MIN" + + if self.max_val == None: + self.max_val = "INT_MAX" + if self.ctype == 'enum': if not self.choices: raise KeyError(f"Missing choices in enum definition for {spec}") @@ -71,6 +79,44 @@ def __init__(self, spec): default = " (default)" if self.default == choice else "" self.help_choices += f"'{choice}'{default}" + # This will force the user to implement custom parsing functions + self.argparse_signature = f"int dp_argparse_{self.optid.lower()}" + if not self.arg: + if self.ctype == "bool": + self.argparse_signature = None + else: + self.argparse_signature += "(void)" + else: + if not self.ctype: + self.argparse_signature += "(const char *arg)" + elif self.ctype == "enum" or self.ctype == "char" or self.ctype == "int": + self.argparse_signature = None + elif self.array_size is not None: + self.argparse_signature += f"({self.ctype} dst_{self.varname}[{self.array_size}], const char *arg)" + else: + self.argparse_signature += f"({self.ctype} *dst_{self.varname}, const char *arg)" + + # This is the actual parsing implementation call + self.argparse_call = f"dp_argparse_{self.optid.lower()}" + if not self.arg: + if self.ctype == "bool": + self.argparse_call = f"dp_argparse_store_{'false' if self.default == 'true' else 'true'}" + self.argparse_call += f"(&{self.varname})" + else: + if not self.ctype: + self.argparse_call += "(arg)" + elif self.ctype == "enum": + choices = f"{self.varname}_choices" + self.argparse_call = f"dp_argparse_enum(arg, (int *)&{self.varname}, {choices}, ARRAY_SIZE({choices}))" + elif self.ctype == "char": + self.argparse_call = f"dp_argparse_string(arg, {self.varname}, ARRAY_SIZE({self.varname}))" + elif self.ctype == "int": + self.argparse_call = f"dp_argparse_int(arg, &{self.varname}, {self.min_val}, {self.max_val})" + elif self.array_size is not None: + self.argparse_call += f"(arg, {self.varname}, ARRAY_SIZE({self.varname}))" + else: + self.argparse_call += f"(arg, &{self.varname})" + # Use this to make sure given line is under #ifdef def print(self, string): for ifdef in self.ifdef: @@ -88,8 +134,15 @@ def get_signature(option): return f"{const}{ctype} {ptr}{prefix}{option.varname}(void)" def generate_c(options): + # dp_argparse.h is needed for the default parsing functions + print('#include "dp_argparse.h"\n') + print("#ifndef ARRAY_SIZE") + print("#\tdefine ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof((ARRAY)[0]))") + print("#endif\n") # Generate IDs print("enum {") + print("\tOPT_HELP = 'h',") + print("\tOPT_VERSION = 'v',") for option in options: if not option.shopt: continue @@ -101,7 +154,7 @@ def generate_c(options): option.print(f"\t{option.optid},") print("};\n") # Generate getopt() and getopt_long() optstring - print("#define OPTSTRING \\") + print('#define OPTSTRING ":hv" \\') for option in options: if not option.shopt: continue @@ -109,7 +162,9 @@ def generate_c(options): option.print(f'\t"{option.shopt}{arg}" \\') print("") # Generate getopt_long() option array - print("static const struct option longopts[] = {") + print("static const struct option dp_conf_longopts[] = {") + print('\t{ "help", 0, 0, OPT_HELP },') + print('\t{ "version", 0, 0, OPT_VERSION },') for option in options: if not option.lgopt: continue @@ -125,36 +180,108 @@ def generate_c(options): for choice in option.choices: option.print(f'\t"{choice}",') option.print("};\n") - # Generate help function - longest_opts = max(len(option.help_opts) for option in options) - print('static void print_help_args(FILE *outfile)\n{\n\tfprintf(outfile, "%s",') - for option in options: - help_opts = option.help_opts.ljust(longest_opts) - choices = f": {option.help_choices}" if option.choices else "" - option.print(f'\t\t" {help_opts} {option.help_str}{choices}\\n"') - print("\t);\n}\n") # Generate storage variables for option in options: - if not option.ctype or not option.varname: + if not option.ctype: continue array = f"[{option.array_size}]" if option.array_size is not None else ""; ctype = option.ctype default = "" if ctype == "enum": - if option.default: + if option.default is not None: default = f" = DP_CONF_{option.varname.upper()}_{option.default.upper()}" ctype = f"enum dp_conf_{option.varname}" else: - if option.default: + if option.default is not None: default = f" = {option.default}" option.print(f"static {ctype} {option.varname}{array}{default};") print("") # Generate getters for option in options: - if not option.ctype or not option.varname: + if not option.ctype: continue signature = get_signature(option) option.print(f"{signature}\n{{\n\treturn {option.varname};\n}}\n") + print("") + # Generate function signatures to be provided by the user + print("\n/* These functions need to be implemented by the user of this generated code */") + print("static void dp_argparse_version(void);") + for option in options: + if option.argparse_signature: + option.print(f"static {option.argparse_signature};") + print("\n") + # Generate help function + print("static inline void dp_argparse_help(const char *progname, FILE *outfile)") + print("{") + print('\tfprintf(outfile, "Usage: %s [options]\\n"') + longest_opt = max(len(option.help_opts) for option in options) + help_opts_help = "-h, --help" + help_opts_version = "-v, --version" + longest_opt = max(longest_opt, len(help_opts_help), len(help_opts_version)) + print(f'\t\t" {help_opts_help.ljust(longest_opt)} display this help and exit\\n"') + print(f'\t\t" {help_opts_version.ljust(longest_opt)} display version and exit\\n"') + for option in options: + help_opts = option.help_opts.ljust(longest_opt) + choices = f": {option.help_choices}" if option.choices else "" + option.print(f'\t\t" {help_opts} {option.help_str}{choices}\\n"') + print("\t, progname);") + print("}\n") + # Generate the main switch to enforce user-implemented functions + print("static int dp_conf_parse_arg(int opt, const char *arg)") + print("{") + print("\t(void)arg; // if no option uses an argument, this would be unused") + print("\tswitch (opt) {") + for option in options: + option.print(f"\tcase {option.optid}:\n\t\treturn {option.argparse_call};") + print("\tdefault:") + print('\t\tfprintf(stderr, "Unimplemented option %d\\n", opt);') + print("\t\treturn DP_ERROR;") + print("\t}") + print("}\n") + # Generate the parser entrypoint + print("enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv)") + print("{") + print("\tconst char *progname = argv[0];") + print("\tint option_index = -1;") + print("\tint opt;") + print("") + print("\twhile ((opt = getopt_long(argc, argv, OPTSTRING, dp_conf_longopts, &option_index)) != -1) {") + print("\t\tswitch (opt) {") + print("\t\tcase OPT_HELP:") + print("\t\t\tdp_argparse_help(progname, stdout);") + print("\t\t\treturn DP_CONF_RUNMODE_EXIT;") + print("\t\tcase OPT_VERSION:") + print("\t\t\tdp_argparse_version();") + print("\t\t\treturn DP_CONF_RUNMODE_EXIT;") + print("\t\tcase ':':") + print("\t\t\tfprintf(stderr, \"Missing argument for '%s'\\n\", argv[optind-1]);") + print("\t\t\treturn DP_CONF_RUNMODE_ERROR;") + print("\t\tcase '?':") + print("\t\t\tif (optopt > 0)") + print("\t\t\t\tfprintf(stderr, \"Unknown option '-%c'\\n\", optopt);") + print("\t\t\telse") + print("\t\t\t\tfprintf(stderr, \"Unknown option '%s'\\n\", argv[optind-1]);") + print("\t\t\treturn DP_CONF_RUNMODE_ERROR;") + print("\t\tdefault:") + print("\t\t\tif (DP_FAILED(dp_conf_parse_arg(opt, optarg))) {") + print("\t\t\t\tif (option_index >= 0)") + print("\t\t\t\t\tfprintf(stderr, \"Invalid argument for '--%s'\\n\", dp_conf_longopts[option_index].name);") + print("\t\t\t\telse") + print("\t\t\t\t\tfprintf(stderr, \"Invalid argument for '-%c'\\n\", opt);") + print("\t\t\t\treturn DP_CONF_RUNMODE_ERROR;") + print("\t\t\t}") + print("\t\t}") + print("\t\toption_index = -1;") + print("\t}") + print("\treturn DP_CONF_RUNMODE_NORMAL;") + print("}\n") + +""" +TODO markwówn, mention _args and _arg generated output? TODO public _arg? +In file included from ../tools/dump/main.c:19: +../tools/dump/opts.c:60:13: error: ‘dp_argparse_version’ used but never defined [-Werror] + 60 | static void dp_argparse_version(void); +""" def generate_h(options): # Generate enums @@ -167,31 +294,34 @@ def generate_h(options): option.print("};\n") # Generate getters for option in options: - if not option.ctype or not option.varname: + if not option.ctype: continue signature = get_signature(option) option.print(f"{signature};") - -def generate_md(options): - print("# Dataplane Service Command-line Options") - print("> This file has been generated by dp_conf_generate.py. As such it should fully reflect the current `dp_service` argument parser.") + # Generate parser entrypoint and result print("") - print("`dp_service` accepts two sets of options separated by `--`. The first set contains DPDK options, the second `dp_service` options proper. Both sets support `--help`") + print("enum dp_conf_runmode {") + print("\tDP_CONF_RUNMODE_NORMAL, /**< Start normally */") + print("\tDP_CONF_RUNMODE_EXIT, /**< End succesfully (e.g. for --help etc.) */") + print("\tDP_CONF_RUNMODE_ERROR, /**< Error parsing arguments */") + print("};") print("") - print("## EAL Options") - print("For more information on EAL options, please see [the official docs](https://doc.dpdk.org/guides/linux_gsg/linux_eal_parameters.html)") + print("enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv);") + +def generate_md(options): + print("# Command-line Options") print("") - print("## Dataplane Service Options") print("| Option | Argument | Description | Choices |") print("|--------|----------|-------------|---------|") + print("| -h, --help | None | display this help and exit | |") + print("| -v, --version | None | display version and exit | |") for option in options: opts = option.shopt opts = f"-{opts}, --{option.lgopt}" if opts else f"--{option.lgopt}" print(f"| {opts} | {option.arg} | {option.help_str} | {option.help_choices} |") print("") - print("## Configuration file") - print("Unless an environment variable `DP_CONF` is set to override the path, `dp_service` uses `/tmp/dp_service.conf` to read configuration before processing any arguments.") - print("This way you can provide any arguments via such file and simplify the commandline use. The helper script `prepare.sh` generates such a file for Mellanox users.") + print("> This file has been generated by dp_conf_generate.py. As such it should fully reflect the output of `--help`.") + print("") def print_warning(): @@ -207,34 +337,36 @@ def print_warning(): script_path = os.path.dirname(os.path.abspath(__file__)) parser = argparse.ArgumentParser() parser.add_argument("-s", "--specs", action="store", default=f"{script_path}/dp_conf.json") - parser.add_argument("--source", action="store", default=f"{script_path}/../src/dp_conf_opts.c") - parser.add_argument("--header", action="store", default=f"{script_path}/../include/dp_conf_opts.h") - parser.add_argument("--markdown", action="store", default=f"{script_path}/../docs/deployment/commandline.md") - parser.add_argument("--no-markdown", action="store_true") args = parser.parse_args() + specs_path = os.path.dirname(args.specs) + with open(args.specs, "r") as infile: specs = json.load(infile) - if not isinstance(specs, list): + header = f"{specs_path}/{specs['header']}" + source = f"{specs_path}/{specs['source']}" + markdown = f"{specs_path}/{specs['markdown']}" + opts = specs['options'] + if not isinstance(opts, list): raise ValueError("Specs do not contain a list of options") - options = [ Option(spec) for spec in specs ] + options = [ Option(opt) for opt in opts ] + # easier use of print() stdout = sys.stdout - with open(args.source, "w") as outfile: + with open(source, "w") as outfile: sys.stdout = outfile print_warning() generate_c(options) - with open(args.header, "w") as outfile: + with open(header, "w") as outfile: sys.stdout = outfile print_warning() generate_h(options) - if not args.no_markdown: - with open(args.markdown, "w") as outfile: - sys.stdout = outfile - generate_md(options) + with open(markdown, "w") as outfile: + sys.stdout = outfile + generate_md(options) sys.stdout = stdout diff --git a/include/dp_argparse.h b/include/dp_argparse.h new file mode 100644 index 000000000..02a06aaad --- /dev/null +++ b/include/dp_argparse.h @@ -0,0 +1,35 @@ +#ifndef __DP_ARGPARSE_H__ +#define __DP_ARGPARSE_H__ + +#include +#include + +#include "dp_error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static inline int dp_argparse_store_true(bool *dst) +{ + *dst = true; + return DP_OK; +} + +static inline int dp_argparse_store_false(bool *dst) +{ + *dst = false; + return DP_OK; +} + +int dp_argparse_string(const char *arg, char *dst, size_t dst_size); + +int dp_argparse_int(const char *arg, int *dst, int min, int max); + +int dp_argparse_enum(const char *arg, int *dst, const char *choices[], size_t choice_count); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/dp_conf.h b/include/dp_conf.h index 1d6eeb212..f2a3f1303 100644 --- a/include/dp_conf.h +++ b/include/dp_conf.h @@ -29,14 +29,6 @@ struct dp_conf_dhcp_dns { uint8_t *array; }; -enum dp_conf_runmode { - DP_CONF_RUNMODE_NORMAL, /**< Start dp_service normally */ - DP_CONF_RUNMODE_EXIT, /**< End succesfully (e.g. for --help etc.) */ - DP_CONF_RUNMODE_ERROR /**< Error parsing arguments */ -}; - -enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv); - int dp_conf_parse_file(const char *filename); void dp_conf_free(void); @@ -48,6 +40,7 @@ void dp_conf_free(void); int dp_conf_is_wcmp_enabled(void); const char *dp_conf_get_eal_a_pf0(void); const char *dp_conf_get_eal_a_pf1(void); +const uint8_t *dp_conf_get_underlay_ip(void); const struct dp_conf_dhcp_dns *dp_conf_get_dhcp_dns(void); #ifdef ENABLE_VIRTSVC const struct dp_conf_virtual_services *dp_conf_get_virtual_services(void); diff --git a/include/dp_conf_opts.h b/include/dp_conf_opts.h index 1b7475b90..6d39d7193 100644 --- a/include/dp_conf_opts.h +++ b/include/dp_conf_opts.h @@ -23,10 +23,9 @@ enum dp_conf_log_format { const char *dp_conf_get_pf0_name(void); const char *dp_conf_get_pf1_name(void); -const uint8_t *dp_conf_get_underlay_ip(void); const char *dp_conf_get_vf_pattern(void); int dp_conf_get_dhcp_mtu(void); -double dp_conf_get_wcmp_frac(void); +int dp_conf_get_wcmp_perc(void); enum dp_conf_nic_type dp_conf_get_nic_type(void); bool dp_conf_is_stats_enabled(void); bool dp_conf_is_conntrack_enabled(void); @@ -41,3 +40,11 @@ int dp_conf_get_grpc_port(void); #ifdef ENABLE_PYTEST int dp_conf_get_flow_timeout(void); #endif + +enum dp_conf_runmode { + DP_CONF_RUNMODE_NORMAL, /**< Start normally */ + DP_CONF_RUNMODE_EXIT, /**< End succesfully (e.g. for --help etc.) */ + DP_CONF_RUNMODE_ERROR, /**< Error parsing arguments */ +}; + +enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv); diff --git a/include/dp_util.h b/include/dp_util.h index 4c334ea6a..e16823460 100644 --- a/include/dp_util.h +++ b/include/dp_util.h @@ -14,8 +14,6 @@ extern "C" { #include #include -#include "dp_conf.h" - #define VM_IFACE_ID_MAX_LEN 64 #define DP_FIREWALL_ID_MAX_LEN 64 #define VM_MACHINE_PXE_MAX_LEN 32 diff --git a/src/dp_argparse.c b/src/dp_argparse.c new file mode 100644 index 000000000..5349f570e --- /dev/null +++ b/src/dp_argparse.c @@ -0,0 +1,54 @@ +#include "dp_argparse.h" +#include +#include +#include +#include "dp_error.h" + +/* NOTE: This module uses plain printf() because it is being used by other tools, not just dp-service */ + +int dp_argparse_string(const char *arg, char *dst, size_t dst_size) +{ + size_t len = strlen(arg); + + if (len >= dst_size) { + fprintf(stderr, "Value '%s' is too long (max %lu characters)\n", arg, dst_size-1); + return DP_ERROR; + } + + memcpy(dst, arg, len+1); // including \0 + return DP_OK; +} + +int dp_argparse_int(const char *arg, int *dst, int min, int max) +{ + long result; + char *endptr; + + result = strtol(arg, &endptr, 10); + if (*endptr) { + fprintf(stderr, "Value '%s' is not an integer\n", arg); + return DP_ERROR; + } + if (result < min || result > max) { + fprintf(stderr, "Value '%s' is out of range (%d-%d)\n", arg, min, max); + return DP_ERROR; + } + + *dst = (int)result; // this is fine, limited by min/max + return DP_OK; +} + +int dp_argparse_enum(const char *arg, int *dst, const char *choices[], size_t choice_count) +{ + for (size_t i = 0; i < choice_count; ++i) { + if (!strcmp(choices[i], arg)) { + *dst = i; + return DP_OK; + } + } + fprintf(stderr, "Invalid choice '%s' (choose from:", arg); + for (size_t i = 0; i < choice_count; ++i) + fprintf(stderr, "%s '%s'", i > 0 ? "," : "", choices[i]); + fprintf(stderr, ")\n"); + return DP_ERROR; +} diff --git a/src/dp_cntrack.c b/src/dp_cntrack.c index 384ec7c47..259bb5668 100644 --- a/src/dp_cntrack.c +++ b/src/dp_cntrack.c @@ -1,4 +1,5 @@ #include "dp_cntrack.h" +#include "dp_conf.h" #include "dp_error.h" #include "dp_log.h" #include "dp_vnf.h" diff --git a/src/dp_conf.c b/src/dp_conf.c index 91175461f..1a6f04c67 100644 --- a/src/dp_conf.c +++ b/src/dp_conf.c @@ -26,9 +26,9 @@ #include "dp_conf_opts.c" // custom storage variables and getters -static bool wcmp_enabled = false; static char eal_a_pf0[DP_EAL_A_MAXLEN] = {0}; static char eal_a_pf1[DP_EAL_A_MAXLEN] = {0}; +static uint8_t underlay_ip[16] = {0}; static struct dp_conf_dhcp_dns dhcp_dns = {0}; #ifdef ENABLE_VIRTSVC static struct dp_conf_virtual_services virtual_services = {0}; @@ -36,7 +36,7 @@ static struct dp_conf_virtual_services virtual_services = {0}; int dp_conf_is_wcmp_enabled(void) { - return wcmp_enabled; + return wcmp_perc < 100; } const char *dp_conf_get_eal_a_pf0(void) @@ -49,6 +49,11 @@ const char *dp_conf_get_eal_a_pf1(void) return eal_a_pf1; } +const uint8_t *dp_conf_get_underlay_ip(void) +{ + return underlay_ip; +} + const struct dp_conf_dhcp_dns *dp_conf_get_dhcp_dns(void) { return &dhcp_dns; @@ -62,78 +67,6 @@ const struct dp_conf_virtual_services *dp_conf_get_virtual_services(void) #endif -static inline int opt_strcpy(char *dst, const char *src, size_t max_size) -{ - size_t len = strlen(src); - - if (len >= max_size) { - DP_EARLY_ERR("Value '%s' is too long (max %lu characters)", src, max_size-1); - return DP_ERROR; - } - - memcpy(dst, src, len+1); // including \0 - return DP_OK; -} - -static inline int opt_str_to_int(int *dst, const char *src, int min, int max) -{ - long result; - char *endptr; - - result = strtol(src, &endptr, 10); - if (*endptr) { - DP_EARLY_ERR("Value '%s' is not an integer", src); - return DP_ERROR; - } - if (result < min || result > max) { - DP_EARLY_ERR("Value '%s' is not in range %d-%d", src, min, max); - return DP_ERROR; - } - - *dst = (int)result; // this is fine, limited by min/max - return DP_OK; -} - -static inline int opt_str_to_double(double *dst, const char *src, double min, double max) -{ - double result; - char *endptr; - - result = strtod(src, &endptr); - if (*endptr) { - DP_EARLY_ERR("Value '%s' is not a double", src); - return DP_ERROR; - } - if (result < min || result > max) { - DP_EARLY_ERR("Value '%s' is not in range %lf-%lf", src, min, max); - return DP_ERROR; - } - - *dst = result; - return DP_OK; -} - -static int opt_str_to_ipv6(void *dst, const char *arg) -{ - if (inet_pton(AF_INET6, arg, dst) != 1) { - DP_EARLY_ERR("Invalid IPv6 address format: '%s'", arg); - return DP_ERROR; - } - return DP_OK; -} - -static int opt_str_to_enum(int *dst, const char *arg, const char *choices[], size_t choice_count) -{ - for (size_t i = 0; i < choice_count; ++i) { - if (!strcmp(choices[i], arg)) { - *dst = i; - return DP_OK; - } - } - DP_EARLY_ERR("Invalid choice '%s'", arg); - return DP_ERROR; -} - static int add_dhcp_dns(const char *str) { uint8_t *tmp; @@ -260,101 +193,12 @@ static int add_virtsvc(uint8_t proto, const char *str) } #endif -static int parse_opt(int opt, const char *arg) -{ - switch (opt) { - case OPT_PF0: - return opt_strcpy(pf0_name, arg, sizeof(pf0_name)); - case OPT_PF1: - return opt_strcpy(pf1_name, arg, sizeof(pf1_name)); - case OPT_VF_PATTERN: - return opt_strcpy(vf_pattern, arg, sizeof(vf_pattern)); - case OPT_IPV6: - return opt_str_to_ipv6(underlay_ip, arg); - case OPT_NIC_TYPE: - return opt_str_to_enum((int *)&nic_type, arg, nic_type_choices, RTE_DIM(nic_type_choices)); - case OPT_DHCP_MTU: - return opt_str_to_int(&dhcp_mtu, arg, 68, 1500); // RFC 791, RFC 894 - case OPT_DHCP_DNS: - return add_dhcp_dns(arg); -#ifdef ENABLE_VIRTSVC - case OPT_TCP_VIRTSVC: - return add_virtsvc(IPPROTO_TCP, arg); - case OPT_UDP_VIRTSVC: - return add_virtsvc(IPPROTO_UDP, arg); -#endif - case OPT_WCMP_FRACTION: - wcmp_enabled = true; - return opt_str_to_double(&wcmp_frac, arg, 0.0, 1.0); - case OPT_NO_OFFLOAD: - offload_enabled = false; - return DP_OK; - case OPT_NO_CONNTRACK: - conntrack_enabled = false; - return DP_OK; - case OPT_NO_STATS: - stats_enabled = false; - return DP_OK; - case OPT_ENABLE_IPV6_OVERLAY: - ipv6_overlay_enabled = true; - return DP_OK; -#ifdef ENABLE_PYTEST - case OPT_GRAPHTRACE_LOGLEVEL: - return opt_str_to_int(&graphtrace_loglevel, arg, 0, DP_GRAPHTRACE_LOGLEVEL_MAX); -#endif - case OPT_COLOR: - return opt_str_to_enum((int *)&color, arg, color_choices, RTE_DIM(color_choices)); - case OPT_LOG_FORMAT: - return opt_str_to_enum((int *)&log_format, arg, log_format_choices, RTE_DIM(log_format_choices)); - case OPT_GRPC_PORT: - return opt_str_to_int(&grpc_port, arg, 1024, 65535); -#ifdef ENABLE_PYTEST - case OPT_FLOW_TIMEOUT: - return opt_str_to_int((int *)&flow_timeout, arg, 1, 300); -#endif - default: - DP_EARLY_ERR("Unimplemented option %d", opt); - return DP_ERROR; - } -} - -static inline void print_usage(const char *progname, FILE *outfile) -{ - fprintf(outfile, "Usage: %s [EAL options] -- [service options]\n", progname); - print_help_args(outfile); -} - -enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv) -{ - const char *progname = argv[0]; - int opt; - - while ((opt = getopt_long(argc, argv, OPTSTRING, longopts, NULL)) != -1) { - switch (opt) { - case OPT_HELP: - print_usage(progname, stdout); - return DP_CONF_RUNMODE_EXIT; - case OPT_VERSION: - printf("DP Service version %s\n", DP_SERVICE_VERSION); - return DP_CONF_RUNMODE_EXIT; - case '?': - print_usage(progname, stderr); - return DP_CONF_RUNMODE_ERROR; - default: - if (DP_FAILED(parse_opt(opt, optarg))) - return DP_CONF_RUNMODE_ERROR; - } - } - - return DP_CONF_RUNMODE_NORMAL; -} - - static const struct option *get_opt_by_name(const char *name) { const struct option *longopt; - for (longopt = longopts; longopt->name; ++longopt) + // accessing the generated longopts array here + for (longopt = dp_conf_longopts; longopt->name; ++longopt) if (!strcmp(name, longopt->name)) return longopt; @@ -387,9 +231,9 @@ static int parse_line(char *line, int lineno) // Config-file-specific keys if (!strcmp(key, "a-pf0")) - return opt_strcpy(eal_a_pf0, value, sizeof(eal_a_pf0)); + return dp_argparse_string(value, eal_a_pf0, sizeof(eal_a_pf0)); if (!strcmp(key, "a-pf1")) - return opt_strcpy(eal_a_pf1, value, sizeof(eal_a_pf1)); + return dp_argparse_string(value, eal_a_pf1, sizeof(eal_a_pf1)); // Otherwise support all long options if (!longopt) { @@ -397,7 +241,9 @@ static int parse_line(char *line, int lineno) return DP_ERROR; } - return parse_opt(longopt->val, value); + // This is re-using the function generated by dp_conf_generate.py + // that parses a single option + return dp_conf_parse_arg(longopt->val, value); } static int parse_file(FILE *file) @@ -448,3 +294,35 @@ void dp_conf_free(void) free(virtual_services.entries); #endif } + +// Implement required functions for dp_conf_parse_args() +static void dp_argparse_version(void) +{ + printf("DP Service version %s\n", DP_SERVICE_VERSION); +} + +static int dp_argparse_opt_ipv6(const char *arg) +{ + if (inet_pton(AF_INET6, arg, underlay_ip) != 1) { + DP_EARLY_ERR("Invalid IPv6 address format: '%s'", arg); + return DP_ERROR; + } + return DP_OK; +} + +static int dp_argparse_opt_dhcp_dns(const char *arg) +{ + return add_dhcp_dns(arg); +} + +#ifdef ENABLE_VIRTSVC +static int dp_argparse_opt_udp_virtsvc(const char *arg) +{ + return add_virtsvc(IPPROTO_UDP, arg); +} + +static int dp_argparse_opt_tcp_virtsvc(const char *arg) +{ + return add_virtsvc(IPPROTO_TCP, arg); +} +#endif diff --git a/src/dp_conf_opts.c b/src/dp_conf_opts.c index 8e8f6576d..4f839583c 100644 --- a/src/dp_conf_opts.c +++ b/src/dp_conf_opts.c @@ -5,6 +5,12 @@ /* Please edit dp_conf.json and re-run the script to update this file. */ /***********************************************************************/ +#include "dp_argparse.h" + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof((ARRAY)[0])) +#endif + enum { OPT_HELP = 'h', OPT_VERSION = 'v', @@ -21,7 +27,7 @@ _OPT_SHOPT_MAX = 255, #ifdef ENABLE_VIRTSVC OPT_TCP_VIRTSVC, #endif - OPT_WCMP_FRACTION, + OPT_WCMP, OPT_NIC_TYPE, OPT_NO_STATS, OPT_NO_CONNTRACK, @@ -38,11 +44,9 @@ _OPT_SHOPT_MAX = 255, #endif }; -#define OPTSTRING \ - "h" \ - "v" \ +#define OPTSTRING ":hv" \ -static const struct option longopts[] = { +static const struct option dp_conf_longopts[] = { { "help", 0, 0, OPT_HELP }, { "version", 0, 0, OPT_VERSION }, { "pf0", 1, 0, OPT_PF0 }, @@ -57,7 +61,7 @@ static const struct option longopts[] = { #ifdef ENABLE_VIRTSVC { "tcp-virtsvc", 1, 0, OPT_TCP_VIRTSVC }, #endif - { "wcmp-fraction", 1, 0, OPT_WCMP_FRACTION }, + { "wcmp", 1, 0, OPT_WCMP }, { "nic-type", 1, 0, OPT_NIC_TYPE }, { "no-stats", 0, 0, OPT_NO_STATS }, { "no-conntrack", 0, 0, OPT_NO_CONNTRACK }, @@ -91,47 +95,11 @@ static const char *log_format_choices[] = { "json", }; -static void print_help_args(FILE *outfile) -{ - fprintf(outfile, "%s", - " -h, --help display this help and exit\n" - " -v, --version display version and exit\n" - " --pf0=IFNAME first physical interface (e.g. eth0)\n" - " --pf1=IFNAME second physical interface (e.g. eth1)\n" - " --ipv6=ADDR6 IPv6 underlay address\n" - " --vf-pattern=PATTERN virtual interface name pattern (e.g. 'eth1vf')\n" - " --dhcp-mtu=SIZE set the mtu field in DHCP responses (68 - 1500)\n" - " --dhcp-dns=IPv4 set the domain name server field in DHCP responses (can be used multiple times)\n" -#ifdef ENABLE_VIRTSVC - " --udp-virtsvc=IPv4,port,IPv6,port map a VM-accessible IPv4 endpoint to an outside IPv6 UDP service\n" -#endif -#ifdef ENABLE_VIRTSVC - " --tcp-virtsvc=IPv4,port,IPv6,port map a VM-accessible IPv4 endpoint to an outside IPv6 TCP service\n" -#endif - " --wcmp-fraction=FRACTION weighted-cost-multipath coefficient for pf0 (0.0 - 1.0)\n" - " --nic-type=NICTYPE NIC type to use: 'hw' (default) or 'tap'\n" - " --no-stats do not print periodic statistics to stdout\n" - " --no-conntrack disable connection tracking\n" - " --enable-ipv6-overlay enable IPv6 overlay addresses\n" - " --no-offload disable traffic offloading\n" -#ifdef ENABLE_PYTEST - " --graphtrace-loglevel=LEVEL verbosity level of packet traversing the graph framework\n" -#endif - " --color=MODE output colorization mode: 'never' (default), 'always' or 'auto'\n" - " --log-format=FORMAT set the format of individual log lines (on standard output): 'text' (default) or 'json'\n" - " --grpc-port=PORT listen for gRPC clients on this port\n" -#ifdef ENABLE_PYTEST - " --flow-timeout=SECONDS inactive flow timeout (except TCP established flows)\n" -#endif - ); -} - -static char pf0_name[IFNAMSIZ]; -static char pf1_name[IFNAMSIZ]; -static uint8_t underlay_ip[16]; -static char vf_pattern[IFNAMSIZ]; +static char pf0_name[IF_NAMESIZE]; +static char pf1_name[IF_NAMESIZE]; +static char vf_pattern[IF_NAMESIZE]; static int dhcp_mtu = 1500; -static double wcmp_frac = 1.0; +static int wcmp_perc = 100; static enum dp_conf_nic_type nic_type = DP_CONF_NIC_TYPE_HW; static bool stats_enabled = true; static bool conntrack_enabled = true; @@ -157,11 +125,6 @@ const char *dp_conf_get_pf1_name(void) return pf1_name; } -const uint8_t *dp_conf_get_underlay_ip(void) -{ - return underlay_ip; -} - const char *dp_conf_get_vf_pattern(void) { return vf_pattern; @@ -172,9 +135,9 @@ int dp_conf_get_dhcp_mtu(void) return dhcp_mtu; } -double dp_conf_get_wcmp_frac(void) +int dp_conf_get_wcmp_perc(void) { - return wcmp_frac; + return wcmp_perc; } enum dp_conf_nic_type dp_conf_get_nic_type(void) @@ -231,3 +194,145 @@ int dp_conf_get_flow_timeout(void) } #endif + + +/* These functions need to be implemented by the user of this generated code */ +static void dp_argparse_version(void); +static int dp_argparse_opt_ipv6(const char *arg); +static int dp_argparse_opt_dhcp_dns(const char *arg); +#ifdef ENABLE_VIRTSVC +static int dp_argparse_opt_udp_virtsvc(const char *arg); +#endif +#ifdef ENABLE_VIRTSVC +static int dp_argparse_opt_tcp_virtsvc(const char *arg); +#endif + + +static inline void dp_argparse_help(const char *progname, FILE *outfile) +{ + fprintf(outfile, "Usage: %s [options]\n" + " -h, --help display this help and exit\n" + " -v, --version display version and exit\n" + " --pf0=IFNAME first physical interface (e.g. eth0)\n" + " --pf1=IFNAME second physical interface (e.g. eth1)\n" + " --ipv6=ADDR6 IPv6 underlay address\n" + " --vf-pattern=PATTERN virtual interface name pattern (e.g. 'eth1vf')\n" + " --dhcp-mtu=SIZE set the mtu field in DHCP responses (68 - 1500)\n" + " --dhcp-dns=IPv4 set the domain name server field in DHCP responses (can be used multiple times)\n" +#ifdef ENABLE_VIRTSVC + " --udp-virtsvc=IPv4,port,IPv6,port map a VM-accessible IPv4 endpoint to an outside IPv6 UDP service\n" +#endif +#ifdef ENABLE_VIRTSVC + " --tcp-virtsvc=IPv4,port,IPv6,port map a VM-accessible IPv4 endpoint to an outside IPv6 TCP service\n" +#endif + " --wcmp=PERCENTAGE weighted-cost-multipath percentage for pf0 (0 - 100)\n" + " --nic-type=NICTYPE NIC type to use: 'hw' (default) or 'tap'\n" + " --no-stats do not print periodic statistics to stdout\n" + " --no-conntrack disable connection tracking\n" + " --enable-ipv6-overlay enable IPv6 overlay addresses\n" + " --no-offload disable traffic offloading\n" +#ifdef ENABLE_PYTEST + " --graphtrace-loglevel=LEVEL verbosity level of packet traversing the graph framework\n" +#endif + " --color=MODE output colorization mode: 'never' (default), 'always' or 'auto'\n" + " --log-format=FORMAT set the format of individual log lines (on standard output): 'text' (default) or 'json'\n" + " --grpc-port=PORT listen for gRPC clients on this port\n" +#ifdef ENABLE_PYTEST + " --flow-timeout=SECONDS inactive flow timeout (except TCP established flows)\n" +#endif + , progname); +} + +static int dp_conf_parse_arg(int opt, const char *arg) +{ + (void)arg; // if no option uses an argument, this would be unused + switch (opt) { + case OPT_PF0: + return dp_argparse_string(arg, pf0_name, ARRAY_SIZE(pf0_name)); + case OPT_PF1: + return dp_argparse_string(arg, pf1_name, ARRAY_SIZE(pf1_name)); + case OPT_IPV6: + return dp_argparse_opt_ipv6(arg); + case OPT_VF_PATTERN: + return dp_argparse_string(arg, vf_pattern, ARRAY_SIZE(vf_pattern)); + case OPT_DHCP_MTU: + return dp_argparse_int(arg, &dhcp_mtu, 68, 1500); + case OPT_DHCP_DNS: + return dp_argparse_opt_dhcp_dns(arg); +#ifdef ENABLE_VIRTSVC + case OPT_UDP_VIRTSVC: + return dp_argparse_opt_udp_virtsvc(arg); +#endif +#ifdef ENABLE_VIRTSVC + case OPT_TCP_VIRTSVC: + return dp_argparse_opt_tcp_virtsvc(arg); +#endif + case OPT_WCMP: + return dp_argparse_int(arg, &wcmp_perc, 0, 100); + case OPT_NIC_TYPE: + return dp_argparse_enum(arg, (int *)&nic_type, nic_type_choices, ARRAY_SIZE(nic_type_choices)); + case OPT_NO_STATS: + return dp_argparse_store_false(&stats_enabled); + case OPT_NO_CONNTRACK: + return dp_argparse_store_false(&conntrack_enabled); + case OPT_ENABLE_IPV6_OVERLAY: + return dp_argparse_store_true(&ipv6_overlay_enabled); + case OPT_NO_OFFLOAD: + return dp_argparse_store_false(&offload_enabled); +#ifdef ENABLE_PYTEST + case OPT_GRAPHTRACE_LOGLEVEL: + return dp_argparse_int(arg, &graphtrace_loglevel, 0, DP_GRAPHTRACE_LOGLEVEL_MAX); +#endif + case OPT_COLOR: + return dp_argparse_enum(arg, (int *)&color, color_choices, ARRAY_SIZE(color_choices)); + case OPT_LOG_FORMAT: + return dp_argparse_enum(arg, (int *)&log_format, log_format_choices, ARRAY_SIZE(log_format_choices)); + case OPT_GRPC_PORT: + return dp_argparse_int(arg, &grpc_port, 1024, 65535); +#ifdef ENABLE_PYTEST + case OPT_FLOW_TIMEOUT: + return dp_argparse_int(arg, &flow_timeout, 1, 300); +#endif + default: + fprintf(stderr, "Unimplemented option %d\n", opt); + return DP_ERROR; + } +} + +enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv) +{ + const char *progname = argv[0]; + int option_index = -1; + int opt; + + while ((opt = getopt_long(argc, argv, OPTSTRING, dp_conf_longopts, &option_index)) != -1) { + switch (opt) { + case OPT_HELP: + dp_argparse_help(progname, stdout); + return DP_CONF_RUNMODE_EXIT; + case OPT_VERSION: + dp_argparse_version(); + return DP_CONF_RUNMODE_EXIT; + case ':': + fprintf(stderr, "Missing argument for '%s'\n", argv[optind-1]); + return DP_CONF_RUNMODE_ERROR; + case '?': + if (optopt > 0) + fprintf(stderr, "Unknown option '-%c'\n", optopt); + else + fprintf(stderr, "Unknown option '%s'\n", argv[optind-1]); + return DP_CONF_RUNMODE_ERROR; + default: + if (DP_FAILED(dp_conf_parse_arg(opt, optarg))) { + if (option_index >= 0) + fprintf(stderr, "Invalid argument for '--%s'\n", dp_conf_longopts[option_index].name); + else + fprintf(stderr, "Invalid argument for '-%c'\n", opt); + return DP_CONF_RUNMODE_ERROR; + } + } + option_index = -1; + } + return DP_CONF_RUNMODE_NORMAL; +} + diff --git a/src/dp_flow.c b/src/dp_flow.c index 2e133c9e1..8876034af 100644 --- a/src/dp_flow.c +++ b/src/dp_flow.c @@ -3,6 +3,7 @@ #include #include "dp_cntrack.h" +#include "dp_conf.h" #include "dp_error.h" #include "dp_log.h" #include "dp_lpm.h" diff --git a/src/dp_lpm.c b/src/dp_lpm.c index b3750818d..2102ce0bb 100644 --- a/src/dp_lpm.c +++ b/src/dp_lpm.c @@ -1,5 +1,6 @@ #include "dp_lpm.h" #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_firewall.h" #include "dp_flow.h" diff --git a/src/dp_multi_path.c b/src/dp_multi_path.c index dabc1f71f..3b36c475d 100644 --- a/src/dp_multi_path.c +++ b/src/dp_multi_path.c @@ -21,13 +21,12 @@ void dp_multipath_init(void) if (!dp_conf_is_wcmp_enabled()) return; - double frac = dp_conf_get_wcmp_frac(); - int round_frac = RTE_MIN(lround(frac * 10), PORT_SELECT_TABLE_SIZE); + int frac = (int)lround(dp_conf_get_wcmp_perc() / (100.0/PORT_SELECT_TABLE_SIZE)); - for (int i = 0; i < round_frac; ++i) + for (int i = 0; i < frac; ++i) pf0_egress_select_table[i] = OWNER_PORT; - for (int i = round_frac; i < PORT_SELECT_TABLE_SIZE; ++i) + for (int i = frac; i < PORT_SELECT_TABLE_SIZE; ++i) pf0_egress_select_table[i] = PEER_PORT; } diff --git a/src/dp_port.c b/src/dp_port.c index 2f538e606..71b389072 100644 --- a/src/dp_port.c +++ b/src/dp_port.c @@ -1,5 +1,6 @@ #include "dp_error.h" #include +#include "dp_conf.h" #include "dp_hairpin.h" #include "dp_log.h" #include "dp_lpm.h" diff --git a/src/dp_service.c b/src/dp_service.c index 3d3ed219a..0e2edd96a 100644 --- a/src/dp_service.c +++ b/src/dp_service.c @@ -255,7 +255,6 @@ int main(int argc, char **argv) { int retval = EXIT_SUCCESS; int eal_argcount; - enum dp_conf_runmode runmode; // Read the config file first because it can contain EAL arguments // (those need to be injected *before* rte_eal_init()) @@ -268,8 +267,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - runmode = dp_conf_parse_args(argc - eal_argcount, argv + eal_argcount); - switch (runmode) { + switch (dp_conf_parse_args(argc - eal_argcount, argv + eal_argcount)) { case DP_CONF_RUNMODE_ERROR: retval = EXIT_FAILURE; break; diff --git a/src/dp_util.c b/src/dp_util.c index 6032aeffd..cd2dc7cb2 100644 --- a/src/dp_util.c +++ b/src/dp_util.c @@ -2,9 +2,10 @@ #include -#include "rte_flow/dp_rte_flow.h" -#include "dp_log.h" +#include "dp_conf.h" #include "dp_error.h" +#include "dp_log.h" +#include "rte_flow/dp_rte_flow.h" #define DP_SYSFS_PREFIX_MLX_VF_COUNT "/sys/class/net/" #define DP_SYSFS_SUFFIX_MLX_VF_COUNT "/device/sriov_numvfs" diff --git a/src/grpc/dp_grpc_impl.c b/src/grpc/dp_grpc_impl.c index 16dc006f0..a78410a70 100644 --- a/src/grpc/dp_grpc_impl.c +++ b/src/grpc/dp_grpc_impl.c @@ -1,5 +1,6 @@ #include "grpc/dp_grpc_impl.h" #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_flow.h" #include "dp_lb.h" diff --git a/src/meson.build b/src/meson.build index 0bdbba6e6..1b75b7bf2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -34,6 +34,7 @@ dp_sources = [ 'rte_flow/dp_rte_flow.c', 'rte_flow/dp_rte_flow_init.c', 'rte_flow/dp_rte_flow_traffic_forward.c', + 'dp_argparse.c', 'dp_conf.c', 'dp_error.c', 'dp_firewall.c', diff --git a/src/monitoring/dp_graphtrace.c b/src/monitoring/dp_graphtrace.c index 86c8fcea7..fc2cceacc 100644 --- a/src/monitoring/dp_graphtrace.c +++ b/src/monitoring/dp_graphtrace.c @@ -2,6 +2,7 @@ #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_log.h" #include "dpdk_layer.h" diff --git a/src/nodes/conntrack_node.c b/src/nodes/conntrack_node.c index a79c2d404..6c6fa8b32 100644 --- a/src/nodes/conntrack_node.c +++ b/src/nodes/conntrack_node.c @@ -2,6 +2,7 @@ #include #include #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_flow.h" #include "dp_log.h" diff --git a/src/nodes/dhcp_node.c b/src/nodes/dhcp_node.c index 9352e5c97..b4cf657fa 100644 --- a/src/nodes/dhcp_node.c +++ b/src/nodes/dhcp_node.c @@ -7,6 +7,7 @@ #include #include #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_log.h" #include "dp_lpm.h" diff --git a/src/nodes/ipv6_lookup_node.c b/src/nodes/ipv6_lookup_node.c index 101f9bce3..01cc9a573 100644 --- a/src/nodes/ipv6_lookup_node.c +++ b/src/nodes/ipv6_lookup_node.c @@ -3,6 +3,7 @@ #include #include #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_lpm.h" #include "dp_mbuf_dyn.h" diff --git a/src/nodes/ipv6_nd_node.c b/src/nodes/ipv6_nd_node.c index 396bf5df1..5962727d5 100644 --- a/src/nodes/ipv6_nd_node.c +++ b/src/nodes/ipv6_nd_node.c @@ -3,6 +3,7 @@ #include #include #include +#include "dp_conf.h" #include "dp_lpm.h" #include "dp_port.h" #include "nodes/common_node.h" diff --git a/src/nodes/virtsvc_node.c b/src/nodes/virtsvc_node.c index 7460be4bc..b504d656c 100644 --- a/src/nodes/virtsvc_node.c +++ b/src/nodes/virtsvc_node.c @@ -5,6 +5,7 @@ #include #include #include +#include "dp_conf.h" #include "dp_error.h" #include "dp_log.h" #include "dp_mbuf_dyn.h" diff --git a/src/rte_flow/dp_rte_flow_traffic_forward.c b/src/rte_flow/dp_rte_flow_traffic_forward.c index 2f460314a..ebfff95e5 100644 --- a/src/rte_flow/dp_rte_flow_traffic_forward.c +++ b/src/rte_flow/dp_rte_flow_traffic_forward.c @@ -1,4 +1,5 @@ #include "rte_flow/dp_rte_flow_traffic_forward.h" +#include "dp_conf.h" #include "dp_error.h" #include "dp_log.h" #include "dp_lpm.h" diff --git a/test/dp_service.py b/test/dp_service.py index 0c3e6453f..76e07f694 100755 --- a/test/dp_service.py +++ b/test/dp_service.py @@ -56,7 +56,7 @@ def __init__(self, build_path, port_redundancy, fast_flow_timeout, self.cmd += ' --no-offload' if self.port_redundancy: - self.cmd += ' --wcmp-fraction=0.5' + self.cmd += ' --wcmp=50' if fast_flow_timeout: self.cmd += f' --flow-timeout={flow_timeout}' if test_virtsvc: diff --git a/test/test_flows.py b/test/test_flows.py index 75e5f795e..0e9409293 100644 --- a/test/test_flows.py +++ b/test/test_flows.py @@ -20,7 +20,10 @@ def tcp_server_nat_pkt_check(pkt): assert pkt[TCP].sport == nat_only_port, \ "Failed to use NAT's only single port" -def test_nat_table_flush(prepare_ipv4, grpc_client): +def test_nat_table_flush(prepare_ipv4, grpc_client, port_redundancy): + + if port_redundancy: + pytest.skip("Port redundancy is not supported for ipv6 in ipv6") global nat_only_port @@ -49,7 +52,10 @@ def send_bounce_pkt_to_pf(ipv6_nat): TCP(sport=8989, dport=510)) delayed_sendp(bouce_pkt, PF0.tap) -def test_neighnat_table_flush(prepare_ipv4, grpc_client): +def test_neighnat_table_flush(prepare_ipv4, grpc_client, port_redundancy): + + if port_redundancy: + pytest.skip("Port redundancy is not supported for ipv6 in ipv6") nat_ul_ipv6 = grpc_client.addnat(VM1.name, nat_vip, nat_local_min_port, nat_local_max_port) diff --git a/tools/dump/dp_conf.json b/tools/dump/dp_conf.json index 2767b253c..3b7aed0e2 100644 --- a/tools/dump/dp_conf.json +++ b/tools/dump/dp_conf.json @@ -1,40 +1,35 @@ -[ - { - "shopt": "h", - "lgopt": "help", - "help": "display this help and exit" - }, - { - "shopt": "v", - "lgopt": "version", - "help": "display dp-service version and exit" - }, - { - "lgopt": "drops", - "help": "show dropped packets", - "var": "showing_drops", - "type": "bool", - "default": "false" - }, - { - "lgopt": "nodes", - "help": "show graph node traversal", - "var": "showing_nodes", - "type": "bool", - "default": "false" - }, - { - "lgopt": "hw", - "help": "capture offloaded packets (only outgoing VF->PF packets supported)", - "var": "offload_enabled", - "type": "bool", - "default": "false" - }, - { - "lgopt": "stop", - "help": "do nothing, only make sure tracing is disabled in dp-service", - "var": "stop_mode", - "type": "bool", - "default": "false" - } -] +{ + "header": "opts.h", + "source": "opts.c", + "markdown": "../../docs/deployment/help_dpservice-dump.md", + "options": [ + { + "lgopt": "drops", + "help": "show dropped packets", + "var": "showing_drops", + "type": "bool", + "default": "false" + }, + { + "lgopt": "nodes", + "help": "show graph node traversal", + "var": "showing_nodes", + "type": "bool", + "default": "false" + }, + { + "lgopt": "hw", + "help": "capture offloaded packets (only outgoing VF->PF packets supported)", + "var": "offload_enabled", + "type": "bool", + "default": "false" + }, + { + "lgopt": "stop", + "help": "do nothing, only make sure tracing is disabled in dp-service", + "var": "stop_mode", + "type": "bool", + "default": "false" + } + ] +} diff --git a/tools/dump/main.c b/tools/dump/main.c index b6a39fc29..92c6d69e2 100644 --- a/tools/dump/main.c +++ b/tools/dump/main.c @@ -297,55 +297,9 @@ static int stop_graphtrace(void) return DP_OK; } -static inline void print_usage(const char *progname, FILE *outfile) +static void dp_argparse_version(void) { - fprintf(outfile, "Usage: %s [options]\n", progname); - print_help_args(outfile); -} - -static int parse_opt(int opt, __rte_unused const char *arg) -{ - switch (opt) { - case OPT_DROPS: - showing_drops = true; - return DP_OK; - case OPT_NODES: - showing_nodes = true; - return DP_OK; - case OPT_HW: - offload_enabled = true; - return DP_OK; - case OPT_STOP: - stop_mode = true; - return DP_OK; - default: - fprintf(stderr, "Unimplemented option %d", opt); - return DP_ERROR; - } -} - -enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv) -{ - const char *progname = argv[0]; - int opt; - - while ((opt = getopt_long(argc, argv, OPTSTRING, longopts, NULL)) != -1) { - switch (opt) { - case OPT_HELP: - print_usage(progname, stdout); - return DP_CONF_RUNMODE_EXIT; - case OPT_VERSION: - printf("DP Service version %s\n", DP_SERVICE_VERSION); - return DP_CONF_RUNMODE_EXIT; - case '?': - print_usage(progname, stderr); - return DP_CONF_RUNMODE_ERROR; - default: - if (DP_FAILED(parse_opt(opt, optarg))) - return DP_CONF_RUNMODE_ERROR; - } - } - return DP_CONF_RUNMODE_NORMAL; + printf("DP Service version %s\n", DP_SERVICE_VERSION); } int main(int argc, char **argv) diff --git a/tools/dump/opts.c b/tools/dump/opts.c index fa45c9fc5..9b32c34b2 100644 --- a/tools/dump/opts.c +++ b/tools/dump/opts.c @@ -5,6 +5,12 @@ /* Please edit dp_conf.json and re-run the script to update this file. */ /***********************************************************************/ +#include "dp_argparse.h" + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof((ARRAY)[0])) +#endif + enum { OPT_HELP = 'h', OPT_VERSION = 'v', @@ -15,11 +21,9 @@ _OPT_SHOPT_MAX = 255, OPT_STOP, }; -#define OPTSTRING \ - "h" \ - "v" \ +#define OPTSTRING ":hv" \ -static const struct option longopts[] = { +static const struct option dp_conf_longopts[] = { { "help", 0, 0, OPT_HELP }, { "version", 0, 0, OPT_VERSION }, { "drops", 0, 0, OPT_DROPS }, @@ -29,18 +33,6 @@ static const struct option longopts[] = { { NULL, 0, 0, 0 } }; -static void print_help_args(FILE *outfile) -{ - fprintf(outfile, "%s", - " -h, --help display this help and exit\n" - " -v, --version display dp-service version and exit\n" - " --drops show dropped packets\n" - " --nodes show graph node traversal\n" - " --hw capture offloaded packets (only outgoing VF->PF packets supported)\n" - " --stop do nothing, only make sure tracing is disabled in dp-service\n" - ); -} - static bool showing_drops = false; static bool showing_nodes = false; static bool offload_enabled = false; @@ -66,3 +58,76 @@ bool dp_conf_is_stop_mode(void) return stop_mode; } + + +/* These functions need to be implemented by the user of this generated code */ +static void dp_argparse_version(void); + + +static inline void dp_argparse_help(const char *progname, FILE *outfile) +{ + fprintf(outfile, "Usage: %s [options]\n" + " -h, --help display this help and exit\n" + " -v, --version display version and exit\n" + " --drops show dropped packets\n" + " --nodes show graph node traversal\n" + " --hw capture offloaded packets (only outgoing VF->PF packets supported)\n" + " --stop do nothing, only make sure tracing is disabled in dp-service\n" + , progname); +} + +static int dp_conf_parse_arg(int opt, const char *arg) +{ + (void)arg; // if no option uses an argument, this would be unused + switch (opt) { + case OPT_DROPS: + return dp_argparse_store_true(&showing_drops); + case OPT_NODES: + return dp_argparse_store_true(&showing_nodes); + case OPT_HW: + return dp_argparse_store_true(&offload_enabled); + case OPT_STOP: + return dp_argparse_store_true(&stop_mode); + default: + fprintf(stderr, "Unimplemented option %d\n", opt); + return DP_ERROR; + } +} + +enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv) +{ + const char *progname = argv[0]; + int option_index = -1; + int opt; + + while ((opt = getopt_long(argc, argv, OPTSTRING, dp_conf_longopts, &option_index)) != -1) { + switch (opt) { + case OPT_HELP: + dp_argparse_help(progname, stdout); + return DP_CONF_RUNMODE_EXIT; + case OPT_VERSION: + dp_argparse_version(); + return DP_CONF_RUNMODE_EXIT; + case ':': + fprintf(stderr, "Missing argument for '%s'\n", argv[optind-1]); + return DP_CONF_RUNMODE_ERROR; + case '?': + if (optopt > 0) + fprintf(stderr, "Unknown option '-%c'\n", optopt); + else + fprintf(stderr, "Unknown option '%s'\n", argv[optind-1]); + return DP_CONF_RUNMODE_ERROR; + default: + if (DP_FAILED(dp_conf_parse_arg(opt, optarg))) { + if (option_index >= 0) + fprintf(stderr, "Invalid argument for '--%s'\n", dp_conf_longopts[option_index].name); + else + fprintf(stderr, "Invalid argument for '-%c'\n", opt); + return DP_CONF_RUNMODE_ERROR; + } + } + option_index = -1; + } + return DP_CONF_RUNMODE_NORMAL; +} + diff --git a/tools/dump/opts.h b/tools/dump/opts.h index 671b37d35..0432754da 100644 --- a/tools/dump/opts.h +++ b/tools/dump/opts.h @@ -9,3 +9,11 @@ bool dp_conf_is_showing_drops(void); bool dp_conf_is_showing_nodes(void); bool dp_conf_is_offload_enabled(void); bool dp_conf_is_stop_mode(void); + +enum dp_conf_runmode { + DP_CONF_RUNMODE_NORMAL, /**< Start normally */ + DP_CONF_RUNMODE_EXIT, /**< End succesfully (e.g. for --help etc.) */ + DP_CONF_RUNMODE_ERROR, /**< Error parsing arguments */ +}; + +enum dp_conf_runmode dp_conf_parse_args(int argc, char **argv);