diff options
Diffstat (limited to 'test/packetdrill/config.c')
-rw-r--r-- | test/packetdrill/config.c | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/test/packetdrill/config.c b/test/packetdrill/config.c new file mode 100644 index 0000000..37e2eb0 --- /dev/null +++ b/test/packetdrill/config.c @@ -0,0 +1,605 @@ +/* + * Copyright 2013 Google Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +/* + * Author: ncardwell@google.com (Neal Cardwell) + * + * Helper functions for configuration information for a test run. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#include "config.h" +#include "logging.h" +#include "ip_prefix.h" + +/* For the sake of clarity, we require long option names, e.g. --foo, + * for all options except -v. + */ +enum option_codes { + OPT_IP_VERSION = 256, + OPT_BIND_PORT, + OPT_CODE_COMMAND, + OPT_CODE_FORMAT, + OPT_CODE_SOCKOPT, + OPT_CONNECT_PORT, + OPT_REMOTE_IP, + OPT_LOCAL_IP, + OPT_GATEWAY_IP, + OPT_NETMASK_IP, + OPT_SPEED, + OPT_MSS, + OPT_MTU, + OPT_INIT_SCRIPTS, + OPT_TOLERANCE_USECS, + OPT_WIRE_CLIENT, + OPT_WIRE_SERVER, + OPT_WIRE_SERVER_IP, + OPT_WIRE_SERVER_PORT, + OPT_WIRE_CLIENT_DEV, + OPT_WIRE_SERVER_DEV, + OPT_SO_FILENAME, + OPT_SO_FLAGS, + OPT_TCP_TS_ECR_SCALED, + OPT_TCP_TS_TICK_USECS, + OPT_STRICT_SEGMENTS, + OPT_NON_FATAL, + OPT_DRY_RUN, + OPT_IS_ANYIP, + OPT_SEND_OMIT_FREE, + OPT_DEFINE = 'D', /* a '-D' single-letter option */ + OPT_VERBOSE = 'v', /* a '-v' single-letter option */ +}; + +/* Specification of command line options for getopt_long(). */ +struct option options[] = { + { "ip_version", .has_arg = true, NULL, OPT_IP_VERSION }, + { "bind_port", .has_arg = true, NULL, OPT_BIND_PORT }, + { "code_command", .has_arg = true, NULL, OPT_CODE_COMMAND }, + { "code_format", .has_arg = true, NULL, OPT_CODE_FORMAT }, + { "code_sockopt", .has_arg = true, NULL, OPT_CODE_SOCKOPT }, + { "connect_port", .has_arg = true, NULL, OPT_CONNECT_PORT }, + { "remote_ip", .has_arg = true, NULL, OPT_REMOTE_IP }, + { "local_ip", .has_arg = true, NULL, OPT_LOCAL_IP }, + { "gateway_ip", .has_arg = true, NULL, OPT_GATEWAY_IP }, + { "netmask_ip", .has_arg = true, NULL, OPT_NETMASK_IP }, + { "speed", .has_arg = true, NULL, OPT_SPEED }, + { "mss", .has_arg = true, NULL, OPT_MSS }, + { "mtu", .has_arg = true, NULL, OPT_MTU }, + { "init_scripts", .has_arg = true, NULL, OPT_INIT_SCRIPTS }, + { "tolerance_usecs", .has_arg = true, NULL, OPT_TOLERANCE_USECS }, + { "wire_client", .has_arg = false, NULL, OPT_WIRE_CLIENT }, + { "wire_server", .has_arg = false, NULL, OPT_WIRE_SERVER }, + { "wire_server_ip", .has_arg = true, NULL, OPT_WIRE_SERVER_IP }, + { "wire_server_port", .has_arg = true, NULL, OPT_WIRE_SERVER_PORT }, + { "wire_client_dev", .has_arg = true, NULL, OPT_WIRE_CLIENT_DEV }, + { "wire_server_dev", .has_arg = true, NULL, OPT_WIRE_SERVER_DEV }, + { "so_filename", .has_arg = true, NULL, OPT_SO_FILENAME }, + { "so_flags", .has_arg = true, NULL, OPT_SO_FLAGS }, + { "tcp_ts_ecr_scaled", .has_arg = false, NULL, OPT_TCP_TS_ECR_SCALED }, + { "tcp_ts_tick_usecs", .has_arg = true, NULL, OPT_TCP_TS_TICK_USECS }, + { "strict_segments", .has_arg = false, NULL, OPT_STRICT_SEGMENTS }, + { "non_fatal", .has_arg = true, NULL, OPT_NON_FATAL }, + { "dry_run", .has_arg = false, NULL, OPT_DRY_RUN }, + { "is_anyip", .has_arg = false, NULL, OPT_IS_ANYIP }, + { "send_omit_free", .has_arg = false, NULL, OPT_SEND_OMIT_FREE }, + { "define", .has_arg = true, NULL, OPT_DEFINE }, + { "verbose", .has_arg = false, NULL, OPT_VERBOSE }, + { NULL }, +}; + +void show_usage(void) +{ + fprintf(stderr, "Usage: packetdrill\n" + "\t[--ip_version=[ipv4,ipv4-mapped-ipv6,ipv6]]\n" + "\t[--bind_port=bind_port]\n" + "\t[--code_command=code_command]\n" + "\t[--code_format=code_format]\n" + "\t[--code_sockopt=TCP_INFO]\n" + "\t[--connect_port=connect_port]\n" + "\t[--remote_ip=remote_ip]\n" + "\t[--local_ip=local_ip]\n" + "\t[--gateway_ip=gateway_ip]\n" + "\t[--netmask_ip=netmask_ip]\n" + "\t[--init_scripts=<comma separated filenames>\n" + "\t[--speed=<speed in Mbps>\n" + "\t[--mss=<MSS in bytes>\n" + "\t[--mtu=<MTU in bytes>\n" + "\t[--tolerance_usecs=tolerance_usecs]\n" + "\t[--tcp_ts_ecr_scaled]\n" + "\t[--tcp_ts_tick_usecs=<microseconds per TCP TS val tick>]\n" + "\t[--strict_segments]\n" + "\t[--non_fatal=<comma separated types: packet,syscall>]\n" + "\t[--wire_client]\n" + "\t[--wire_server]\n" + "\t[--wire_server_ip=<server_ipv4_address>]\n" + "\t[--wire_server_port=<server_port>]\n" + "\t[--wire_client_dev=<eth_dev_name>]\n" + "\t[--wire_server_dev=<eth_dev_name>]\n" + "\t[--so_filename=<filename>]\n" + "\t[--so_flags=<flags passed to SO init function>]\n" + "\t[--dry_run]\n" + "\t[--is_anyip]\n" + "\t[--send_omit_free]\n" + "\t[--define symbol1=val1 --define symbol2=val2 ...]\n" + "\t[--verbose|-v]\n" + "\tscript_path ...\n"); +} + +/* Address Configuration for IPv4 + * + * For IPv4, we use the 192.168.0.0/16 RFC 1918 private IP space for + * our tun interface. To avoid accidents and confusion we want remote + * addresses to be permanently unallocated addresses outside of the + * private/unroutable RFC 1918 ranges (kernel code can behave + * differently for private addresses). So for remote addresses we use + * the 192.0.2.0/24 TEST-NET-1 range (see RFC 5737). + * + * Summary for IPv4: + * - local address: 192.168.0.0/16 private IP space (RFC 1918) + * - remote address: 192.0.2.0/24 TEST-NET-1 range (RFC 5737) + */ + +#define DEFAULT_V4_LIVE_REMOTE_IP_STRING "192.0.2.1/24" +#define DEFAULT_V4_LIVE_LOCAL_IP_STRING "192.168.0.0" +/* Note : generate_random_ipv4_addr() assumes the gateway is .1 + */ +#define DEFAULT_V4_LIVE_GATEWAY_IP_STRING "192.168.0.1" +#define DEFAULT_V4_LIVE_NETMASK_IP_STRING "255.255.0.0" + +/* Address Configuration for IPv6 + * + * For IPv6 we use a ULA (unique local address) for our local (tun) + * interface, and the RFC 3849 documentation space for our remote + * address. + * + * Summary for IPv6: + * - local address: fd3d:fa7b:d17d::/48 in unique local address space (RFC 4193) + * - remote address: 2001:DB8::/32 documentation prefix (RFC 3849) + */ + +#define DEFAULT_V6_LIVE_REMOTE_IP_STRING "2001:DB8::1/32" +#define DEFAULT_V6_LIVE_LOCAL_IP_STRING "fd3d:fa7b:d17d::0" +#define DEFAULT_V6_LIVE_GATEWAY_IP_STRING "fd3d:fa7b:d17d:8888::0" +#define DEFAULT_V6_LIVE_PREFIX_LEN 48 + +/* Fill in any as-yet-unspecified IP address attributes using IPv4 defaults. */ +static void set_ipv4_defaults(struct config *config) +{ + if (strlen(config->live_remote_ip_string) == 0) + strcpy(config->live_remote_ip_string, + DEFAULT_V4_LIVE_REMOTE_IP_STRING); + if (strlen(config->live_netmask_ip_string) == 0) + strcpy(config->live_netmask_ip_string, + DEFAULT_V4_LIVE_NETMASK_IP_STRING); + if (strlen(config->live_local_ip_string) == 0) + generate_random_ipv4_addr(config->live_local_ip_string, + DEFAULT_V4_LIVE_LOCAL_IP_STRING, + config->live_netmask_ip_string); + if (strlen(config->live_gateway_ip_string) == 0) + strcpy(config->live_gateway_ip_string, + DEFAULT_V4_LIVE_GATEWAY_IP_STRING); +} + +/* Fill in any as-yet-unspecified IP address attributes using IPv6 defaults. */ +static void set_ipv6_defaults(struct config *config) +{ + if (strlen(config->live_remote_ip_string) == 0) + strcpy(config->live_remote_ip_string, + DEFAULT_V6_LIVE_REMOTE_IP_STRING); + if (strlen(config->live_local_ip_string) == 0) + generate_random_ipv6_addr(config->live_local_ip_string, + DEFAULT_V6_LIVE_LOCAL_IP_STRING, + DEFAULT_V6_LIVE_PREFIX_LEN); + if (strlen(config->live_gateway_ip_string) == 0) + strcpy(config->live_gateway_ip_string, + DEFAULT_V6_LIVE_GATEWAY_IP_STRING); +} + +/* Set default configuration before we begin parsing. */ +void set_default_config(struct config *config) +{ + memset(config, 0, sizeof(*config)); + config->code_command_line = "/usr/bin/python"; + config->code_format = "python"; + config->code_sockopt = ""; /* auto-detect */ + config->ip_version = IP_VERSION_4; + config->live_bind_port = 8080; + config->live_connect_port = 8080; + config->tolerance_usecs = 4000; + config->speed = TUN_DRIVER_SPEED_CUR; + config->mtu = TUN_DRIVER_DEFAULT_MTU; + + config->tcp_ts_ecr_scaled = false; + + /* For now, by default we disable checks of outbound TS val + * values, since there are timestamp val bugs in the tests and + * kernel. TODO(ncardwell): Switch default tcp_ts_tick_usecs + * to 1000 when TCP timestamp val bugs have been eradicated + * from kernel and tests. + */ + config->tcp_ts_tick_usecs = 0; /* disable checks of TS val */ + + config->live_remote_ip_string[0] = '\0'; + config->live_local_ip_string[0] = '\0'; + config->live_gateway_ip_string[0] = '\0'; + config->live_netmask_ip_string[0] = '\0'; + + config->init_scripts = NULL; + + config->wire_server_port = 8081; + config->wire_client_device = "eth0"; + config->wire_server_device = "eth0"; +} + +static void set_remote_ip_and_prefix(struct config *config) +{ + config->live_remote_ip = config->live_remote_prefix.ip; + ip_to_string(&config->live_remote_ip, + config->live_remote_ip_string); + + ip_prefix_normalize(&config->live_remote_prefix); + ip_prefix_to_string(&config->live_remote_prefix, + config->live_remote_prefix_string); +} + +/* Here's a table summarizing the types of various entities in the + * different flavors of IP that we support: + * + * flavor socket_domain bind/connect/accept IP local/remote IP + * -------- ------------- ------------------------- --------------- + * 4 AF_INET AF_INET AF_INET + * 4-mapped-6 AF_INET6 AF_INET6 mapped from IPv4 AF_INET + * 6 AF_INET6 AF_INET6 AF_INET6 + */ + +/* Calculate final configuration values needed for IPv4 */ +static void finalize_ipv4_config(struct config *config) +{ + set_ipv4_defaults(config); + + config->live_local_ip = ipv4_parse(config->live_local_ip_string); + + config->live_remote_prefix = + ipv4_prefix_parse(config->live_remote_ip_string); + set_remote_ip_and_prefix(config); + + config->live_prefix_len = + netmask_to_prefix(config->live_netmask_ip_string); + config->live_gateway_ip = ipv4_parse(config->live_gateway_ip_string); + config->live_bind_ip = config->live_local_ip; + config->live_connect_ip = config->live_remote_ip; + config->socket_domain = AF_INET; + config->wire_protocol = AF_INET; +} + +/* Calculate final configuration values needed for ipv4-mapped-ipv6 */ +static void finalize_ipv4_mapped_ipv6_config(struct config *config) +{ + set_ipv4_defaults(config); + + config->live_local_ip = ipv4_parse(config->live_local_ip_string); + + config->live_remote_prefix = + ipv4_prefix_parse(config->live_remote_ip_string); + set_remote_ip_and_prefix(config); + + config->live_prefix_len = + netmask_to_prefix(config->live_netmask_ip_string); + config->live_gateway_ip = ipv4_parse(config->live_gateway_ip_string); + config->live_bind_ip = ipv6_map_from_ipv4(config->live_local_ip); + config->live_connect_ip = ipv6_map_from_ipv4(config->live_remote_ip); + config->socket_domain = AF_INET6; + config->wire_protocol = AF_INET; +} + +/* Calculate final configuration values needed for IPv6 */ +static void finalize_ipv6_config(struct config *config) +{ + set_ipv6_defaults(config); + + config->live_local_ip = ipv6_parse(config->live_local_ip_string); + + config->live_remote_prefix = + ipv6_prefix_parse(config->live_remote_ip_string); + set_remote_ip_and_prefix(config); + + config->live_prefix_len = DEFAULT_V6_LIVE_PREFIX_LEN; + config->live_gateway_ip = ipv6_parse(config->live_gateway_ip_string); + config->live_bind_ip = config->live_local_ip; + config->live_connect_ip = config->live_remote_ip; + config->socket_domain = AF_INET6; + config->wire_protocol = AF_INET6; +} + +void finalize_config(struct config *config) +{ + assert(config->ip_version >= IP_VERSION_4); + assert(config->ip_version <= IP_VERSION_6); + switch (config->ip_version) { + case IP_VERSION_4: + finalize_ipv4_config(config); + break; + case IP_VERSION_4_MAPPED_6: + finalize_ipv4_mapped_ipv6_config(config); + break; + case IP_VERSION_6: + finalize_ipv6_config(config); + break; + /* omitting default so compiler will catch missing cases */ + } +} + +/* Expect that arg is comma-delimited, allowing for spaces. */ +void parse_non_fatal_arg(char *arg, struct config *config) +{ + char *argdup, *saveptr, *token; + + if (arg == NULL || strlen(arg) == 0) + return; + + argdup = strdup(arg); + token = strtok_r(argdup, ", ", &saveptr); + while (token != NULL) { + if (strcmp(token, "packet") == 0) + config->non_fatal_packet = true; + else if (strcmp(token, "syscall") == 0) + config->non_fatal_syscall = true; + token = strtok_r(NULL, ", ", &saveptr); + } + + free(argdup); +} + + +/* Process a command line option */ +static void process_option(int opt, char *optarg, struct config *config, + char *where) +{ + int port = 0; + char *end = NULL, *equals = NULL, *symbol = NULL, *value = NULL; + unsigned long speed = 0; + + DEBUGP("process_option %d ('%c') = %s\n", + opt, (char)opt, optarg); + + switch (opt) { + case OPT_IP_VERSION: + if (strcmp(optarg, "ipv4") == 0) + config->ip_version = IP_VERSION_4; + else if (strcmp(optarg, "ipv4-mapped-ipv6") == 0) + config->ip_version = IP_VERSION_4_MAPPED_6; + else if (strcmp(optarg, "ipv6") == 0) + config->ip_version = IP_VERSION_6; + else + die("%s: bad --ip_version: %s\n", where, optarg); + break; + case OPT_BIND_PORT: + port = atoi(optarg); + if ((port <= 0) || (port > 0xffff)) + die("%s: bad --bind_port: %s\n", where, optarg); + config->live_bind_port = port; + break; + case OPT_CODE_COMMAND: + config->code_command_line = optarg; + break; + case OPT_CODE_FORMAT: + config->code_format = optarg; + break; + case OPT_CODE_SOCKOPT: + config->code_sockopt = optarg; + break; + case OPT_CONNECT_PORT: + port = atoi(optarg); + if ((port <= 0) || (port > 0xffff)) + die("%s: bad --connect_port: %s\n", where, optarg); + config->live_connect_port = port; + break; + case OPT_REMOTE_IP: + strncpy(config->live_remote_ip_string, optarg, ADDR_STR_LEN-1); + break; + case OPT_LOCAL_IP: + strncpy(config->live_local_ip_string, optarg, ADDR_STR_LEN-1); + break; + case OPT_GATEWAY_IP: + strncpy(config->live_gateway_ip_string, optarg, ADDR_STR_LEN-1); + break; + case OPT_MSS: + config->mss = atoi(optarg); + if (config->mss <= 0) + die("%s: bad --mss: %s\n", where, optarg); + break; + case OPT_MTU: + config->mtu = atoi(optarg); + if (config->mtu < 0) + die("%s: bad --mtu: %s\n", where, optarg); + break; + case OPT_NETMASK_IP: + strncpy(config->live_netmask_ip_string, optarg, ADDR_STR_LEN-1); + break; + case OPT_INIT_SCRIPTS: + config->init_scripts = optarg; + break; + case OPT_NON_FATAL: + parse_non_fatal_arg(optarg, config); + break; + case OPT_SPEED: + speed = strtoul(optarg, &end, 10); + if (end == optarg || *end || !is_valid_u32(speed)) + die("%s: bad --speed: %s\n", where, optarg); + config->speed = speed; + break; + case OPT_TOLERANCE_USECS: + config->tolerance_usecs = atoi(optarg); + if (config->tolerance_usecs <= 0) + die("%s: bad --tolerance_usecs: %s\n", where, optarg); + break; + case OPT_TCP_TS_ECR_SCALED: + config->tcp_ts_ecr_scaled = true; + break; + case OPT_TCP_TS_TICK_USECS: + config->tcp_ts_tick_usecs = atoi(optarg); + if (config->tcp_ts_tick_usecs < 0 || + config->tcp_ts_tick_usecs > 1000000) + die("%s: bad --tcp_ts_tick_usecs: %s\n", where, optarg); + break; + case OPT_STRICT_SEGMENTS: + config->strict_segments = true; + break; + case OPT_WIRE_CLIENT: + config->is_wire_client = true; + break; + case OPT_WIRE_SERVER: + config->is_wire_server = true; + break; + case OPT_WIRE_SERVER_IP: + config->wire_server_ip_string = strdup(optarg); + config->wire_server_ip = + ipv4_parse(config->wire_server_ip_string); + break; + case OPT_WIRE_SERVER_PORT: + port = atoi(optarg); + if ((port <= 0) || (port > 0xffff)) + die("%s: bad --wire_server_port: %s\n", where, optarg); + config->wire_server_port = port; + break; + case OPT_WIRE_CLIENT_DEV: + config->wire_client_device = strdup(optarg); + break; + case OPT_WIRE_SERVER_DEV: + config->wire_server_device = strdup(optarg); + break; + case OPT_SO_FILENAME: + config->so_filename = strdup(optarg); + break; + case OPT_SO_FLAGS: + config->so_flags = strdup(optarg); + break; + case OPT_DRY_RUN: + config->dry_run = true; + break; + case OPT_IS_ANYIP: + config->is_anyip = true; + break; + case OPT_SEND_OMIT_FREE: + config->send_omit_free = true; + break; + case OPT_DEFINE: + equals = strstr(optarg, "="); + if (equals == optarg || equals == NULL) + die("%s: bad definition: %s\n", where, optarg); + symbol = strndup(optarg, equals - optarg); + value = strdup(equals + 1); + definition_set(&config->defines, symbol, value); + break; + case OPT_VERBOSE: + config->verbose = true; + break; + default: + show_usage(); + exit(EXIT_FAILURE); + } +} + + +/* Parse command line options. Returns a pointer to the first argument + * beyond the options. + */ +char **parse_command_line_options(int argc, char *argv[], + struct config *config) +{ + int c = 0; + int i = 0; + + DEBUGP("parse_command_line_options argc=%d\n", argc); + for (i = 0; i < argc; ++i) + DEBUGP("argv[%d] = '%s'\n", i, argv[i]); + + /* Make a copy of our arguments for later, in case we need to + * pass our options to a server. We use argc+1 here because, + * following main() calling conventions, we make the array + * element at argv[argc] a NULL pointer. + */ + config->argv = calloc(argc + 1, sizeof(char *)); + for (i = 0; argv[i]; ++i) + config->argv[i] = strdup(argv[i]); + + /* Parse the arguments. */ + optind = 0; + while ((c = getopt_long(argc, argv, "vD:", options, NULL)) > 0) + process_option(c, optarg, config, "Command Line"); + return argv + optind; +} + +static void parse_script_options(struct config *config, + struct option_list *option_list) +{ + struct option_list *opt = option_list; + while (opt != NULL) { + int i; + int c = 0; + for (i = 0; options[i].name != NULL; i++) { + if (strcmp(options[i].name, opt->name) == 0) { + c = options[i].val; + break; + } + } + + if (!c) + die("%s: option '%s' unknown\n", + config->script_path, opt->name); + if (opt->value && !options[i].has_arg) + die("%s: option '%s' forbids an argument\n", + config->script_path, opt->name); + if (!opt->value && options[i].has_arg) + die("%s: option '%s' requires an argument\n", + config->script_path, opt->name); + + process_option(options[i].val, + opt->value, config, + config->script_path); + + opt = opt->next; + } +} + +/* The parser calls this callback after it finishes parsing all + * --foo=bar options inside the script. At this point we know all + * command line and in-script options, and can finalize our + * configuration. Notably, this allows us to know when we parse a TCP + * packet line in the script whether we should create an IPv4 or IPv6 + * packet. + */ +void parse_and_finalize_config(struct invocation *invocation) +{ + DEBUGP("parse_and_finalize_config\n"); + + /* Parse options in script */ + parse_script_options(invocation->config, + invocation->script->option_list); + + /* Command line options overwrite options in script */ + parse_command_line_options(invocation->argc, invocation->argv, + invocation->config); + + /* Now take care of the last details */ + finalize_config(invocation->config); +} |