diff options
Diffstat (limited to 'conf.c')
| -rw-r--r-- | conf.c | 1351 |
1 files changed, 785 insertions, 566 deletions
@@ -16,6 +16,7 @@ #include <errno.h> #include <fcntl.h> #include <getopt.h> +#include <libgen.h> #include <string.h> #include <sched.h> #include <sys/types.h> @@ -35,6 +36,7 @@ #include <netinet/if_ether.h> #include "util.h" +#include "bitmap.h" #include "ip.h" #include "passt.h" #include "netlink.h" @@ -46,6 +48,10 @@ #include "isolation.h" #include "log.h" #include "vhost_user.h" +#include "epoll_ctl.h" +#include "conf.h" +#include "pesto.h" +#include "serialise.h" #define NETNS_RUN_DIR "/run/netns" @@ -61,314 +67,7 @@ {{{ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, \ 0, 0, 0, 0, 0, 0, 0, 0x01 }}} -const char *pasta_default_ifn = "tap0"; - -/** - * next_chunk - Return the next piece of a string delimited by a character - * @s: String to search - * @c: Delimiter character - * - * Return: If another @c is found in @s, returns a pointer to the - * character *after* the delimiter, if no further @c is in @s, - * return NULL - */ -static char *next_chunk(const char *s, char c) -{ - char *sep = strchr(s, c); - return sep ? sep + 1 : NULL; -} - -/** - * port_range - Represents a non-empty range of ports - * @first: First port number in the range - * @last: Last port number in the range (inclusive) - * - * Invariant: @last >= @first - */ -struct port_range { - in_port_t first, last; -}; - -/** - * parse_port_range() - Parse a range of port numbers '<first>[-<last>]' - * @s: String to parse - * @endptr: Update to the character after the parsed range (similar to - * strtol() etc.) - * @range: Update with the parsed values on success - * - * Return: -EINVAL on parsing error, -ERANGE on out of range port - * numbers, 0 on success - */ -static int parse_port_range(const char *s, char **endptr, - struct port_range *range) -{ - unsigned long first, last; - - last = first = strtoul(s, endptr, 10); - if (*endptr == s) /* Parsed nothing */ - return -EINVAL; - if (**endptr == '-') { /* we have a last value too */ - const char *lasts = *endptr + 1; - last = strtoul(lasts, endptr, 10); - if (*endptr == lasts) /* Parsed nothing */ - return -EINVAL; - } - - if ((last < first) || (last >= NUM_PORTS)) - return -ERANGE; - - range->first = first; - range->last = last; - - return 0; -} - -/** - * conf_ports() - Parse port configuration options, initialise UDP/TCP sockets - * @c: Execution context - * @optname: Short option name, t, T, u, or U - * @optarg: Option argument (port specification) - * @fwd: Pointer to @fwd_ports to be updated - */ -static void conf_ports(const struct ctx *c, char optname, const char *optarg, - struct fwd_ports *fwd) -{ - union inany_addr addr_buf = inany_any6, *addr = &addr_buf; - char buf[BUFSIZ], *spec, *ifname = NULL, *p; - bool exclude_only = true, bound_one = false; - uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; - unsigned i; - int ret; - - if (!strcmp(optarg, "none")) { - if (fwd->mode) - goto mode_conflict; - - fwd->mode = FWD_NONE; - return; - } - - if ((optname == 't' || optname == 'T') && c->no_tcp) - die("TCP port forwarding requested but TCP is disabled"); - if ((optname == 'u' || optname == 'U') && c->no_udp) - die("UDP port forwarding requested but UDP is disabled"); - - if (!strcmp(optarg, "auto")) { - if (fwd->mode) - goto mode_conflict; - - if (c->mode != MODE_PASTA) - die("'auto' port forwarding is only allowed for pasta"); - - fwd->mode = FWD_AUTO; - return; - } - - if (!strcmp(optarg, "all")) { - if (fwd->mode) - goto mode_conflict; - - if (c->mode == MODE_PASTA) - die("'all' port forwarding is only allowed for passt"); - - fwd->mode = FWD_ALL; - - /* Skip port 0. It has special meaning for many socket APIs, so - * trying to bind it is not really safe. - */ - for (i = 1; i < NUM_PORTS; i++) { - if (fwd_port_is_ephemeral(i)) - continue; - - bitmap_set(fwd->map, i); - if (optname == 't') { - ret = tcp_sock_init(c, NULL, NULL, i); - if (ret == -ENFILE || ret == -EMFILE) - goto enfile; - if (!ret) - bound_one = true; - } else if (optname == 'u') { - ret = udp_sock_init(c, 0, NULL, NULL, i); - if (ret == -ENFILE || ret == -EMFILE) - goto enfile; - if (!ret) - bound_one = true; - } - } - - if (!bound_one) - goto bind_all_fail; - - return; - } - - if (fwd->mode > FWD_SPEC) - die("Specific ports cannot be specified together with all/none/auto"); - - fwd->mode = FWD_SPEC; - - strncpy(buf, optarg, sizeof(buf) - 1); - - if ((spec = strchr(buf, '/'))) { - *spec = 0; - spec++; - - if (optname != 't' && optname != 'u') - goto bad; - - if ((ifname = strchr(buf, '%'))) { - *ifname = 0; - ifname++; - - /* spec is already advanced one past the '/', - * so the length of the given ifname is: - * (spec - ifname - 1) - */ - if (spec - ifname - 1 >= IFNAMSIZ) - goto bad; - - } - - if (ifname == buf + 1) { /* Interface without address */ - addr = NULL; - } else { - p = buf; - - /* Allow square brackets for IPv4 too for convenience */ - if (*p == '[' && p[strlen(p) - 1] == ']') { - p[strlen(p) - 1] = '\0'; - p++; - } - - if (!inany_pton(p, addr)) - goto bad; - } - } else { - spec = buf; - - addr = NULL; - } - - /* Mark all exclusions first, they might be given after base ranges */ - p = spec; - do { - struct port_range xrange; - - if (*p != '~') { - /* Not an exclude range, parse later */ - exclude_only = false; - continue; - } - p++; - - if (parse_port_range(p, &p, &xrange)) - goto bad; - if ((*p != '\0') && (*p != ',')) /* Garbage after the range */ - goto bad; - - for (i = xrange.first; i <= xrange.last; i++) { - if (bitmap_isset(exclude, i)) - die("Overlapping excluded ranges %s", optarg); - - bitmap_set(exclude, i); - } - } while ((p = next_chunk(p, ','))); - - if (exclude_only) { - /* Skip port 0. It has special meaning for many socket APIs, so - * trying to bind it is not really safe. - */ - for (i = 1; i < NUM_PORTS; i++) { - if (fwd_port_is_ephemeral(i) || - bitmap_isset(exclude, i)) - continue; - - bitmap_set(fwd->map, i); - - if (optname == 't') { - ret = tcp_sock_init(c, addr, ifname, i); - if (ret == -ENFILE || ret == -EMFILE) - goto enfile; - if (!ret) - bound_one = true; - } else if (optname == 'u') { - ret = udp_sock_init(c, 0, addr, ifname, i); - if (ret == -ENFILE || ret == -EMFILE) - goto enfile; - if (!ret) - bound_one = true; - } else { - /* No way to check in advance for -T and -U */ - bound_one = true; - } - } - - if (!bound_one) - goto bind_all_fail; - - return; - } - - /* Now process base ranges, skipping exclusions */ - p = spec; - do { - struct port_range orig_range, mapped_range; - - if (*p == '~') - /* Exclude range, already parsed */ - continue; - - if (parse_port_range(p, &p, &orig_range)) - goto bad; - - if (*p == ':') { /* There's a range to map to as well */ - if (parse_port_range(p + 1, &p, &mapped_range)) - goto bad; - if ((mapped_range.last - mapped_range.first) != - (orig_range.last - orig_range.first)) - goto bad; - } else { - mapped_range = orig_range; - } - - if ((*p != '\0') && (*p != ',')) /* Garbage after the ranges */ - goto bad; - - for (i = orig_range.first; i <= orig_range.last; i++) { - if (bitmap_isset(fwd->map, i)) - warn( -"Altering mapping of already mapped port number: %s", optarg); - - if (bitmap_isset(exclude, i)) - continue; - - bitmap_set(fwd->map, i); - - fwd->delta[i] = mapped_range.first - orig_range.first; - - ret = 0; - if (optname == 't') - ret = tcp_sock_init(c, addr, ifname, i); - else if (optname == 'u') - ret = udp_sock_init(c, 0, addr, ifname, i); - if (ret) - goto bind_fail; - } - } while ((p = next_chunk(p, ','))); - - return; -enfile: - die("Can't open enough sockets for port specifier: %s", optarg); -bad: - die("Invalid port specifier %s", optarg); -mode_conflict: - die("Port forwarding mode '%s' conflicts with previous mode", optarg); -bind_fail: - die("Failed to bind port %u (%s) for option '-%c %s', exiting", - i, strerror_(-ret), optname, optarg); -bind_all_fail: - die("Failed to bind any port for '-%c %s', exiting", optname, optarg); -} +static const char *pasta_default_ifn = "tap0"; /** * add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration @@ -376,7 +75,7 @@ bind_all_fail: * @addr: Guest nameserver IPv4 address * @idx: Index of free entry in array of IPv4 resolvers * - * Return: Number of entries added (0 or 1) + * Return: number of entries added (0 or 1) */ static unsigned add_dns4(struct ctx *c, const struct in_addr *addr, unsigned idx) @@ -394,7 +93,7 @@ static unsigned add_dns4(struct ctx *c, const struct in_addr *addr, * @addr: Guest nameserver IPv6 address * @idx: Index of free entry in array of IPv6 resolvers * - * Return: Number of entries added (0 or 1) + * Return: number of entries added (0 or 1) */ static unsigned add_dns6(struct ctx *c, const struct in6_addr *addr, unsigned idx) @@ -407,6 +106,76 @@ static unsigned add_dns6(struct ctx *c, const struct in6_addr *addr, } /** + * add_dns_resolv4() - Possibly add one IPv4 nameserver from host's resolv.conf + * @c: Execution context + * @ns: Nameserver address + * @idx: Pointer to index of current IPv4 resolver entry, set on return + */ +static void add_dns_resolv4(struct ctx *c, struct in_addr *ns, unsigned *idx) +{ + if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host)) + c->ip4.dns_host = *ns; + + /* Special handling if guest or container can only access local + * addresses via redirect, or if the host gateway is also a resolver and + * we shadow its address + */ + if (IN4_IS_ADDR_LOOPBACK(ns) || + IN4_ARE_ADDR_EQUAL(ns, &c->ip4.map_host_loopback)) { + if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match)) { + if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback)) + return; /* Address unreachable */ + + *ns = c->ip4.map_host_loopback; + c->ip4.dns_match = c->ip4.map_host_loopback; + } else { + /* No general host mapping, but requested for DNS + * (--dns-forward and --no-map-gw): advertise resolver + * address from --dns-forward, and map that to loopback + */ + *ns = c->ip4.dns_match; + } + } + + *idx += add_dns4(c, ns, *idx); +} + +/** + * add_dns_resolv6() - Possibly add one IPv6 nameserver from host's resolv.conf + * @c: Execution context + * @ns: Nameserver address + * @idx: Pointer to index of current IPv6 resolver entry, set on return + */ +static void add_dns_resolv6(struct ctx *c, struct in6_addr *ns, unsigned *idx) +{ + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host)) + c->ip6.dns_host = *ns; + + /* Special handling if guest or container can only access local + * addresses via redirect, or if the host gateway is also a resolver and + * we shadow its address + */ + if (IN6_IS_ADDR_LOOPBACK(ns) || + IN6_ARE_ADDR_EQUAL(ns, &c->ip6.map_host_loopback)) { + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match)) { + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) + return; /* Address unreachable */ + + *ns = c->ip6.map_host_loopback; + c->ip6.dns_match = c->ip6.map_host_loopback; + } else { + /* No general host mapping, but requested for DNS + * (--dns-forward and --no-map-gw): advertise resolver + * address from --dns-forward, and map that to loopback + */ + *ns = c->ip6.dns_match; + } + } + + *idx += add_dns6(c, ns, *idx); +} + +/** * add_dns_resolv() - Possibly add ns from host resolv.conf to configuration * @c: Execution context * @nameserver: Nameserver address string from /etc/resolv.conf @@ -422,48 +191,11 @@ static void add_dns_resolv(struct ctx *c, const char *nameserver, struct in6_addr ns6; struct in_addr ns4; - if (idx4 && inet_pton(AF_INET, nameserver, &ns4)) { - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host)) - c->ip4.dns_host = ns4; - - /* Special handling if guest or container can only access local - * addresses via redirect, or if the host gateway is also a - * resolver and we shadow its address - */ - if (IN4_IS_ADDR_LOOPBACK(&ns4) || - IN4_ARE_ADDR_EQUAL(&ns4, &c->ip4.map_host_loopback)) { - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback)) - return; - - ns4 = c->ip4.map_host_loopback; - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match)) - c->ip4.dns_match = c->ip4.map_host_loopback; - } - - *idx4 += add_dns4(c, &ns4, *idx4); - } - - if (idx6 && inet_pton(AF_INET6, nameserver, &ns6)) { - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host)) - c->ip6.dns_host = ns6; - - /* Special handling if guest or container can only access local - * addresses via redirect, or if the host gateway is also a - * resolver and we shadow its address - */ - if (IN6_IS_ADDR_LOOPBACK(&ns6) || - IN6_ARE_ADDR_EQUAL(&ns6, &c->ip6.map_host_loopback)) { - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) - return; - - ns6 = c->ip6.map_host_loopback; - - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match)) - c->ip6.dns_match = c->ip6.map_host_loopback; - } + if (idx4 && inet_pton(AF_INET, nameserver, &ns4)) + add_dns_resolv4(c, &ns4, idx4); - *idx6 += add_dns6(c, &ns6, *idx6); - } + if (idx6 && inet_pton(AF_INET6, nameserver, &ns6)) + add_dns_resolv6(c, &ns6, idx6); } /** @@ -580,7 +312,9 @@ static void conf_netns_opt(char *netns, const char *arg) * @argv: Command line arguments */ static void conf_pasta_ns(int *netns_only, char *userns, char *netns, - int optind, int argc, char *argv[]) + int optind, int argc, +/* cppcheck-suppress [constParameter, unmatchedSuppression] */ + char *argv[]) { if (*netns && optind != argc) die("Both --netns and PID or command given"); @@ -615,7 +349,7 @@ static void conf_pasta_ns(int *netns_only, char *userns, char *netns, /** conf_ip4_prefix() - Parse an IPv4 prefix length or netmask * @arg: Netmask in dotted decimal or prefix length * - * Return: Validated prefix length on success, -1 on failure + * Return: validated prefix length on success, -1 on failure */ static int conf_ip4_prefix(const char *arg) { @@ -629,7 +363,7 @@ static int conf_ip4_prefix(const char *arg) return -1; } else { errno = 0; - len = strtoul(optarg, NULL, 0); + len = strtoul(arg, NULL, 0); if (len > 32 || errno) return -1; } @@ -642,7 +376,7 @@ static int conf_ip4_prefix(const char *arg) * @ifi: Host interface to attempt (0 to determine one) * @ip4: IPv4 context (will be written) * - * Return: Interface index for IPv4, or 0 on failure. + * Return: interface index for IPv4, or 0 on failure. */ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) { @@ -714,7 +448,7 @@ static void conf_ip4_local(struct ip4_ctx *ip4) * @ifi: Host interface to attempt (0 to determine one) * @ip6: IPv6 context (will be written) * - * Return: Interface index for IPv6, or 0 on failure. + * Return: interface index for IPv6, or 0 on failure. */ static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) { @@ -773,10 +507,12 @@ static void conf_ip6_local(struct ip6_ctx *ip6) * usage() - Print usage, exit with given status code * @name: Executable name * @f: Stream to print usage info to - * @status: Status code for _exit() + * @status: Status code for exit(2) */ static void usage(const char *name, FILE *f, int status) { + const char *guest, *fwd_default; + if (strstr(name, "pasta")) { FPRINTF(f, "Usage: %s [OPTION]... [COMMAND] [ARGS]...\n", name); FPRINTF(f, " %s [OPTION]... PID\n", name); @@ -786,14 +522,22 @@ static void usage(const char *name, FILE *f, int status) "Without PID or --netns, run the given command or a\n" "default shell in a new network and user namespace, and\n" "connect it via pasta.\n"); + + guest = "namespace"; + fwd_default = "auto"; } else { FPRINTF(f, "Usage: %s [OPTION]...\n", name); + + guest = "guest"; + fwd_default = "none"; } FPRINTF(f, "\n" " -d, --debug Be verbose\n" " --trace Be extra verbose, implies --debug\n" + " --stats DELAY Display events statistics\n" + " minimum DELAY seconds between updates\n" " -q, --quiet Don't print informational messages\n" " -f, --foreground Don't run in background\n" " default: run in background\n" @@ -803,6 +547,7 @@ static void usage(const char *name, FILE *f, int status) " --runas UID|UID:GID Run as given UID, GID, which can be\n" " numeric, or login and group names\n" " default: drop to user \"nobody\"\n" + " -c, --conf-path PATH Configuration socket path\n" " -h, --help Display this help message and exit\n" " --version Show version and exit\n"); @@ -820,6 +565,17 @@ static void usage(const char *name, FILE *f, int status) " UNIX domain socket is provided by -s option\n" " --print-capabilities print back-end capabilities in JSON format,\n" " only meaningful for vhost-user mode\n"); + FPRINTF(f, + " --repair-path PATH path for passt-repair(1)\n" + " default: append '.repair' to UNIX domain path\n"); + FPRINTF(f, + " --migrate-exit DEPRECATED:\n" + " source quits after migration\n" + " default: source keeps running after migration\n"); + FPRINTF(f, + " --migrate-no-linger DEPRECATED:\n" + " close sockets on migration\n" + " default: keep sockets open, ignore events\n"); } FPRINTF(f, @@ -830,7 +586,7 @@ static void usage(const char *name, FILE *f, int status) " a zero value disables assignment\n" " default: 65520: maximum 802.3 MTU minus 802.3 header\n" " length, rounded to 32 bits (IPv4 words)\n" - " -a, --address ADDR Assign IPv4 or IPv6 address ADDR\n" + " -a, --address ADDR Assign IPv4 or IPv6 address ADDR[/PREFIXLEN]\n" " can be specified zero to two times (for IPv4 and IPv6)\n" " default: use addresses from interface with default route\n" " -n, --netmask MASK Assign IPv4 MASK, dot-decimal or bits\n" @@ -858,7 +614,9 @@ static void usage(const char *name, FILE *f, int status) FPRINTF(f, " default: use addresses from /etc/resolv.conf\n"); FPRINTF(f, " -S, --search LIST Space-separated list, search domains\n" - " a single, empty option disables the DNS search list\n"); + " a single, empty option disables the DNS search list\n" + " -H, --hostname NAME Hostname to configure client with\n" + " --fqdn NAME FQDN to configure client with\n"); if (strstr(name, "pasta")) FPRINTF(f, " default: don't use any search list\n"); else @@ -897,70 +655,63 @@ static void usage(const char *name, FILE *f, int status) " --freebind Bind to any address for forwarding\n" " --no-map-gw Don't map gateway address to host\n" " -4, --ipv4-only Enable IPv4 operation only\n" - " -6, --ipv6-only Enable IPv6 operation only\n"); - - if (strstr(name, "pasta")) - goto pasta_opts; - - FPRINTF(f, - " -1, --one-off Quit after handling one single client\n" - " -t, --tcp-ports SPEC TCP port forwarding to guest\n" + " -6, --ipv6-only Enable IPv6 operation only\n" + " -t, --tcp-ports SPEC TCP port forwarding to %s\n" " can be specified multiple times\n" " SPEC can be:\n" " 'none': don't forward any ports\n" - " 'all': forward all unbound, non-ephemeral ports\n" - " a comma-separated list, optionally ranged with '-'\n" - " and optional target ports after ':', with optional\n" - " address specification suffixed by '/' and optional\n" - " interface prefixed by '%%'. Ranges can be reduced by\n" - " excluding ports or ranges prefixed by '~'\n" + " [ADDR[%%IFACE]/]PORTS: forward specific ports\n" + " PORTS is either 'all' (forward all unbound, non-ephemeral\n" + " ports), or a comma-separated list of ports, optionally\n" + " ranged with '-' and optional target ports after ':'.\n" + " Ranges can be reduced by excluding ports or ranges\n" + " prefixed by '~'.\n" + "%s" " Examples:\n" - " -t 22 Forward local port 22 to 22 on guest\n" - " -t 22:23 Forward local port 22 to 23 on guest\n" + " -t all Forward all ports\n" + " -t ::1/all Forward all ports from local address ::1\n" + " -t 22 Forward local port 22 to 22 on %s\n" + " -t 22:23 Forward local port 22 to 23 on %s\n" " -t 22,25 Forward ports 22, 25 to ports 22, 25\n" " -t 22-80 Forward ports 22 to 80\n" " -t 22-80:32-90 Forward ports 22 to 80 to\n" " corresponding port numbers plus 10\n" - " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to guest\n" + " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to %s\n" " -t 5-25,~10-20 Forward ports 5 to 9, and 21 to 25\n" " -t ~25 Forward all ports except for 25\n" - " default: none\n" - " -u, --udp-ports SPEC UDP port forwarding to guest\n" + "%s" + " default: %s\n" + " -u, --udp-ports SPEC UDP port forwarding to %s\n" " SPEC is as described for TCP above\n" - " default: none\n"); + " default: %s\n", + guest, + strstr(name, "pasta") ? + " The 'auto' keyword may be given to only forward\n" + " ports which are bound in the target namespace\n" + : "", + guest, guest, guest, + strstr(name, "pasta") ? + " -t auto\t Forward all ports bound in namespace\n" + " -t ::1/auto Forward ports from ::1 if they are\n" + " bound in the namespace\n" + " -t 80-82,auto Forward ports 80-82 if they are bound\n" + " in the namespace\n" + : "", + + fwd_default, guest, fwd_default); + + if (strstr(name, "pasta")) + goto pasta_opts; - _exit(status); + FPRINTF(f, + " -1, --one-off Quit after handling one single client\n" + ); + + passt_exit(status); pasta_opts: FPRINTF(f, - " -t, --tcp-ports SPEC TCP port forwarding to namespace\n" - " can be specified multiple times\n" - " SPEC can be:\n" - " 'none': don't forward any ports\n" - " 'auto': forward all ports currently bound in namespace\n" - " a comma-separated list, optionally ranged with '-'\n" - " and optional target ports after ':', with optional\n" - " address specification suffixed by '/' and optional\n" - " interface prefixed by '%%'. Examples:\n" - " -t 22 Forward local port 22 to port 22 in netns\n" - " -t 22:23 Forward local port 22 to port 23\n" - " -t 22,25 Forward ports 22, 25 to ports 22, 25\n" - " -t 22-80 Forward ports 22 to 80\n" - " -t 22-80:32-90 Forward ports 22 to 80 to\n" - " corresponding port numbers plus 10\n" - " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to namespace\n" - " -t 5-25,~10-20 Forward ports 5 to 9, and 21 to 25\n" - " -t ~25 Forward all bound ports except for 25\n" - " default: auto\n" - " IPv6 bound ports are also forwarded for IPv4\n" - " -u, --udp-ports SPEC UDP port forwarding to namespace\n" - " SPEC is as described for TCP above\n" - " default: auto\n" - " IPv6 bound ports are also forwarded for IPv4\n" - " unless specified, with '-t auto', UDP ports with numbers\n" - " corresponding to forwarded TCP port numbers are\n" - " forwarded too\n" " -T, --tcp-ns SPEC TCP port forwarding to init namespace\n" " SPEC is as described above\n" " default: auto\n" @@ -981,9 +732,49 @@ pasta_opts: " --no-copy-addrs DEPRECATED:\n" " Don't copy all addresses to namespace\n" " --ns-mac-addr ADDR Set MAC address on tap interface\n" - " --no-splice Disable inbound socket splicing\n"); + " --no-splice Disable inbound socket splicing\n" + " --splice-only Only enable loopback forwarding\n"); - _exit(status); + passt_exit(status); +} + +/** + * conf_mode() - Determine passt/pasta's operating mode from command line + * @argc: Argument count + * @argv: Command line arguments + * + * Return: mode to operate in, PASTA or PASST + */ +enum passt_modes conf_mode(int argc, char *argv[]) +{ + int vhost_user = 0; + const struct option optvu[] = { + {"vhost-user", no_argument, &vhost_user, 1 }, + { 0 }, + }; + char argv0[PATH_MAX], *basearg0; + int name; + + optind = 0; + do { + name = getopt_long(argc, argv, "-:", optvu, NULL); + } while (name != -1); + + if (vhost_user) + return MODE_VU; + + if (argc < 1) + die("Cannot determine argv[0]"); + + strncpy(argv0, argv[0], PATH_MAX - 1); + basearg0 = basename(argv0); + if (strstr(basearg0, "pasta")) + return MODE_PASTA; + + if (strstr(basearg0, "passt")) + return MODE_PASST; + + die("Cannot determine mode, invoke as \"passt\" or \"pasta\""); } /** @@ -992,15 +783,19 @@ pasta_opts: */ static void conf_print(const struct ctx *c) { - char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN]; - char bufmac[ETH_ADDRSTRLEN], ifn[IFNAMSIZ]; + char buf[INANY_ADDRSTRLEN]; int i; + if (c->fd_control_listen >= 0) + info("Configuration socket: %s", c->control_path); + if (c->ifi4 > 0 || c->ifi6 > 0) { + char ifn[IFNAMSIZ]; + info("Template interface: %s%s%s%s%s", c->ifi4 > 0 ? if_indextoname(c->ifi4, ifn) : "", c->ifi4 > 0 ? " (IPv4)" : "", - (c->ifi4 && c->ifi6) ? ", " : "", + (c->ifi4 > 0 && c->ifi6 > 0) ? ", " : "", c->ifi6 > 0 ? if_indextoname(c->ifi6, ifn) : "", c->ifi6 > 0 ? " (IPv6)" : ""); } @@ -1014,28 +809,27 @@ static void conf_print(const struct ctx *c) *c->ip6.ifname_out ? " (IPv6)" : ""); } - if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out) || - !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out)) { - info("Outbound address: %s%s%s", - IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out) ? "" : - inet_ntop(AF_INET, &c->ip4.addr_out, buf4, sizeof(buf4)), - (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out) && - !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out)) ? ", " : "", - IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out) ? "" : - inet_ntop(AF_INET6, &c->ip6.addr_out, buf6, sizeof(buf6))); + if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out)) { + inet_ntop(AF_INET, &c->ip4.addr_out, buf, sizeof(buf)); + info("Outbound IPv4 address: %s", buf); } - if (c->mode == MODE_PASTA) + if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out)) { + inet_ntop(AF_INET6, &c->ip6.addr_out, buf, sizeof(buf)); + info("Outbound IPv6 address: %s", buf); + } + + if (c->mode == MODE_PASTA && !c->splice_only) info("Namespace interface: %s", c->pasta_ifn); info("MAC:"); - info(" host: %s", eth_ntop(c->our_tap_mac, bufmac, sizeof(bufmac))); + info(" host: %s", eth_ntop(c->our_tap_mac, buf, sizeof(buf))); if (c->ifi4) { if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback)) info(" NAT to host 127.0.0.1: %s", inet_ntop(AF_INET, &c->ip4.map_host_loopback, - buf4, sizeof(buf4))); + buf, sizeof(buf))); if (!c->no_dhcp) { uint32_t mask; @@ -1044,19 +838,21 @@ static void conf_print(const struct ctx *c) info("DHCP:"); info(" assign: %s", - inet_ntop(AF_INET, &c->ip4.addr, buf4, sizeof(buf4))); + inet_ntop(AF_INET, &c->ip4.addr, buf, sizeof(buf))); info(" mask: %s", - inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); + inet_ntop(AF_INET, &mask, buf, sizeof(buf))); info(" router: %s", inet_ntop(AF_INET, &c->ip4.guest_gw, - buf4, sizeof(buf4))); + buf, sizeof(buf))); } - for (i = 0; !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i]); i++) { + for (i = 0; i < ARRAY_SIZE(c->ip4.dns); i++) { + if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i])) + break; if (!i) info("DNS:"); - inet_ntop(AF_INET, &c->ip4.dns[i], buf4, sizeof(buf4)); - info(" %s", buf4); + inet_ntop(AF_INET, &c->ip4.dns[i], buf, sizeof(buf)); + info(" %s", buf); } for (i = 0; *c->dns_search[i].n; i++) { @@ -1070,7 +866,7 @@ static void conf_print(const struct ctx *c) if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) info(" NAT to host ::1: %s", inet_ntop(AF_INET6, &c->ip6.map_host_loopback, - buf6, sizeof(buf6))); + buf, sizeof(buf))); if (!c->no_ndp && !c->no_dhcpv6) info("NDP/DHCPv6:"); @@ -1082,19 +878,21 @@ static void conf_print(const struct ctx *c) goto dns6; info(" assign: %s", - inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6))); + inet_ntop(AF_INET6, &c->ip6.addr, buf, sizeof(buf))); info(" router: %s", - inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); + inet_ntop(AF_INET6, &c->ip6.guest_gw, buf, sizeof(buf))); info(" our link-local: %s", inet_ntop(AF_INET6, &c->ip6.our_tap_ll, - buf6, sizeof(buf6))); + buf, sizeof(buf))); dns6: - for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[i]); i++) { + for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) { + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[i])) + break; if (!i) info("DNS:"); - inet_ntop(AF_INET6, &c->ip6.dns[i], buf6, sizeof(buf6)); - info(" %s", buf6); + inet_ntop(AF_INET6, &c->ip6.dns[i], buf, sizeof(buf)); + info(" %s", buf); } for (i = 0; *c->dns_search[i].n; i++) { @@ -1103,6 +901,20 @@ dns6: info(" %s", c->dns_search[i].n); } } + + for (i = 0; i < PIF_NUM_TYPES; i++) { + const char *dir = "Outbound"; + + if (!c->fwd[i]) + continue; + + if (i == PIF_HOST) + dir = "Inbound"; + + info("%s forwarding rules (%s):", dir, pif_name(i)); + fwd_rules_dump(info, c->fwd[i]->rules, c->fwd[i]->count, + " ", ""); + } } /** @@ -1113,6 +925,7 @@ dns6: * * Return: 0 on success, negative error code on failure */ +/* cppcheck-suppress [constParameterPointer,unmatchedSuppression] */ static int conf_runas(char *opt, unsigned int *uid, unsigned int *gid) { const char *uopt, *gopt = NULL; @@ -1220,6 +1033,8 @@ static void conf_nat(const char *arg, struct in_addr *addr4, *addr6 = in6addr_any; if (no_map_gw) *no_map_gw = 1; + + return; } if (inet_pton(AF_INET6, arg, addr6) && @@ -1243,18 +1058,48 @@ static void conf_nat(const char *arg, struct in_addr *addr4, */ static void conf_open_files(struct ctx *c) { - if (c->mode != MODE_PASTA && c->fd_tap == -1) - c->fd_tap_listen = tap_sock_unix_open(c->sock_path); + if (c->mode != MODE_PASTA && c->fd_tap == -1) { + c->fd_tap_listen = sock_unix(c->sock_path); + + if (c->mode == MODE_VU && strcmp(c->repair_path, "none")) { + if (!*c->repair_path && + snprintf_check(c->repair_path, + sizeof(c->repair_path), "%s.repair", + c->sock_path)) { + warn("passt-repair path %s not usable", + c->repair_path); + c->fd_repair_listen = -1; + } else { + c->fd_repair_listen = sock_unix(c->repair_path); + } + } else { + c->fd_repair_listen = -1; + } + c->fd_repair = -1; + } if (*c->pidfile) { c->pidfile_fd = output_file_open(c->pidfile, O_WRONLY); if (c->pidfile_fd < 0) die_perror("Couldn't open PID file %s", c->pidfile); } + + c->fd_control = -1; + if (*c->control_path) { + c->fd_control_listen = sock_unix(c->control_path); + if (c->fd_control_listen < 0) { + die_perror("Couldn't open control socket %s", + c->control_path); + } + if (fcntl(c->fd_control_listen, F_SETFL, O_NONBLOCK)) + die_perror("Couldn't set O_NONBLOCK on control socket"); + } else { + c->fd_control_listen = -1; + } } /** - * parse_mac - Parse a MAC address from a string + * parse_mac() - Parse a MAC address from a string * @mac: Binary MAC address, initialised on success * @str: String to parse * @@ -1287,6 +1132,25 @@ fail: } /** + * conf_sock_listen() - Start listening for connections on configuration socket + * @c: Execution context + */ +static void conf_sock_listen(const struct ctx *c) +{ + union epoll_ref ref = { .type = EPOLL_TYPE_CONF_LISTEN }; + + if (c->fd_control_listen < 0) + return; + + if (listen(c->fd_control_listen, 0)) + die_perror("Couldn't listen on configuration socket"); + + ref.fd = c->fd_control_listen; + if (epoll_add(c->epollfd, EPOLLIN | EPOLLET, ref)) + die_perror("Couldn't add configuration socket to epoll"); +} + +/** * conf() - Process command-line arguments and set configuration * @c: Execution context * @argc: Argument count @@ -1316,6 +1180,7 @@ void conf(struct ctx *c, int argc, char **argv) {"outbound", required_argument, NULL, 'o' }, {"dns", required_argument, NULL, 'D' }, {"search", required_argument, NULL, 'S' }, + {"hostname", required_argument, NULL, 'H' }, {"no-tcp", no_argument, &c->no_tcp, 1 }, {"no-udp", no_argument, &c->no_udp, 1 }, {"no-icmp", no_argument, &c->no_icmp, 1 }, @@ -1324,6 +1189,7 @@ void conf(struct ctx *c, int argc, char **argv) {"no-ndp", no_argument, &c->no_ndp, 1 }, {"no-ra", no_argument, &c->no_ra, 1 }, {"no-splice", no_argument, &c->no_splice, 1 }, + {"splice-only", no_argument, &c->splice_only, 1 }, {"freebind", no_argument, &c->freebind, 1 }, {"no-map-gw", no_argument, &no_map_gw, 1 }, {"ipv4-only", no_argument, NULL, '4' }, @@ -1357,38 +1223,45 @@ void conf(struct ctx *c, int argc, char **argv) {"host-lo-to-ns-lo", no_argument, NULL, 23 }, {"dns-host", required_argument, NULL, 24 }, {"vhost-user", no_argument, NULL, 25 }, + /* vhost-user backend program convention */ {"print-capabilities", no_argument, NULL, 26 }, {"socket-path", required_argument, NULL, 's' }, + {"fqdn", required_argument, NULL, 27 }, + {"repair-path", required_argument, NULL, 28 }, + {"migrate-exit", no_argument, NULL, 29 }, + {"migrate-no-linger", no_argument, NULL, 30 }, + {"stats", required_argument, NULL, 31 }, + {"conf-path", required_argument, NULL, 'c' }, { 0 }, }; + const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt"; + bool opt_t = false, opt_T = false, opt_u = false, opt_U = false; char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 }; bool copy_addrs_opt = false, copy_routes_opt = false; - enum fwd_ports_mode fwd_default = FWD_NONE; bool v4_only = false, v6_only = false; unsigned dns4_idx = 0, dns6_idx = 0; + unsigned long max_mtu = IP_MAX_MTU; struct fqdn *dnss = c->dns_search; + bool addr_has_prefix_len = false; + uint8_t prefix_len_from_opt = 0; unsigned int ifi4 = 0, ifi6 = 0; const char *logfile = NULL; - const char *optstring; - size_t logsize = 0; char *runas = NULL; + size_t logsize = 0; long fd_tap_opt; int name, ret; uid_t uid; gid_t gid; + - if (c->mode == MODE_PASTA) { + if (c->mode == MODE_PASTA) c->no_dhcp_dns = c->no_dhcp_dns_search = 1; - fwd_default = FWD_AUTO; - optstring = "+dqfel:hF:I:p:P:m:a:n:M:g:i:o:D:S:46t:u:T:U:"; - } else { - optstring = "+dqfel:hs:F:p:P:m:a:n:M:g:i:o:D:S:461t:u:"; - } - c->tcp.fwd_in.mode = c->tcp.fwd_out.mode = FWD_UNSET; - c->udp.fwd_in.mode = c->udp.fwd_out.mode = FWD_UNSET; + if (tap_l2_max_len(c) - ETH_HLEN < max_mtu) + max_mtu = tap_l2_max_len(c) - ETH_HLEN; + c->mtu = ROUND_DOWN(max_mtu, sizeof(uint32_t)); memcpy(c->our_tap_mac, MAC_OUR_LAA, ETH_ALEN); optind = 0; @@ -1485,7 +1358,7 @@ void conf(struct ctx *c, int argc, char **argv) FPRINTF(stdout, c->mode == MODE_PASTA ? "pasta " : "passt "); FPRINTF(stdout, VERSION_BLOB); - _exit(EXIT_SUCCESS); + passt_exit(EXIT_SUCCESS); case 15: ret = snprintf(c->ip4.ifname_out, sizeof(c->ip4.ifname_out), "%s", optarg); @@ -1554,13 +1427,44 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid host nameserver address: %s", optarg); case 25: - if (c->mode == MODE_PASTA) - die("--vhost-user is for passt mode only"); - c->mode = MODE_VU; + /* Already handled in conf_mode() */ + assert(c->mode == MODE_VU); break; case 26: vu_print_capabilities(); break; + case 27: + if (snprintf_check(c->fqdn, PASST_MAXDNAME, + "%s", optarg)) + die("Invalid FQDN: %s", optarg); + break; + case 28: + if (c->mode != MODE_VU && strcmp(optarg, "none")) + die("--repair-path is for vhost-user mode only"); + + if (snprintf_check(c->repair_path, + sizeof(c->repair_path), "%s", + optarg)) + die("Invalid passt-repair path: %s", optarg); + + break; + case 29: + if (c->mode != MODE_VU) + die("--migrate-exit is for vhost-user mode only"); + c->migrate_exit = true; + + break; + case 30: + if (c->mode != MODE_VU) + die("--migrate-no-linger is for vhost-user mode only"); + c->migrate_no_linger = true; + + break; + case 31: + if (!c->foreground) + die("Can't display statistics if not running in foreground"); + c->stats = strtol(optarg, NULL, 0); + break; case 'd': c->debug = 1; c->quiet = 0; @@ -1579,6 +1483,9 @@ void conf(struct ctx *c, int argc, char **argv) c->foreground = 1; break; case 's': + if (c->mode == MODE_PASTA) + die("-s is for passt / vhost-user mode only"); + ret = snprintf(c->sock_path, sizeof(c->sock_path), "%s", optarg); if (ret <= 0 || ret >= (int)sizeof(c->sock_path)) @@ -1586,12 +1493,20 @@ void conf(struct ctx *c, int argc, char **argv) c->fd_tap = -1; break; + case 'c': + ret = snprintf(c->control_path, sizeof(c->control_path), + "%s", optarg); + if (ret <= 0 || ret >= (int)sizeof(c->control_path)) + die("Invalid configuration path: %s", optarg); + c->fd_control_listen = c->fd_control = -1; + break; case 'F': errno = 0; fd_tap_opt = strtol(optarg, NULL, 0); if (errno || - fd_tap_opt <= STDERR_FILENO || fd_tap_opt > INT_MAX) + (fd_tap_opt != STDIN_FILENO && fd_tap_opt <= STDERR_FILENO) || + fd_tap_opt > INT_MAX) die("Invalid --fd: %s", optarg); c->fd_tap = fd_tap_opt; @@ -1599,6 +1514,9 @@ void conf(struct ctx *c, int argc, char **argv) *c->sock_path = 0; break; case 'I': + if (c->mode != MODE_PASTA) + die("-I is for pasta mode only"); + ret = snprintf(c->pasta_ifn, IFNAMSIZ, "%s", optarg); if (ret <= 0 || ret >= IFNAMSIZ) @@ -1618,50 +1536,74 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid PID file: %s", optarg); break; - case 'm': - errno = 0; - c->mtu = strtol(optarg, NULL, 0); + case 'm': { + unsigned long mtu; + char *e; - if (!c->mtu) { - c->mtu = -1; - break; - } + errno = 0; + mtu = strtoul(optarg, &e, 0); - if (c->mtu < ETH_MIN_MTU || c->mtu > (int)ETH_MAX_MTU || - errno) + if (errno || *e) die("Invalid MTU: %s", optarg); - break; - case 'a': - if (inet_pton(AF_INET6, optarg, &c->ip6.addr) && - !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr) && - !IN6_IS_ADDR_LOOPBACK(&c->ip6.addr) && - !IN6_IS_ADDR_V4MAPPED(&c->ip6.addr) && - !IN6_IS_ADDR_V4COMPAT(&c->ip6.addr) && - !IN6_IS_ADDR_MULTICAST(&c->ip6.addr)) { - if (c->mode == MODE_PASTA) - c->ip6.no_copy_addrs = true; - break; + if (mtu > max_mtu) { + die("MTU %lu too large (max %lu)", + mtu, max_mtu); } - if (inet_pton(AF_INET, optarg, &c->ip4.addr) && - !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr) && - !IN4_IS_ADDR_BROADCAST(&c->ip4.addr) && - !IN4_IS_ADDR_LOOPBACK(&c->ip4.addr) && - !IN4_IS_ADDR_MULTICAST(&c->ip4.addr)) { + c->mtu = mtu; + break; + } + case 'a': { + union inany_addr addr; + uint8_t prefix_len; + + addr_has_prefix_len = inany_prefix_pton(optarg, &addr, + &prefix_len); + + if (addr_has_prefix_len && prefix_len_from_opt) + die("Redundant prefix length specification"); + + if (!addr_has_prefix_len && !inany_pton(optarg, &addr)) + die("Invalid address: %s", optarg); + + if (prefix_len_from_opt && inany_v4(&addr)) + prefix_len = prefix_len_from_opt; + else if (!addr_has_prefix_len) + prefix_len = inany_default_prefix_len(&addr); + + if (inany_is_unspecified(&addr) || + inany_is_multicast(&addr) || + inany_is_loopback(&addr) || + IN6_IS_ADDR_V4COMPAT(&addr.a6)) + die("Invalid address: %s", optarg); + + if (inany_v4(&addr)) { + c->ip4.addr = *inany_v4(&addr); + c->ip4.prefix_len = prefix_len - 96; if (c->mode == MODE_PASTA) c->ip4.no_copy_addrs = true; - break; + } else { + c->ip6.addr = addr.a6; + if (c->mode == MODE_PASTA) + c->ip6.no_copy_addrs = true; } - - die("Invalid address: %s", optarg); break; - case 'n': - c->ip4.prefix_len = conf_ip4_prefix(optarg); - if (c->ip4.prefix_len < 0) - die("Invalid netmask: %s", optarg); + } + case 'n': { + int plen; + + if (addr_has_prefix_len) + die("Redundant prefix length specification"); + + plen = conf_ip4_prefix(optarg); + if (plen < 0) + die("Invalid prefix length: %s", optarg); + prefix_len_from_opt = plen + 96; + c->ip4.prefix_len = plen; break; + } case 'M': parse_mac(c->our_tap_mac, optarg); break; @@ -1730,6 +1672,11 @@ void conf(struct ctx *c, int argc, char **argv) die("Cannot use DNS search domain %s", optarg); break; + case 'H': + if (snprintf_check(c->hostname, PASST_MAXDNAME, + "%s", optarg)) + die("Invalid hostname: %s", optarg); + break; case '4': v4_only = true; v6_only = false; @@ -1744,13 +1691,52 @@ void conf(struct ctx *c, int argc, char **argv) c->one_off = true; break; - case 't': - case 'u': case 'T': case 'U': - case 'D': + if (c->mode != MODE_PASTA) + die("-%c is for pasta mode only", name); + + /* fall through */ + case 't': + case 'u': /* Handle these later, once addresses are configured */ break; + case 'D': { + struct in6_addr dns6_tmp; + struct in_addr dns4_tmp; + + if (!strcmp(optarg, "none")) { + c->no_dns = 1; + + dns4_idx = 0; + memset(c->ip4.dns, 0, sizeof(c->ip4.dns)); + c->ip4.dns[0] = (struct in_addr){ 0 }; + c->ip4.dns_match = (struct in_addr){ 0 }; + c->ip4.dns_host = (struct in_addr){ 0 }; + + dns6_idx = 0; + memset(c->ip6.dns, 0, sizeof(c->ip6.dns)); + c->ip6.dns_match = (struct in6_addr){ 0 }; + c->ip6.dns_host = (struct in6_addr){ 0 }; + + continue; + } + + c->no_dns = 0; + + if (inet_pton(AF_INET, optarg, &dns4_tmp)) { + dns4_idx += add_dns4(c, &dns4_tmp, dns4_idx); + continue; + } + + if (inet_pton(AF_INET6, optarg, &dns6_tmp)) { + dns6_idx += add_dns6(c, &dns6_tmp, dns6_idx); + continue; + } + + die("Cannot use DNS address %s", optarg); + } + break; case 'h': usage(argv[0], stdout, EXIT_SUCCESS); break; @@ -1761,8 +1747,11 @@ void conf(struct ctx *c, int argc, char **argv) } } while (name != -1); - if (c->mode != MODE_PASTA) + if (c->mode != MODE_PASTA) { c->no_splice = 1; + if (c->splice_only) + die("--splice-only is for pasta mode only"); + } if (c->mode == MODE_PASTA && !c->pasta_conf_ns) { if (copy_routes_opt) @@ -1771,6 +1760,16 @@ void conf(struct ctx *c, int argc, char **argv) die("--no-copy-addrs needs --config-net"); } + if (c->mode == MODE_PASTA && c->splice_only) { + if (c->no_splice) + die("--splice-only is incompatible with --no-splice"); + + c->host_lo_to_ns_lo = 1; + c->no_icmp = 1; + c->no_dns = 1; + c->no_dns_search = 1; + } + if (!ifi4 && *c->ip4.ifname_out) ifi4 = if_nametoindex(c->ip4.ifname_out); @@ -1794,26 +1793,43 @@ void conf(struct ctx *c, int argc, char **argv) log_conf_parsed = true; /* Stop printing everything */ nl_sock_init(c, false); - if (!v6_only) + if (!v6_only && !c->splice_only) c->ifi4 = conf_ip4(ifi4, &c->ip4); - if (!v4_only) + if (!v4_only && !c->splice_only) c->ifi6 = conf_ip6(ifi6, &c->ip6); + + if (c->ifi4 && c->mtu < IPV4_MIN_MTU) { + warn("MTU %"PRIu16" is too small for IPv4 (minimum %u)", + c->mtu, IPV4_MIN_MTU); + } + if (c->ifi6 && c->mtu < IPV6_MIN_MTU) { + warn("MTU %"PRIu16" is too small for IPv6 (minimum %u)", + c->mtu, IPV6_MIN_MTU); + } + if ((*c->ip4.ifname_out && !c->ifi4) || (*c->ip6.ifname_out && !c->ifi6)) die("External interface not usable"); - if (!c->ifi4 && !c->ifi6) { - info("No external interface as template, switch to local mode"); - conf_ip4_local(&c->ip4); - c->ifi4 = -1; + if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) { + strncpy(c->pasta_ifn, pasta_default_ifn, + sizeof(c->pasta_ifn) - 1); + } - conf_ip6_local(&c->ip6); - c->ifi6 = -1; + if (!c->ifi4 && !v6_only) { + if (!c->splice_only) { + info("IPv4: no external interface as template, use local mode"); + conf_ip4_local(&c->ip4); + } + c->ifi4 = -1; + } - if (!*c->pasta_ifn) { - strncpy(c->pasta_ifn, pasta_default_ifn, - sizeof(c->pasta_ifn) - 1); + if (!c->ifi6 && !v4_only) { + if (!c->splice_only) { + info("IPv6: no external interface as template, use local mode"); + conf_ip6_local(&c->ip6); } + c->ifi6 = -1; } if (c->ifi4 && !no_map_gw && @@ -1827,53 +1843,25 @@ void conf(struct ctx *c, int argc, char **argv) if (c->ifi4 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw)) c->no_dhcp = 1; - /* Inbound port options & DNS can be parsed now (after IPv4/IPv6 - * settings) - */ + /* Forwarding options can be parsed now, after IPv4/IPv6 settings */ fwd_probe_ephemeral(); - udp_portmap_clear(); + fwd_rule_init(c); optind = 0; do { name = getopt_long(argc, argv, optstring, options, NULL); if (name == 't') { - conf_ports(c, name, optarg, &c->tcp.fwd_in); + opt_t = true; + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'u') { - conf_ports(c, name, optarg, &c->udp.fwd_in); - } else if (name == 'D') { - struct in6_addr dns6_tmp; - struct in_addr dns4_tmp; - - if (!strcmp(optarg, "none")) { - c->no_dns = 1; - - dns4_idx = 0; - memset(c->ip4.dns, 0, sizeof(c->ip4.dns)); - c->ip4.dns[0] = (struct in_addr){ 0 }; - c->ip4.dns_match = (struct in_addr){ 0 }; - c->ip4.dns_host = (struct in_addr){ 0 }; - - dns6_idx = 0; - memset(c->ip6.dns, 0, sizeof(c->ip6.dns)); - c->ip6.dns_match = (struct in6_addr){ 0 }; - c->ip6.dns_host = (struct in6_addr){ 0 }; - - continue; - } - - c->no_dns = 0; - - if (inet_pton(AF_INET, optarg, &dns4_tmp)) { - dns4_idx += add_dns4(c, &dns4_tmp, dns4_idx); - continue; - } - - if (inet_pton(AF_INET6, optarg, &dns6_tmp)) { - dns6_idx += add_dns6(c, &dns6_tmp, dns6_idx); - continue; - } - - die("Cannot use DNS address %s", optarg); + opt_u = true; + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); + } else if (name == 'T') { + opt_T = true; + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); + } else if (name == 'U') { + opt_U = true; + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } } while (name != -1); @@ -1886,6 +1874,9 @@ void conf(struct ctx *c, int argc, char **argv) isolate_user(uid, gid, !netns_only, userns, c->mode); + if (c->no_icmp) + c->no_ndp = 1; + if (c->pasta_conf_ns) c->no_ra = 1; @@ -1901,17 +1892,6 @@ void conf(struct ctx *c, int argc, char **argv) if (c->mode == MODE_PASTA) nl_sock_init(c, true); - /* ...and outbound port options now that namespaces are set up. */ - optind = 0; - do { - name = getopt_long(argc, argv, optstring, options, NULL); - - if (name == 'T') - conf_ports(c, name, optarg, &c->tcp.fwd_out); - else if (name == 'U') - conf_ports(c, name, optarg, &c->udp.fwd_out); - } while (name != -1); - if (!c->ifi4) c->no_dhcp = 1; @@ -1922,9 +1902,6 @@ void conf(struct ctx *c, int argc, char **argv) c->no_dhcpv6 = 1; } - if (!c->mtu) - c->mtu = ROUND_DOWN(ETH_MAX_MTU - ETH_HLEN, sizeof(uint32_t)); - get_dns(c); if (!*c->pasta_ifn) { @@ -1934,17 +1911,259 @@ void conf(struct ctx *c, int argc, char **argv) if_indextoname(c->ifi6, c->pasta_ifn); } - if (!c->tcp.fwd_in.mode) - c->tcp.fwd_in.mode = fwd_default; - if (!c->tcp.fwd_out.mode) - c->tcp.fwd_out.mode = fwd_default; - if (!c->udp.fwd_in.mode) - c->udp.fwd_in.mode = fwd_default; - if (!c->udp.fwd_out.mode) - c->udp.fwd_out.mode = fwd_default; + if (c->mode == MODE_PASTA) { + if (!opt_t) + fwd_rule_parse('t', false, "auto", c->fwd[PIF_HOST]); + if (!opt_T) + fwd_rule_parse('T', false, "auto", c->fwd[PIF_SPLICE]); + if (!opt_u) + fwd_rule_parse('u', false, "auto", c->fwd[PIF_HOST]); + if (!opt_U) + fwd_rule_parse('U', false, "auto", c->fwd[PIF_SPLICE]); + } - fwd_scan_ports_init(c); + conf_sock_listen(c); if (!c->quiet) conf_print(c); } + +static void conf_accept(struct ctx *c); + +/** + * conf_send_rules() - Send current forwarding rules to config client (pesto) + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + * + * FIXME: So far only sends pif ids and names + */ +static int conf_send_rules(const struct ctx *c, int fd) +{ + unsigned pif; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd[pif]; + struct pesto_pif_info info = { 0 }; + unsigned i; + int rc; + + if (!fwd) + continue; + + assert(pif != PIF_NONE); + + rc = snprintf(info.name, sizeof(info.name), "%s", pif_name(pif)); + assert(rc >= 0 && (size_t)rc < sizeof(info.name)); + info.caps = htonl(fwd->caps); + info.count = htonl(fwd->count); + + if (write_u8(fd, pif) < 0) + return -1; + if (write_all_buf(fd, &info, sizeof(info)) < 0) + return -1; + + for (i = 0; i < fwd->count; i++) { + if (fwd_rule_write(fd, &fwd->rules[i])) + return -1; + } + } + + if (write_u8(fd, PIF_NONE) < 0) + return -1; + + return 0; +} + +/** + * conf_recv_rules() - Receive forwarding rules from configuration client + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + */ +static int conf_recv_rules(const struct ctx *c, int fd) +{ + while (1) { + struct fwd_table *fwd; + struct fwd_rule r; + uint32_t count; + uint8_t pif; + unsigned i; + + if (read_u8(fd, &pif)) + return -1; + + if (pif == PIF_NONE) + break; + + if (pif >= ARRAY_SIZE(c->fwd_pending) || + !(fwd = c->fwd_pending[pif])) { + err("Received rules for non-existent table"); + return -1; + } + + if (read_u32(fd, &count)) + return -1; + + if (count > MAX_FWD_RULES) { + err("Received %"PRIu32" rules (maximum %u)", + count, MAX_FWD_RULES); + return -1; + } + + for (i = 0; i < count; i++) { + if (fwd_rule_read(fd, &r)) + return -1; + + if (r.ifname[sizeof(r.ifname) - 1]) { + err("Interface name was not NULL terminated"); + return -1; + } + /* Redundant, to make static checkers happy */ + r.ifname[sizeof(r.ifname) - 1] = '\0'; + + if (fwd_rule_add(fwd, &r) < 0) + return -1; + } + } + + return 0; +} + +/** + * conf_close() - Close configuration / control socket and clean up + * @c: Execution context + */ +static void conf_close(struct ctx *c) +{ + debug("Closing configuration socket"); + epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_control, NULL); + close(c->fd_control); + c->fd_control = -1; +} + +/** + * conf_listen_handler() - Handle events on configuration listening socket + * @c: Execution context + * @events: epoll events + */ +void conf_listen_handler(struct ctx *c, uint32_t events) +{ + if (events != EPOLLIN) { + err("Unexpected event 0x%04x on configuration socket", events); + return; + } + + if (c->fd_control >= 0) { + /* Ignore the new connection for now, blocking it until the + * current one finishes. + */ + return; + } + + conf_accept(c); +} + +/** + * conf_accept() - Accept a new control connection + * @c: Execution context + */ +static void conf_accept(struct ctx *c) +{ + struct pesto_hello hello = { + .magic = PESTO_SERVER_MAGIC, + .version = htonl(PESTO_PROTOCOL_VERSION), + .pif_name_size = htonl(PIF_NAME_SIZE), + .ifnamsiz = htonl(IFNAMSIZ), + }; + union epoll_ref ref = { .type = EPOLL_TYPE_CONF }; + struct ucred uc = { 0 }; + socklen_t len = sizeof(uc); + int fd, rc; + +retry: + fd = accept4(c->fd_control_listen, NULL, NULL, SOCK_CLOEXEC); + if (fd < 0) { + if (errno != EAGAIN) + warn_perror("accept4() on configuration listening socket"); + return; + } + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0) + warn_perror("Can't get configuration client credentials"); + + c->fd_control = ref.fd = fd; + rc = epoll_add(c->epollfd, EPOLLIN | EPOLLET, ref); + if (rc < 0) { + warn_perror("epoll_ctl() on configuration socket"); + goto fail; + } + + rc = write_all_buf(fd, &hello, sizeof(hello)); + if (rc < 0) { + warn_perror("Error writing configuration protocol hello"); + goto fail; + } + + info("Accepted configuration client, PID %i", uc.pid); + if (!PESTO_PROTOCOL_VERSION) { + warn( +"Warning: Using experimental unsupported configuration protocol"); + } + + if (conf_send_rules(c, fd) < 0) + goto fail; + + return; + +fail: + conf_close(c); + goto retry; +} + +/** + * conf_handler() - Handle events on configuration socket + * @c: Execution context + * @events: epoll events + */ +void conf_handler(struct ctx *c, uint32_t events) +{ + if (events & EPOLLIN) { + unsigned pif; + + /* Clear pending tables */ + for (pif = 0; pif < PIF_NUM_TYPES; pif++) + fwd_rule_clear(c->fwd_pending[pif]); + + /* FIXME: this could block indefinitely if the client doesn't + * write as much as it should + */ + if (conf_recv_rules(c, c->fd_control) < 0) + goto close; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + + info("New forwarding rules for %s:", pif_name(pif)); + fwd_rules_dump(info, fwd->rules, fwd->count, + " ", ""); + } + + fwd_listen_switch(c); + } + + if (events & EPOLLHUP) { + debug("Configuration client hangup"); + } + +close: + conf_close(c); + + /* Check if any other clients are waiting to connect */ + conf_accept(c); +} |
