diff options
Diffstat (limited to 'conf.c')
| -rw-r--r-- | conf.c | 1712 |
1 files changed, 1061 insertions, 651 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" @@ -45,363 +47,155 @@ #include "lineread.h" #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" + +#define IP4_LL_GUEST_ADDR (struct in_addr){ htonl_constant(0xa9fe0201) } + /* 169.254.2.1, libslirp default: 10.0.2.1 */ + +#define IP4_LL_GUEST_GW (struct in_addr){ htonl_constant(0xa9fe0202) } + /* 169.254.2.2, libslirp default: 10.0.2.2 */ + +#define IP4_LL_PREFIX_LEN 16 + +#define IP6_LL_GUEST_GW (struct in6_addr) \ + {{{ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, \ + 0, 0, 0, 0, 0, 0, 0, 0x01 }}} + +static 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 + * add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration + * @c: Execution context + * @addr: Guest nameserver IPv4 address + * @idx: Index of free entry in array of IPv4 resolvers * - * 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 + * Return: number of entries added (0 or 1) */ -static char *next_chunk(const char *s, char c) +static unsigned add_dns4(struct ctx *c, const struct in_addr *addr, + unsigned idx) { - char *sep = strchr(s, c); - return sep ? sep + 1 : NULL; -} + if (idx >= ARRAY_SIZE(c->ip4.dns)) + return 0; -/** - * 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; -}; + c->ip4.dns[idx] = *addr; + return 1; +} /** - * 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 + * add_dns6() - Possibly add the IPv6 address of a DNS resolver to configuration + * @c: Execution context + * @addr: Guest nameserver IPv6 address + * @idx: Index of free entry in array of IPv6 resolvers * - * Return: -EINVAL on parsing error, -ERANGE on out of range port - * numbers, 0 on success + * Return: number of entries added (0 or 1) */ -static int parse_port_range(const char *s, char **endptr, - struct port_range *range) +static unsigned add_dns6(struct ctx *c, const struct in6_addr *addr, + unsigned idx) { - 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; + if (idx >= ARRAY_SIZE(c->ip6.dns)) + return 0; - return 0; + c->ip6.dns[idx] = *addr; + return 1; } /** - * conf_ports() - Parse port configuration options, initialise UDP/TCP sockets + * add_dns_resolv4() - Possibly add one IPv4 nameserver from host's resolv.conf * @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 + * @ns: Nameserver address + * @idx: Pointer to index of current IPv4 resolver entry, set on return */ -static void conf_ports(const struct ctx *c, char optname, const char *optarg, - struct fwd_ports *fwd) +static void add_dns_resolv4(struct ctx *c, struct in_addr *ns, unsigned *idx) { - char addr_buf[sizeof(struct in6_addr)] = { 0 }, *addr = addr_buf; - char buf[BUFSIZ], *spec, *ifname = NULL, *p; - bool exclude_only = true, bound_one = false; - uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; - sa_family_t af = AF_UNSPEC; - 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; - memset(fwd->map, 0xff, PORT_EPHEMERAL_MIN / 8); - - for (i = 0; i < PORT_EPHEMERAL_MIN; i++) { - if (optname == 't') { - ret = tcp_sock_init(c, AF_UNSPEC, 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, AF_UNSPEC, 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 (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host)) + c->ip4.dns_host = *ns; - if (ifname == buf + 1) { /* Interface without address */ - addr = NULL; + /* 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 { - p = buf; - - /* Allow square brackets for IPv4 too for convenience */ - if (*p == '[' && p[strlen(p) - 1] == ']') { - p[strlen(p) - 1] = '\0'; - p++; - } - - if (inet_pton(AF_INET, p, addr)) - af = AF_INET; - else if (inet_pton(AF_INET6, p, addr)) - af = AF_INET6; - else - 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) { - for (i = 0; i < PORT_EPHEMERAL_MIN; i++) { - if (bitmap_isset(exclude, i)) - continue; - - bitmap_set(fwd->map, i); - - if (optname == 't') { - ret = tcp_sock_init(c, af, 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, af, 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; - } + /* 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; } - - 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, af, addr, ifname, i); - else if (optname == 'u') - ret = udp_sock_init(c, 0, af, 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); + *idx += add_dns4(c, ns, *idx); } /** - * add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration + * add_dns_resolv6() - Possibly add one IPv6 nameserver from host's resolv.conf * @c: Execution context - * @addr: Address found in /etc/resolv.conf - * @conf: Pointer to reference of current entry in array of IPv4 resolvers + * @ns: Nameserver address + * @idx: Pointer to index of current IPv6 resolver entry, set on return */ -static void add_dns4(struct ctx *c, const struct in_addr *addr, - struct in_addr **conf) +static void add_dns_resolv6(struct ctx *c, struct in6_addr *ns, unsigned *idx) { - /* Guest or container can only access local addresses via redirect */ - if (IN4_IS_ADDR_LOOPBACK(addr)) { - if (!c->no_map_gw) { - **conf = c->ip4.gw; - (*conf)++; - - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match)) - c->ip4.dns_match = c->ip4.gw; + 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; } - } else { - **conf = *addr; - (*conf)++; } - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host)) - c->ip4.dns_host = *addr; + *idx += add_dns6(c, ns, *idx); } /** - * add_dns6() - Possibly add the IPv6 address of a DNS resolver to configuration + * add_dns_resolv() - Possibly add ns from host resolv.conf to configuration * @c: Execution context - * @addr: Address found in /etc/resolv.conf - * @conf: Pointer to reference of current entry in array of IPv6 resolvers + * @nameserver: Nameserver address string from /etc/resolv.conf + * @idx4: Pointer to index of current entry in array of IPv4 resolvers + * @idx6: Pointer to index of current entry in array of IPv6 resolvers + * + * @idx4 or @idx6 may be NULL, in which case resolvers of the corresponding type + * are ignored. */ -static void add_dns6(struct ctx *c, - struct in6_addr *addr, struct in6_addr **conf) +static void add_dns_resolv(struct ctx *c, const char *nameserver, + unsigned *idx4, unsigned *idx6) { - /* Guest or container can only access local addresses via redirect */ - if (IN6_IS_ADDR_LOOPBACK(addr)) { - if (!c->no_map_gw) { - memcpy(*conf, &c->ip6.gw, sizeof(**conf)); - (*conf)++; - - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match)) - memcpy(&c->ip6.dns_match, addr, sizeof(*addr)); - } - } else { - memcpy(*conf, addr, sizeof(**conf)); - (*conf)++; - } + struct in6_addr ns6; + struct in_addr ns4; - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host)) - c->ip6.dns_host = *addr; + if (idx4 && inet_pton(AF_INET, nameserver, &ns4)) + add_dns_resolv4(c, &ns4, idx4); + + if (idx6 && inet_pton(AF_INET6, nameserver, &ns6)) + add_dns_resolv6(c, &ns6, idx6); } /** @@ -410,18 +204,16 @@ static void add_dns6(struct ctx *c, */ static void get_dns(struct ctx *c) { - struct in6_addr *dns6 = &c->ip6.dns[0], dns6_tmp; - struct in_addr *dns4 = &c->ip4.dns[0], dns4_tmp; int dns4_set, dns6_set, dnss_set, dns_set, fd; + unsigned dns4_idx = 0, dns6_idx = 0; struct fqdn *s = c->dns_search; struct lineread resolvconf; - unsigned int added = 0; ssize_t line_len; char *line, *end; const char *p; - dns4_set = !c->ifi4 || !IN4_IS_ADDR_UNSPECIFIED(dns4); - dns6_set = !c->ifi6 || !IN6_IS_ADDR_UNSPECIFIED(dns6); + dns4_set = !c->ifi4 || !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[0]); + dns6_set = !c->ifi6 || !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[0]); dnss_set = !!*s->n || c->no_dns_search; dns_set = (dns4_set && dns6_set) || c->no_dns; @@ -442,19 +234,9 @@ static void get_dns(struct ctx *c) if (end) *end = 0; - if (!dns4_set && - dns4 - &c->ip4.dns[0] < ARRAY_SIZE(c->ip4.dns) - 1 - && inet_pton(AF_INET, p + 1, &dns4_tmp)) { - add_dns4(c, &dns4_tmp, &dns4); - added++; - } - - if (!dns6_set && - dns6 - &c->ip6.dns[0] < ARRAY_SIZE(c->ip6.dns) - 1 - && inet_pton(AF_INET6, p + 1, &dns6_tmp)) { - add_dns6(c, &dns6_tmp, &dns6); - added++; - } + add_dns_resolv(c, p + 1, + dns4_set ? NULL : &dns4_idx, + dns6_set ? NULL : &dns6_idx); } else if (!dnss_set && strstr(line, "search ") == line && s == c->dns_search) { end = strpbrk(line, "\n"); @@ -481,7 +263,7 @@ static void get_dns(struct ctx *c) out: if (!dns_set) { - if (!added) + if (!(dns4_idx + dns6_idx)) warn("Couldn't get any nameserver address"); if (c->no_dhcp_dns) @@ -530,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"); @@ -545,10 +329,15 @@ static void conf_pasta_ns(int *netns_only, char *userns, char *netns, if (pidval < 0 || pidval > INT_MAX) die("Invalid PID %s", argv[optind]); - snprintf(netns, PATH_MAX, "/proc/%ld/ns/net", pidval); - if (!*userns) - snprintf(userns, PATH_MAX, "/proc/%ld/ns/user", - pidval); + if (snprintf_check(netns, PATH_MAX, + "/proc/%ld/ns/net", pidval)) + die_perror("Can't build netns path"); + + if (!*userns) { + if (snprintf_check(userns, PATH_MAX, + "/proc/%ld/ns/user", pidval)) + die_perror("Can't build userns path"); + } } } @@ -560,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) { @@ -574,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; } @@ -586,26 +375,25 @@ static int conf_ip4_prefix(const char *arg) * conf_ip4() - Verify or detect IPv4 support, get relevant addresses * @ifi: Host interface to attempt (0 to determine one) * @ip4: IPv4 context (will be written) - * @mac: MAC address to use (written if unset) * - * 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, unsigned char *mac) +static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) { if (!ifi) ifi = nl_get_ext_if(nl_sock, AF_INET); if (!ifi) { - info("Couldn't pick external interface: disabling IPv4"); + debug("Failed to detect external interface for IPv4"); return 0; } - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->gw)) { - int rc = nl_route_get_def(nl_sock, ifi, AF_INET, &ip4->gw); + if (IN4_IS_ADDR_UNSPECIFIED(&ip4->guest_gw)) { + int rc = nl_route_get_def(nl_sock, ifi, AF_INET, + &ip4->guest_gw); if (rc < 0) { - err("Couldn't discover IPv4 gateway address: %s", - strerror(-rc)); + debug("Couldn't discover IPv4 gateway address: %s", + strerror_(-rc)); return 0; } } @@ -614,8 +402,8 @@ static unsigned int conf_ip4(unsigned int ifi, int rc = nl_addr_get(nl_sock, ifi, AF_INET, &ip4->addr, &ip4->prefix_len, NULL); if (rc < 0) { - err("Couldn't discover IPv4 address: %s", - strerror(-rc)); + debug("Couldn't discover IPv4 address: %s", + strerror_(-rc)); return 0; } } @@ -632,21 +420,9 @@ static unsigned int conf_ip4(unsigned int ifi, ip4->prefix_len = 32; } - memcpy(&ip4->addr_seen, &ip4->addr, sizeof(ip4->addr_seen)); + ip4->addr_seen = ip4->addr; - if (MAC_IS_ZERO(mac)) { - int rc = nl_link_get_mac(nl_sock, ifi, mac); - if (rc < 0) { - char ifname[IFNAMSIZ]; - - err("Couldn't discover MAC address for %s: %s", - if_indextoname(ifi, ifname), strerror(-rc)); - return 0; - } - - if (MAC_IS_ZERO(mac)) - memcpy(mac, MAC_LAA, ETH_ALEN); - } + ip4->our_tap_addr = ip4->guest_gw; if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) return 0; @@ -655,15 +431,26 @@ static unsigned int conf_ip4(unsigned int ifi, } /** + * conf_ip4_local() - Configure IPv4 addresses and attributes for local mode + * @ip4: IPv4 context (will be written) + */ +static void conf_ip4_local(struct ip4_ctx *ip4) +{ + ip4->addr_seen = ip4->addr = IP4_LL_GUEST_ADDR; + ip4->our_tap_addr = ip4->guest_gw = IP4_LL_GUEST_GW; + ip4->prefix_len = IP4_LL_PREFIX_LEN; + + ip4->no_copy_addrs = ip4->no_copy_routes = true; +} + +/** * conf_ip6() - Verify or detect IPv6 support, get relevant addresses * @ifi: Host interface to attempt (0 to determine one) * @ip6: IPv6 context (will be written) - * @mac: MAC address to use (written if unset) * - * 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, unsigned char *mac) +static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) { int prefix_len = 0; int rc; @@ -672,75 +459,85 @@ static unsigned int conf_ip6(unsigned int ifi, ifi = nl_get_ext_if(nl_sock, AF_INET6); if (!ifi) { - info("Couldn't pick external interface: disabling IPv6"); + debug("Failed to detect external interface for IPv6"); return 0; } - if (IN6_IS_ADDR_UNSPECIFIED(&ip6->gw)) { - rc = nl_route_get_def(nl_sock, ifi, AF_INET6, &ip6->gw); + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->guest_gw)) { + rc = nl_route_get_def(nl_sock, ifi, AF_INET6, &ip6->guest_gw); if (rc < 0) { - err("Couldn't discover IPv6 gateway address: %s", - strerror(-rc)); + debug("Couldn't discover IPv6 gateway address: %s", + strerror_(-rc)); return 0; } } rc = nl_addr_get(nl_sock, ifi, AF_INET6, IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ? &ip6->addr : NULL, - &prefix_len, &ip6->addr_ll); + &prefix_len, &ip6->our_tap_ll); if (rc < 0) { - err("Couldn't discover IPv6 address: %s", strerror(-rc)); + debug("Couldn't discover IPv6 address: %s", strerror_(-rc)); return 0; } - memcpy(&ip6->addr_seen, &ip6->addr, sizeof(ip6->addr)); - memcpy(&ip6->addr_ll_seen, &ip6->addr_ll, sizeof(ip6->addr_ll)); - - if (MAC_IS_ZERO(mac)) { - rc = nl_link_get_mac(nl_sock, ifi, mac); - if (rc < 0) { - char ifname[IFNAMSIZ]; - err("Couldn't discover MAC address for %s: %s", - if_indextoname(ifi, ifname), strerror(-rc)); - return 0; - } + ip6->addr_seen = ip6->addr; - if (MAC_IS_ZERO(mac)) - memcpy(mac, MAC_LAA, ETH_ALEN); - } + if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) + ip6->our_tap_ll = ip6->guest_gw; if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) || - IN6_IS_ADDR_UNSPECIFIED(&ip6->addr_ll)) + IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) return 0; return ifi; } /** + * conf_ip6_local() - Configure IPv6 addresses and attributes for local mode + * @ip6: IPv6 context (will be written) + */ +static void conf_ip6_local(struct ip6_ctx *ip6) +{ + ip6->our_tap_ll = ip6->guest_gw = IP6_LL_GUEST_GW; + + ip6->no_copy_addrs = ip6->no_copy_routes = true; +} + +/** * 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); - fprintf(f, " %s [OPTION]... --netns [PATH|NAME]\n", name); - fprintf(f, + FPRINTF(f, "Usage: %s [OPTION]... [COMMAND] [ARGS]...\n", name); + FPRINTF(f, " %s [OPTION]... PID\n", name); + FPRINTF(f, " %s [OPTION]... --netns [PATH|NAME]\n", name); + FPRINTF(f, "\n" "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); + FPRINTF(f, "Usage: %s [OPTION]...\n", name); + + guest = "guest"; + fwd_default = "none"; } - fprintf(f, + 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" @@ -750,21 +547,38 @@ 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"); if (strstr(name, "pasta")) { - fprintf(f, + FPRINTF(f, " -I, --ns-ifname NAME namespace interface name\n" " default: same interface name as external one\n"); } else { - fprintf(f, - " -s, --socket PATH UNIX domain socket path\n" + FPRINTF(f, + " -s, --socket, --socket-path PATH UNIX domain socket path\n" " default: probe free path starting from " UNIX_SOCK_PATH "\n", 1); + FPRINTF(f, + " --vhost-user Enable vhost-user mode\n" + " 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, + FPRINTF(f, " -F, --fd FD Use FD as pre-opened connected socket\n" " -p, --pcap FILE Log tap-facing traffic to pcap file\n" " -P, --pid FILE Write own PID to the given file\n" @@ -772,13 +586,13 @@ 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" " default: netmask from matching address on the host\n" " -M, --mac-addr ADDR Use source MAC address ADDR\n" - " default: MAC address from interface with default route\n" + " default: 9a:55:9a:55:9a:55 (locally administered)\n" " -g, --gateway ADDR Pass IPv4 or IPv6 address as gateway\n" " default: gateway from interface with default route\n" " -i, --interface NAME Interface for addresses and routes\n" @@ -795,31 +609,42 @@ static void usage(const char *name, FILE *f, int status) " can be specified multiple times\n" " a single, empty option disables DNS information\n"); if (strstr(name, "pasta")) - fprintf(f, " default: don't use any addresses\n"); + FPRINTF(f, " default: don't use any addresses\n"); else - fprintf(f, " default: use addresses from /etc/resolv.conf\n"); - fprintf(f, + 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"); + FPRINTF(f, " default: don't use any search list\n"); else - fprintf(f, " default: use search list from /etc/resolv.conf\n"); + FPRINTF(f, " default: use search list from /etc/resolv.conf\n"); if (strstr(name, "pasta")) - fprintf(f, " --dhcp-dns \tPass DNS list via DHCP/DHCPv6/NDP\n"); + FPRINTF(f, " --dhcp-dns \tPass DNS list via DHCP/DHCPv6/NDP\n"); else - fprintf(f, " --no-dhcp-dns No DNS list in DHCP/DHCPv6/NDP\n"); + FPRINTF(f, " --no-dhcp-dns No DNS list in DHCP/DHCPv6/NDP\n"); if (strstr(name, "pasta")) - fprintf(f, " --dhcp-search Pass list via DHCP/DHCPv6/NDP\n"); + FPRINTF(f, " --dhcp-search Pass list via DHCP/DHCPv6/NDP\n"); else - fprintf(f, " --no-dhcp-search No list in DHCP/DHCPv6/NDP\n"); - - fprintf(f, + FPRINTF(f, " --no-dhcp-search No list in DHCP/DHCPv6/NDP\n"); + + FPRINTF(f, + " --map-host-loopback ADDR Translate ADDR to refer to host\n" + " can be specified zero to two times (for IPv4 and IPv6)\n" + " default: gateway address\n" + " --map-guest-addr ADDR Translate ADDR to guest's address\n" + " can be specified zero to two times (for IPv4 and IPv6)\n" + " default: none\n" " --dns-forward ADDR Forward DNS queries sent to ADDR\n" " can be specified zero to two times (for IPv4 and IPv6)\n" " default: don't forward DNS queries\n" + " --dns-host ADDR Host nameserver to direct queries to\n" + " can be specified zero to two times (for IPv4 and IPv6)\n" + " default: first nameserver from host's /etc/resolv.conf\n" " --no-tcp Disable TCP protocol handler\n" " --no-udp Disable UDP protocol handler\n" " --no-icmp Disable ICMP/ICMPv6 protocol handler\n" @@ -827,78 +652,74 @@ static void usage(const char *name, FILE *f, int status) " --no-ndp Disable NDP responses\n" " --no-dhcpv6 Disable DHCPv6 server\n" " --no-ra Disable router advertisements\n" + " --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; + + FPRINTF(f, + " -1, --one-off Quit after handling one single client\n" + ); - exit(status); + 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" + FPRINTF(f, " -T, --tcp-ns SPEC TCP port forwarding to init namespace\n" " SPEC is as described above\n" " default: auto\n" " -U, --udp-ns SPEC UDP port forwarding to init namespace\n" " SPEC is as described above\n" " default: auto\n" + " --host-lo-to-ns-lo Translate host-loopback forwards to\n" + " namespace loopback\n" " --userns NSPATH Target user namespace to join\n" " --netns PATH|NAME Target network namespace to join\n" " --netns-only Don't join existing user namespace\n" @@ -910,9 +731,50 @@ pasta_opts: " Don't copy all routes to namespace\n" " --no-copy-addrs DEPRECATED:\n" " Don't copy all addresses to namespace\n" - " --ns-mac-addr ADDR Set MAC address on tap interface\n"); + " --ns-mac-addr ADDR Set MAC address on tap interface\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\""); } /** @@ -921,15 +783,22 @@ pasta_opts: */ static void conf_print(const struct ctx *c) { - char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN], ifn[IFNAMSIZ]; + char buf[INANY_ADDRSTRLEN]; int i; - info("Template interface: %s%s%s%s%s", - c->ifi4 ? if_indextoname(c->ifi4, ifn) : "", - c->ifi4 ? " (IPv4)" : "", - (c->ifi4 && c->ifi6) ? ", " : "", - c->ifi6 ? if_indextoname(c->ifi6, ifn) : "", - c->ifi6 ? " (IPv6)" : ""); + 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 > 0 && c->ifi6 > 0) ? ", " : "", + c->ifi6 > 0 ? if_indextoname(c->ifi6, ifn) : "", + c->ifi6 > 0 ? " (IPv6)" : ""); + } if (*c->ip4.ifname_out || *c->ip6.ifname_out) { info("Outbound interface: %s%s%s%s%s", @@ -940,26 +809,28 @@ 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: %02x:%02x:%02x:%02x:%02x:%02x", - c->mac[0], c->mac[1], c->mac[2], - c->mac[3], c->mac[4], c->mac[5]); + 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, + buf, sizeof(buf))); + if (!c->no_dhcp) { uint32_t mask; @@ -967,18 +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.gw, buf4, sizeof(buf4))); + inet_ntop(AF_INET, &c->ip4.guest_gw, + 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++) { @@ -989,28 +863,36 @@ static void conf_print(const struct ctx *c) } if (c->ifi6) { + 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, + buf, sizeof(buf))); + if (!c->no_ndp && !c->no_dhcpv6) info("NDP/DHCPv6:"); - else if (!c->no_ndp) - info("DHCPv6:"); else if (!c->no_dhcpv6) + info("DHCPv6:"); + else if (!c->no_ndp) info("NDP:"); else 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.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.addr_ll, buf6, sizeof(buf6))); + inet_ntop(AF_INET6, &c->ip6.our_tap_ll, + 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++) { @@ -1019,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, + " ", ""); + } } /** @@ -1029,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; @@ -1122,19 +1019,87 @@ static void conf_ugid(char *runas, uid_t *uid, gid_t *gid) } /** + * conf_nat() - Parse --map-host-loopback or --map-guest-addr option + * @arg: String argument to option + * @addr4: IPv4 to update with parsed address + * @addr6: IPv6 to update with parsed address + * @no_map_gw: --no-map-gw flag, or NULL, updated for "none" argument + */ +static void conf_nat(const char *arg, struct in_addr *addr4, + struct in6_addr *addr6, int *no_map_gw) +{ + if (strcmp(arg, "none") == 0) { + *addr4 = in4addr_any; + *addr6 = in6addr_any; + if (no_map_gw) + *no_map_gw = 1; + + return; + } + + if (inet_pton(AF_INET6, arg, addr6) && + !IN6_IS_ADDR_UNSPECIFIED(addr6) && + !IN6_IS_ADDR_LOOPBACK(addr6) && + !IN6_IS_ADDR_MULTICAST(addr6)) + return; + + if (inet_pton(AF_INET, arg, addr4) && + !IN4_IS_ADDR_UNSPECIFIED(addr4) && + !IN4_IS_ADDR_LOOPBACK(addr4) && + !IN4_IS_ADDR_MULTICAST(addr4)) + return; + + die("Invalid address to remap to host: %s", optarg); +} + +/** * conf_open_files() - Open files as requested by configuration * @c: Execution context */ 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->pidfile_fd = pidfile_open(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 * @@ -1167,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 @@ -1174,7 +1158,7 @@ fail: */ void conf(struct ctx *c, int argc, char **argv) { - int netns_only = 0; + int netns_only = 0, no_map_gw = 0; const struct option options[] = { {"debug", no_argument, NULL, 'd' }, {"quiet", no_argument, NULL, 'q' }, @@ -1196,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 }, @@ -1203,7 +1188,10 @@ void conf(struct ctx *c, int argc, char **argv) {"no-dhcpv6", no_argument, &c->no_dhcpv6, 1 }, {"no-ndp", no_argument, &c->no_ndp, 1 }, {"no-ra", no_argument, &c->no_ra, 1 }, - {"no-map-gw", no_argument, &c->no_map_gw, 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' }, {"ipv6-only", no_argument, NULL, '6' }, {"one-off", no_argument, NULL, '1' }, @@ -1230,38 +1218,53 @@ void conf(struct ctx *c, int argc, char **argv) {"no-copy-routes", no_argument, NULL, 18 }, {"no-copy-addrs", no_argument, NULL, 19 }, {"netns-only", no_argument, NULL, 20 }, + {"map-host-loopback", required_argument, NULL, 21 }, + {"map-guest-addr", required_argument, NULL, 22 }, + {"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; - struct in6_addr *dns6 = c->ip6.dns; - struct in_addr *dns4 = c->ip4.dns; + 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 = 1; + optind = 0; do { name = getopt_long(argc, argv, optstring, options, NULL); @@ -1290,7 +1293,7 @@ void conf(struct ctx *c, int argc, char **argv) if (c->mode != MODE_PASTA) die("--ns-mac-addr is for pasta mode only"); - parse_mac(c->mac_guest, optarg); + parse_mac(c->guest_mac, optarg); break; case 5: if (c->mode != MODE_PASTA) @@ -1352,10 +1355,10 @@ void conf(struct ctx *c, int argc, char **argv) break; case 14: - fprintf(stdout, + FPRINTF(stdout, c->mode == MODE_PASTA ? "pasta " : "passt "); - fprintf(stdout, VERSION_BLOB); - exit(EXIT_SUCCESS); + FPRINTF(stdout, VERSION_BLOB); + passt_exit(EXIT_SUCCESS); case 15: ret = snprintf(c->ip4.ifname_out, sizeof(c->ip4.ifname_out), "%s", optarg); @@ -1399,6 +1402,69 @@ void conf(struct ctx *c, int argc, char **argv) netns_only = 1; *userns = 0; break; + case 21: + conf_nat(optarg, &c->ip4.map_host_loopback, + &c->ip6.map_host_loopback, &no_map_gw); + break; + case 22: + conf_nat(optarg, &c->ip4.map_guest_addr, + &c->ip6.map_guest_addr, NULL); + break; + case 23: + if (c->mode != MODE_PASTA) + die("--host-lo-to-ns-lo is for pasta mode only"); + c->host_lo_to_ns_lo = 1; + break; + case 24: + if (inet_pton(AF_INET6, optarg, &c->ip6.dns_host) && + !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host)) + break; + + if (inet_pton(AF_INET, optarg, &c->ip4.dns_host) && + !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host) && + !IN4_IS_ADDR_BROADCAST(&c->ip4.dns_host)) + break; + + die("Invalid host nameserver address: %s", optarg); + case 25: + /* 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; @@ -1417,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)) @@ -1424,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; @@ -1437,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) @@ -1456,66 +1536,90 @@ 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->mac, optarg); + parse_mac(c->our_tap_mac, optarg); break; case 'g': - if (inet_pton(AF_INET6, optarg, &c->ip6.gw) && - !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.gw) && - !IN6_IS_ADDR_LOOPBACK(&c->ip6.gw)) { + if (inet_pton(AF_INET6, optarg, &c->ip6.guest_gw) && + !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.guest_gw) && + !IN6_IS_ADDR_LOOPBACK(&c->ip6.guest_gw)) { if (c->mode == MODE_PASTA) c->ip6.no_copy_routes = true; break; } - if (inet_pton(AF_INET, optarg, &c->ip4.gw) && - !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.gw) && - !IN4_IS_ADDR_BROADCAST(&c->ip4.gw) && - !IN4_IS_ADDR_LOOPBACK(&c->ip4.gw)) { + if (inet_pton(AF_INET, optarg, &c->ip4.guest_gw) && + !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw) && + !IN4_IS_ADDR_BROADCAST(&c->ip4.guest_gw) && + !IN4_IS_ADDR_LOOPBACK(&c->ip4.guest_gw)) { if (c->mode == MODE_PASTA) c->ip4.no_copy_routes = true; break; @@ -1568,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; @@ -1582,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; @@ -1599,6 +1747,12 @@ void conf(struct ctx *c, int argc, char **argv) } } while (name != -1); + 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) die("--no-copy-routes needs --config-net"); @@ -1606,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); @@ -1629,69 +1793,75 @@ void conf(struct ctx *c, int argc, char **argv) log_conf_parsed = true; /* Stop printing everything */ nl_sock_init(c, false); - if (!v6_only) - c->ifi4 = conf_ip4(ifi4, &c->ip4, c->mac); - if (!v4_only) - c->ifi6 = conf_ip6(ifi6, &c->ip6, c->mac); - if ((!c->ifi4 && !c->ifi6) || - (*c->ip4.ifname_out && !c->ifi4) || + if (!v6_only && !c->splice_only) + c->ifi4 = conf_ip4(ifi4, &c->ip4); + 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 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.gw)) - c->no_map_gw = c->no_dhcp = 1; - - if (c->ifi6 && IN6_IS_ADDR_UNSPECIFIED(&c->ip6.gw)) - c->no_map_gw = 1; - - /* Inbound port options & DNS can be parsed now (after IPv4/IPv6 - * settings) - */ - udp_portmap_clear(); - optind = 1; - do { - name = getopt_long(argc, argv, optstring, options, NULL); - - if (name == 't') { - conf_ports(c, name, optarg, &c->tcp.fwd_in); - } 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; + if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) { + strncpy(c->pasta_ifn, pasta_default_ifn, + sizeof(c->pasta_ifn) - 1); + } - dns4 = &c->ip4.dns[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 }; + 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; + } - dns6 = &c->ip6.dns[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 }; + 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; + } - continue; - } + if (c->ifi4 && !no_map_gw && + IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback)) + c->ip4.map_host_loopback = c->ip4.guest_gw; - c->no_dns = 0; + if (c->ifi6 && !no_map_gw && + IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) + c->ip6.map_host_loopback = c->ip6.guest_gw; - if (dns4 - &c->ip4.dns[0] < ARRAY_SIZE(c->ip4.dns) && - inet_pton(AF_INET, optarg, &dns4_tmp)) { - add_dns4(c, &dns4_tmp, &dns4); - continue; - } + if (c->ifi4 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw)) + c->no_dhcp = 1; - if (dns6 - &c->ip6.dns[0] < ARRAY_SIZE(c->ip6.dns) && - inet_pton(AF_INET6, optarg, &dns6_tmp)) { - add_dns6(c, &dns6_tmp, &dns6); - continue; - } + /* Forwarding options can be parsed now, after IPv4/IPv6 settings */ + fwd_probe_ephemeral(); + fwd_rule_init(c); + optind = 0; + do { + name = getopt_long(argc, argv, optstring, options, NULL); - die("Cannot use DNS address %s", optarg); + if (name == 't') { + opt_t = true; + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); + } else if (name == 'u') { + 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); @@ -1704,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; @@ -1719,48 +1892,285 @@ 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 = 1; - 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; if (!c->ifi6) { c->no_ndp = 1; c->no_dhcpv6 = 1; + } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { + 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) { - if (c->ifi4) + if (c->ifi4 > 0) if_indextoname(c->ifi4, c->pasta_ifn); - else + else if (c->ifi6 > 0) 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: + /* Currently we perform the configuration transaction more-or-less + * synchronously, so we want the accepted socket to be blocking. + * + * FIXME: We should make the configuration update asynchronous, like + * most of our operation, so a misbehaving configuration client can't + * block the main forwarding loop. + */ + 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); +} |
