aboutgitcodebugslistschat
path: root/conf.c
diff options
context:
space:
mode:
Diffstat (limited to 'conf.c')
-rw-r--r--conf.c1351
1 files changed, 785 insertions, 566 deletions
diff --git a/conf.c b/conf.c
index 142dc94..029b9c7 100644
--- a/conf.c
+++ b/conf.c
@@ -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);
+}