diff options
Diffstat (limited to 'conf.c')
-rw-r--r-- | conf.c | 1143 |
1 files changed, 631 insertions, 512 deletions
@@ -38,6 +38,7 @@ #include "ip.h" #include "passt.h" #include "netlink.h" +#include "tap.h" #include "udp.h" #include "tcp.h" #include "pasta.h" @@ -46,6 +47,8 @@ #include "log.h" #include "vhost_user.h" +#define NETNS_RUN_DIR "/run/netns" + /** * next_chunk - Return the next piece of a string delimited by a character * @s: String to search @@ -116,11 +119,10 @@ static int parse_port_range(const char *s, char **endptr, static void conf_ports(const struct ctx *c, char optname, const char *optarg, struct fwd_ports *fwd) { - char addr_buf[sizeof(struct in6_addr)] = { 0 }, *addr = addr_buf; + 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 }; - sa_family_t af = AF_UNSPEC; unsigned i; int ret; @@ -132,6 +134,11 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg, 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; @@ -151,19 +158,23 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg, 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++) { + /* 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, AF_UNSPEC, NULL, NULL, - i); + 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, AF_UNSPEC, NULL, NULL, - i); + ret = udp_sock_init(c, 0, NULL, NULL, i); if (ret == -ENFILE || ret == -EMFILE) goto enfile; if (!ret) @@ -204,14 +215,20 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg, } - if (ifname == buf + 1) /* Interface without address */ + if (ifname == buf + 1) { /* Interface without address */ addr = NULL; - else if (inet_pton(AF_INET, buf, addr)) - af = AF_INET; - else if (inet_pton(AF_INET6, buf, addr)) - af = AF_INET6; - else - goto bad; + } 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; @@ -244,20 +261,24 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg, } while ((p = next_chunk(p, ','))); if (exclude_only) { - for (i = 0; i < PORT_EPHEMERAL_MIN; i++) { - if (bitmap_isset(exclude, i)) + /* 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, af, addr, ifname, i); + 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, af, addr, ifname, i); + ret = udp_sock_init(c, 0, addr, ifname, i); if (ret == -ENFILE || ret == -EMFILE) goto enfile; if (!ret) @@ -313,9 +334,9 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg, ret = 0; if (optname == 't') - ret = tcp_sock_init(c, af, addr, ifname, i); + ret = tcp_sock_init(c, addr, ifname, i); else if (optname == 'u') - ret = udp_sock_init(c, 0, af, addr, ifname, i); + ret = udp_sock_init(c, 0, addr, ifname, i); if (ret) goto bind_fail; } @@ -338,55 +359,93 @@ bind_all_fail: /** * add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration * @c: Execution context - * @addr: Address found in /etc/resolv.conf - * @conf: Pointer to reference of current entry in array of IPv4 resolvers + * @addr: Guest nameserver IPv4 address + * @idx: Index of free entry in array of IPv4 resolvers + * + * Return: Number of entries added (0 or 1) */ -static void add_dns4(struct ctx *c, const struct in_addr *addr, - struct in_addr **conf) +static unsigned add_dns4(struct ctx *c, const struct in_addr *addr, + 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; - } - } else { - **conf = *addr; - (*conf)++; - } + if (idx >= ARRAY_SIZE(c->ip4.dns)) + return 0; - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_host)) - c->ip4.dns_host = *addr; + c->ip4.dns[idx] = *addr; + return 1; } /** * add_dns6() - Possibly add the IPv6 address of a DNS resolver to configuration * @c: Execution context - * @addr: Address found in /etc/resolv.conf - * @conf: Pointer to reference of current entry in array of IPv6 resolvers + * @addr: Guest nameserver IPv6 address + * @idx: Index of free entry in array of IPv6 resolvers + * + * Return: Number of entries added (0 or 1) */ -static void add_dns6(struct ctx *c, - struct in6_addr *addr, struct in6_addr **conf) +static unsigned add_dns6(struct ctx *c, const struct in6_addr *addr, + unsigned idx) { - /* 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 (idx >= ARRAY_SIZE(c->ip6.dns)) + return 0; - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match)) - memcpy(&c->ip6.dns_match, addr, sizeof(*addr)); + c->ip6.dns[idx] = *addr; + return 1; +} + +/** + * add_dns_resolv() - Possibly add ns from host resolv.conf to configuration + * @c: Execution context + * @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_dns_resolv(struct ctx *c, const char *nameserver, + unsigned *idx4, unsigned *idx6) +{ + 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; + + /* Guest or container can only access local addresses via + * redirect + */ + if (IN4_IS_ADDR_LOOPBACK(&ns4)) { + 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; } - } else { - memcpy(*conf, addr, sizeof(**conf)); - (*conf)++; + + *idx4 += add_dns4(c, &ns4, *idx4); } - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host)) - c->ip6.dns_host = *addr; + if (idx6 && inet_pton(AF_INET6, nameserver, &ns6)) { + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host)) + c->ip6.dns_host = ns6; + + /* Guest or container can only access local addresses via + * redirect + */ + if (IN6_IS_ADDR_LOOPBACK(&ns6)) { + 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; + } + + *idx6 += add_dns6(c, &ns6, *idx6); + } } /** @@ -395,17 +454,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; + ssize_t line_len; char *line, *end; const char *p; - int line_len; - 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; @@ -426,15 +484,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); - - 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); + 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"); @@ -448,7 +500,7 @@ static void get_dns(struct ctx *c) while (s - c->dns_search < ARRAY_SIZE(c->dns_search) - 1 /* cppcheck-suppress strtokCalled */ && (p = strtok(NULL, " \t"))) { - strncpy(s->n, p, sizeof(c->dns_search[0])); + strncpy(s->n, p, sizeof(c->dns_search[0]) - 1); s++; *s->n = 0; } @@ -456,12 +508,25 @@ static void get_dns(struct ctx *c) } if (line_len < 0) - warn("Error reading /etc/resolv.conf: %s", strerror(errno)); + warn_perror("Error reading /etc/resolv.conf"); close(fd); out: - if (!dns_set && dns4 == c->ip4.dns && dns6 == c->ip6.dns) - warn("Couldn't get any nameserver address"); + if (!dns_set) { + if (!(dns4_idx + dns6_idx)) + warn("Couldn't get any nameserver address"); + + if (c->no_dhcp_dns) + return; + + if (c->ifi4 && !c->no_dhcp && + IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[0])) + warn("No IPv4 nameserver available for DHCP"); + + if (c->ifi6 && ((!c->no_ndp && !c->no_ra) || !c->no_dhcpv6) && + IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[0])) + warn("No IPv6 nameserver available for NDP/DHCPv6"); + } } /** @@ -499,9 +564,6 @@ static void conf_netns_opt(char *netns, const char *arg) static void conf_pasta_ns(int *netns_only, char *userns, char *netns, int optind, int argc, char *argv[]) { - if (*netns_only && *userns) - die("Both --userns and --netns-only given"); - if (*netns && optind != argc) die("Both --netns and PID or command given"); @@ -515,10 +577,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"); + } } } @@ -556,23 +623,22 @@ 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. */ -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("No interface with a default route for IPv4: disabling IPv4"); + info("Couldn't pick external interface: disabling 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)); @@ -602,20 +668,11 @@ 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 for %s: %s", - if_indextoname(ifi, ifname), strerror(-rc)); - return 0; - } - } + ip4->our_tap_addr = ip4->guest_gw; - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr) || - MAC_IS_ZERO(mac)) + if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) return 0; return ifi; @@ -625,12 +682,10 @@ static unsigned int conf_ip4(unsigned int ifi, * 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. */ -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; @@ -639,12 +694,12 @@ static unsigned int conf_ip6(unsigned int ifi, ifi = nl_get_ext_if(nl_sock, AF_INET6); if (!ifi) { - info("No interface with a default route for IPv6: disabling IPv6"); + info("Couldn't pick external interface: disabling 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)); @@ -654,246 +709,249 @@ static unsigned int conf_ip6(unsigned int ifi, 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)); return 0; } - memcpy(&ip6->addr_seen, &ip6->addr, sizeof(ip6->addr)); - memcpy(&ip6->addr_ll_seen, &ip6->addr_ll, sizeof(ip6->addr_ll)); + ip6->addr_seen = ip6->addr; - 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 for %s: %s", - if_indextoname(ifi, ifname), strerror(-rc)); - return 0; - } - } + 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) || - MAC_IS_ZERO(mac)) + IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) return 0; return ifi; } /** - * print_usage() - Print usage, exit with given status code + * usage() - Print usage, exit with given status code * @name: Executable name + * @f: Stream to print usage info to * @status: Status code for exit() */ -static void print_usage(const char *name, int status) +static void usage(const char *name, FILE *f, int status) { if (strstr(name, "pasta")) { - info("Usage: %s [OPTION]... [COMMAND] [ARGS]...", name); - info(" %s [OPTION]... PID", name); - info(" %s [OPTION]... --netns [PATH|NAME]", name); - info(""); - info("Without PID or --netns, run the given command or a"); - info("default shell in a new network and user namespace, and"); - info("connect it via 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, + "\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"); } else { - info("Usage: %s [OPTION]...", name); + FPRINTF(f, "Usage: %s [OPTION]...\n", name); } - info(""); - - - info( " -d, --debug Be verbose"); - info( " --trace Be extra verbose, implies --debug"); - info( " -q, --quiet Don't print informational messages"); - info( " -f, --foreground Don't run in background"); - info( " default: run in background if started from a TTY"); - info( " -e, --stderr Log to stderr too"); - info( " default: log to system logger only if started from a TTY"); - info( " -l, --log-file PATH Log (only) to given file"); - info( " --log-size BYTES Maximum size of log file"); - info( " default: 1 MiB"); - info( " --runas UID|UID:GID Run as given UID, GID, which can be"); - info( " numeric, or login and group names"); - info( " default: drop to user \"nobody\""); - info( " -h, --help Display this help message and exit"); - info( " --version Show version and exit"); + + FPRINTF(f, + "\n" + " -d, --debug Be verbose\n" + " --trace Be extra verbose, implies --debug\n" + " -q, --quiet Don't print informational messages\n" + " -f, --foreground Don't run in background\n" + " default: run in background\n" + " -l, --log-file PATH Log (only) to given file\n" + " --log-size BYTES Maximum size of log file\n" + " default: 1 MiB\n" + " --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" + " -h, --help Display this help message and exit\n" + " --version Show version and exit\n"); if (strstr(name, "pasta")) { - info( " -I, --ns-ifname NAME namespace interface name"); - info( " default: same interface name as external one"); + FPRINTF(f, + " -I, --ns-ifname NAME namespace interface name\n" + " default: same interface name as external one\n"); } else { - info( " -s, --socket, --socket-path PATH UNIX domain socket path"); - info( " default: probe free path starting from " - UNIX_SOCK_PATH, 1); - info( " --vhost-user Enable vhost-user mode"); - info( " UNIX domain socket is provided by -s option"); - info( " --print-capabilities print back-end capabilities in JSON format"); + 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"); } - info( " -F, --fd FD Use FD as pre-opened connected socket"); - info( " -p, --pcap FILE Log tap-facing traffic to pcap file"); - info( " -P, --pid FILE Write own PID to the given file"); - info( " -m, --mtu MTU Assign MTU via DHCP/NDP"); - info( " a zero value disables assignment"); - info( " default: 65520: maximum 802.3 MTU minus 802.3 header"); - info( " length, rounded to 32 bits (IPv4 words)"); - info( " -a, --address ADDR Assign IPv4 or IPv6 address ADDR"); - info( " can be specified zero to two times (for IPv4 and IPv6)"); - info( " default: use addresses from interface with default route"); - info( " -n, --netmask MASK Assign IPv4 MASK, dot-decimal or bits"); - info( " default: netmask from matching address on the host"); - info( " -M, --mac-addr ADDR Use source MAC address ADDR"); - info( " default: MAC address from interface with default route"); - info( " -g, --gateway ADDR Pass IPv4 or IPv6 address as gateway"); - info( " default: gateway from interface with default route"); - info( " -i, --interface NAME Interface for addresses and routes"); - info( " default: from --outbound-if4 and --outbound-if6, if any"); - info( " otherwise interface with first default route"); - info( " -o, --outbound ADDR Bind to address as outbound source"); - info( " can be specified zero to two times (for IPv4 and IPv6)"); - info( " default: use source address from routing tables"); - info( " --outbound-if4 NAME Bind to outbound interface for IPv4"); - info( " default: use interface from default route"); - info( " --outbound-if6 NAME Bind to outbound interface for IPv6"); - info( " default: use interface from default route"); - info( " -D, --dns ADDR Use IPv4 or IPv6 address as DNS"); - info( " can be specified multiple times"); - info( " a single, empty option disables DNS information"); + 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" + " -m, --mtu MTU Assign MTU via DHCP/NDP\n" + " 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" + " 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" + " -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" + " default: from --outbound-if4 and --outbound-if6, if any\n" + " otherwise interface with first default route\n" + " -o, --outbound ADDR Bind to address as outbound source\n" + " can be specified zero to two times (for IPv4 and IPv6)\n" + " default: use source address from routing tables\n" + " --outbound-if4 NAME Bind to outbound interface for IPv4\n" + " default: use interface from default route\n" + " --outbound-if6 NAME Bind to outbound interface for IPv6\n" + " default: use interface from default route\n" + " -D, --dns ADDR Use IPv4 or IPv6 address as DNS\n" + " can be specified multiple times\n" + " a single, empty option disables DNS information\n"); if (strstr(name, "pasta")) - info( " default: don't use any addresses"); + FPRINTF(f, " default: don't use any addresses\n"); else - info( " default: use addresses from /etc/resolv.conf"); - - info( " -S, --search LIST Space-separated list, search domains"); - info( " a single, empty option disables the DNS search list"); + 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"); if (strstr(name, "pasta")) - info( " default: don't use any search list"); + FPRINTF(f, " default: don't use any search list\n"); else - info( " default: use search list from /etc/resolv.conf"); + FPRINTF(f, " default: use search list from /etc/resolv.conf\n"); if (strstr(name, "pasta")) - info(" --dhcp-dns \tPass DNS list via DHCP/DHCPv6/NDP"); + FPRINTF(f, " --dhcp-dns \tPass DNS list via DHCP/DHCPv6/NDP\n"); else - info(" --no-dhcp-dns No DNS list in DHCP/DHCPv6/NDP"); + FPRINTF(f, " --no-dhcp-dns No DNS list in DHCP/DHCPv6/NDP\n"); if (strstr(name, "pasta")) - info(" --dhcp-search Pass list via DHCP/DHCPv6/NDP"); + FPRINTF(f, " --dhcp-search Pass list via DHCP/DHCPv6/NDP\n"); else - info(" --no-dhcp-search No list in DHCP/DHCPv6/NDP"); - - info( " --dns-forward ADDR Forward DNS queries sent to ADDR"); - info( " can be specified zero to two times (for IPv4 and IPv6)"); - info( " default: don't forward DNS queries"); - - info( " --no-tcp Disable TCP protocol handler"); - info( " --no-udp Disable UDP protocol handler"); - info( " --no-icmp Disable ICMP/ICMPv6 protocol handler"); - info( " --no-dhcp Disable DHCP server"); - info( " --no-ndp Disable NDP responses"); - info( " --no-dhcpv6 Disable DHCPv6 server"); - info( " --no-ra Disable router advertisements"); - info( " --no-map-gw Don't map gateway address to host"); - info( " -4, --ipv4-only Enable IPv4 operation only"); - info( " -6, --ipv6-only Enable IPv6 operation only"); + 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" + " --no-dhcp Disable DHCP server\n" + " --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; - info( " -1, --one-off Quit after handling one single client"); - info( " -t, --tcp-ports SPEC TCP port forwarding to guest"); - info( " can be specified multiple times"); - info( " SPEC can be:"); - info( " 'none': don't forward any ports"); - info( " 'all': forward all unbound, non-ephemeral ports"); - info( " a comma-separated list, optionally ranged with '-'"); - info( " and optional target ports after ':', with optional"); - info( " address specification suffixed by '/' and optional"); - info( " interface prefixed by '%%'. Ranges can be reduced by"); - info( " excluding ports or ranges prefixed by '~'"); - info( " Examples:"); - info( " -t 22 Forward local port 22 to 22 on guest"); - info( " -t 22:23 Forward local port 22 to 23 on guest"); - info( " -t 22,25 Forward ports 22, 25 to ports 22, 25"); - info( " -t 22-80 Forward ports 22 to 80"); - info( " -t 22-80:32-90 Forward ports 22 to 80 to"); - info( " corresponding port numbers plus 10"); - info( " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to guest"); - info( " -t 5-25,~10-20 Forward ports 5 to 9, and 21 to 25"); - info( " -t ~25 Forward all ports except for 25"); - info( " default: none"); - info( " -u, --udp-ports SPEC UDP port forwarding to guest"); - info( " SPEC is as described for TCP above"); - info( " default: none"); + FPRINTF(f, + " -1, --one-off Quit after handling one single client\n" + " -t, --tcp-ports SPEC TCP port forwarding to guest\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" + " 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 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 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" + " SPEC is as described for TCP above\n" + " default: none\n"); exit(status); pasta_opts: - info( " -t, --tcp-ports SPEC TCP port forwarding to namespace"); - info( " can be specified multiple times"); - info( " SPEC can be:"); - info( " 'none': don't forward any ports"); - info( " 'auto': forward all ports currently bound in namespace"); - info( " a comma-separated list, optionally ranged with '-'"); - info( " and optional target ports after ':', with optional"); - info( " address specification suffixed by '/' and optional"); - info( " interface prefixed by '%%'. Examples:"); - info( " -t 22 Forward local port 22 to port 22 in netns"); - info( " -t 22:23 Forward local port 22 to port 23"); - info( " -t 22,25 Forward ports 22, 25 to ports 22, 25"); - info( " -t 22-80 Forward ports 22 to 80"); - info( " -t 22-80:32-90 Forward ports 22 to 80 to"); - info( " corresponding port numbers plus 10"); - info( " -t 192.0.2.1/5 Bind port 5 of 192.0.2.1 to namespace"); - info( " -t 5-25,~10-20 Forward ports 5 to 9, and 21 to 25"); - info( " -t ~25 Forward all bound ports except for 25"); - info( " default: auto"); - info( " IPv6 bound ports are also forwarded for IPv4"); - info( " -u, --udp-ports SPEC UDP port forwarding to namespace"); - info( " SPEC is as described for TCP above"); - info( " default: auto"); - info( " IPv6 bound ports are also forwarded for IPv4"); - info( " unless specified, with '-t auto', UDP ports with numbers"); - info( " corresponding to forwarded TCP port numbers are"); - info( " forwarded too"); - info( " -T, --tcp-ns SPEC TCP port forwarding to init namespace"); - info( " SPEC is as described above"); - info( " default: auto"); - info( " -U, --udp-ns SPEC UDP port forwarding to init namespace"); - info( " SPEC is as described above"); - info( " default: auto"); - info( " --userns NSPATH Target user namespace to join"); - info( " --netns PATH|NAME Target network namespace to join"); - info( " --netns-only Don't join existing user namespace"); - info( " implied if PATH or NAME are given without --userns"); - info( " --no-netns-quit Don't quit if filesystem-bound target"); - info( " network namespace is deleted"); - info( " --config-net Configure tap interface in namespace"); - info( " --no-copy-routes DEPRECATED:"); - info( " Don't copy all routes to namespace"); - info( " --no-copy-addrs DEPRECATED:"); - info( " Don't copy all addresses to namespace"); - info( " --ns-mac-addr ADDR Set MAC address on tap interface"); + 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" + " -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 DEPRECATED:\n" + " 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" + " implied if PATH or NAME are given without --userns\n" + " --no-netns-quit Don't quit if filesystem-bound target\n" + " network namespace is deleted\n" + " --config-net Configure tap interface in namespace\n" + " --no-copy-routes DEPRECATED:\n" + " 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"); exit(status); } /** - * usage() - Print usage and exit with failure - * @name: Executable name - */ -static void usage(const char *name) -{ - print_usage(name, EXIT_FAILURE); -} - -/** * conf_print() - Print fundamental configuration parameters * @c: Execution context */ static void conf_print(const struct ctx *c) { - char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN], ifn[IFNAMSIZ]; + char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN]; + char bufmac[ETH_ADDRSTRLEN], ifn[IFNAMSIZ]; int i; info("Template interface: %s%s%s%s%s", @@ -927,11 +985,14 @@ static void conf_print(const struct ctx *c) 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, bufmac, sizeof(bufmac))); 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))); + if (!c->no_dhcp) { uint32_t mask; @@ -943,7 +1004,8 @@ static void conf_print(const struct ctx *c) info(" mask: %s", inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); info(" router: %s", - inet_ntop(AF_INET, &c->ip4.gw, buf4, sizeof(buf4))); + inet_ntop(AF_INET, &c->ip4.guest_gw, + buf4, sizeof(buf4))); } for (i = 0; !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i]); i++) { @@ -961,6 +1023,11 @@ 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, + buf6, sizeof(buf6))); + if (!c->no_ndp && !c->no_dhcpv6) info("NDP/DHCPv6:"); else if (!c->no_ndp) @@ -973,9 +1040,10 @@ static void conf_print(const struct ctx *c) info(" assign: %s", inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6))); info(" router: %s", - inet_ntop(AF_INET6, &c->ip6.gw, buf6, sizeof(buf6))); + inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); 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, + buf6, sizeof(buf6))); dns6: for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[i]); i++) { @@ -1075,16 +1143,14 @@ static void conf_ugid(char *runas, uid_t *uid, gid_t *gid) return; /* ...otherwise use nobody:nobody */ - warn("Don't run as root. Changing to nobody..."); + warn("Started as root, will change to nobody."); { #ifndef GLIBC_NO_STATIC_NSS const struct passwd *pw; /* cppcheck-suppress getpwnamCalled */ pw = getpwnam("nobody"); - if (!pw) { - perror("getpwnam"); - exit(EXIT_FAILURE); - } + if (!pw) + die_perror("Can't get password file entry for nobody"); *uid = pw->pw_uid; *gid = pw->pw_gid; @@ -1096,6 +1162,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; + } + + 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->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); + } +} + +/** + * parse_mac - Parse a MAC address from a string + * @mac: Binary MAC address, initialised on success + * @str: String to parse + * + * Parses @str as an Ethernet MAC address stored in @mac on success. Exits on + * failure. + */ +static void parse_mac(unsigned char mac[ETH_ALEN], const char *str) +{ + size_t i; + + if (strlen(str) != (ETH_ALEN * 3 - 1)) + goto fail; + + for (i = 0; i < ETH_ALEN; i++) { + const char *octet = str + 3 * i; + unsigned long b; + char *end; + + errno = 0; + b = strtoul(octet, &end, 16); + if (b > UCHAR_MAX || errno || end != octet + 2 || + *end != ((i == ETH_ALEN - 1) ? '\0' : ':')) + goto fail; + mac[i] = b; + } + return; + +fail: + die("Invalid MAC address: %s", str); +} + +/** * conf() - Process command-line arguments and set configuration * @c: Execution context * @argc: Argument count @@ -1103,7 +1250,7 @@ static void conf_ugid(char *runas, uid_t *uid, gid_t *gid) */ 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' }, @@ -1113,7 +1260,6 @@ void conf(struct ctx *c, int argc, char **argv) {"help", no_argument, NULL, 'h' }, {"socket", required_argument, NULL, 's' }, {"fd", required_argument, NULL, 'F' }, - {"socket-path", required_argument, NULL, 's' }, /* vhost-user mandatory */ {"ns-ifname", required_argument, NULL, 'I' }, {"pcap", required_argument, NULL, 'p' }, {"pid", required_argument, NULL, 'P' }, @@ -1133,7 +1279,8 @@ 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 }, + {"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' }, @@ -1143,7 +1290,6 @@ void conf(struct ctx *c, int argc, char **argv) {"udp-ns", required_argument, NULL, 'U' }, {"userns", required_argument, NULL, 2 }, {"netns", required_argument, NULL, 3 }, - {"netns-only", no_argument, &netns_only, 1 }, {"ns-mac-addr", required_argument, NULL, 4 }, {"dhcp-dns", no_argument, NULL, 5 }, {"no-dhcp-dns", no_argument, NULL, 6 }, @@ -1160,37 +1306,47 @@ void conf(struct ctx *c, int argc, char **argv) {"config-net", no_argument, NULL, 17 }, {"no-copy-routes", no_argument, NULL, 18 }, {"no-copy-addrs", no_argument, NULL, 19 }, - {"vhost-user", no_argument, NULL, 20 }, - {"print-capabilities", no_argument, NULL, 21 }, /* vhost-user mandatory */ + {"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' }, { 0 }, }; + const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt"; 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; + unsigned dns4_idx = 0, dns6_idx = 0; struct fqdn *dnss = c->dns_search; - struct in_addr *dns4 = c->ip4.dns; unsigned int ifi4 = 0, ifi6 = 0; const char *logfile = NULL; const char *optstring; - int name, ret, b, i; size_t logsize = 0; char *runas = NULL; + long fd_tap_opt; + int name, ret; uid_t uid; gid_t gid; 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:"; + 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:"; + 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 = 0; - c->udp.fwd_in.f.mode = c->udp.fwd_out.f.mode = 0; + c->tcp.fwd_in.mode = c->tcp.fwd_out.mode = FWD_UNSET; + c->udp.fwd_in.mode = c->udp.fwd_out.mode = FWD_UNSET; + memcpy(c->our_tap_mac, MAC_OUR_LAA, ETH_ALEN); + optind = 0; do { name = getopt_long(argc, argv, optstring, options, NULL); @@ -1206,6 +1362,8 @@ void conf(struct ctx *c, int argc, char **argv) if (ret <= 0 || ret >= (int)sizeof(userns)) die("Invalid userns: %s", optarg); + netns_only = 0; + break; case 3: if (c->mode != MODE_PASTA) @@ -1217,14 +1375,7 @@ void conf(struct ctx *c, int argc, char **argv) if (c->mode != MODE_PASTA) die("--ns-mac-addr is for pasta mode only"); - for (i = 0; i < ETH_ALEN; i++) { - errno = 0; - b = strtol(optarg + (intptr_t)i * 3, NULL, 16); - if (b < 0 || b > UCHAR_MAX || errno) - die("Invalid MAC address: %s", optarg); - - c->mac_guest[i] = b; - } + parse_mac(c->guest_mac, optarg); break; case 5: if (c->mode != MODE_PASTA) @@ -1251,14 +1402,12 @@ void conf(struct ctx *c, int argc, char **argv) c->no_dhcp_dns_search = 1; break; case 9: - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match) && - inet_pton(AF_INET6, optarg, &c->ip6.dns_match) && + if (inet_pton(AF_INET6, optarg, &c->ip6.dns_match) && !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_match) && !IN6_IS_ADDR_LOOPBACK(&c->ip6.dns_match)) break; - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match) && - inet_pton(AF_INET, optarg, &c->ip4.dns_match) && + if (inet_pton(AF_INET, optarg, &c->ip4.dns_match) && !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns_match) && !IN4_IS_ADDR_BROADCAST(&c->ip4.dns_match) && !IN4_IS_ADDR_LOOPBACK(&c->ip4.dns_match)) @@ -1273,24 +1422,13 @@ void conf(struct ctx *c, int argc, char **argv) c->no_netns_quit = 1; break; case 11: - if (c->trace) - die("Multiple --trace options given"); - - if (c->quiet) - die("Either --trace or --quiet"); - c->trace = c->debug = 1; + c->quiet = 0; break; case 12: - if (runas) - die("Multiple --runas options given"); - runas = optarg; break; case 13: - if (logsize) - die("Multiple --log-size options given"); - errno = 0; logsize = strtol(optarg, NULL, 0); @@ -1299,14 +1437,11 @@ 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); + FPRINTF(stdout, VERSION_BLOB); exit(EXIT_SUCCESS); case 15: - if (*c->ip4.ifname_out) - die("Redundant outbound interface: %s", optarg); - ret = snprintf(c->ip4.ifname_out, sizeof(c->ip4.ifname_out), "%s", optarg); if (ret <= 0 || ret >= (int)sizeof(c->ip4.ifname_out)) @@ -1314,13 +1449,11 @@ void conf(struct ctx *c, int argc, char **argv) break; case 16: - if (*c->ip6.ifname_out) - die("Redundant outbound interface: %s", optarg); - ret = snprintf(c->ip6.ifname_out, sizeof(c->ip6.ifname_out), "%s", optarg); if (ret <= 0 || ret >= (int)sizeof(c->ip6.ifname_out)) die("Invalid interface name: %s", optarg); + break; case 17: if (c->mode != MODE_PASTA) @@ -1333,94 +1466,96 @@ void conf(struct ctx *c, int argc, char **argv) die("--no-copy-routes is for pasta mode only"); warn("--no-copy-routes will be dropped soon"); - c->no_copy_routes = copy_routes_opt = true; + c->ip4.no_copy_routes = c->ip6.no_copy_routes = true; + copy_routes_opt = true; break; case 19: if (c->mode != MODE_PASTA) die("--no-copy-addrs is for pasta mode only"); warn("--no-copy-addrs will be dropped soon"); - c->no_copy_addrs = copy_addrs_opt = true; + c->ip4.no_copy_addrs = c->ip6.no_copy_addrs = true; + copy_addrs_opt = true; break; case 20: + if (c->mode != MODE_PASTA) + die("--netns-only is for pasta mode only"); + + 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: if (c->mode == MODE_PASTA) { err("--vhost-user is for passt mode only"); - usage(argv[0]); + usage(argv[0], stdout, EXIT_SUCCESS); } c->mode = MODE_VU; break; - case 21: + case 26: vu_print_capabilities(); break; case 'd': - if (c->debug) - die("Multiple --debug options given"); - - if (c->quiet) - die("Either --debug or --quiet"); - c->debug = 1; + c->quiet = 0; break; case 'e': - if (logfile) - die("Can't log to both file and stderr"); - - if (c->force_stderr) - die("Multiple --stderr options given"); - - c->force_stderr = 1; + warn("--stderr will be dropped soon"); break; case 'l': - if (c->force_stderr) - die("Can't log to both stderr and file"); - - if (logfile) - die("Multiple --log-file options given"); - logfile = optarg; break; case 'q': - if (c->quiet) - die("Multiple --quiet options given"); - - if (c->debug) - die("Either --debug or --quiet"); - c->quiet = 1; + c->debug = c->trace = 0; break; case 'f': - if (c->foreground) - die("Multiple --foreground options given"); - c->foreground = 1; break; case 's': - if (*c->sock_path) - die("Multiple --socket options given"); - - ret = snprintf(c->sock_path, UNIX_SOCK_MAX - 1, "%s", + ret = snprintf(c->sock_path, sizeof(c->sock_path), "%s", optarg); if (ret <= 0 || ret >= (int)sizeof(c->sock_path)) die("Invalid socket path: %s", optarg); + c->fd_tap = -1; break; case 'F': - if (c->fd_tap >= 0) - die("Multiple --fd options given"); - errno = 0; - c->fd_tap = strtol(optarg, NULL, 0); + fd_tap_opt = strtol(optarg, NULL, 0); - if (c->fd_tap < 0 || errno) + if (errno || + fd_tap_opt <= STDERR_FILENO || fd_tap_opt > INT_MAX) die("Invalid --fd: %s", optarg); + c->fd_tap = fd_tap_opt; c->one_off = true; - + *c->sock_path = 0; break; case 'I': - if (*c->pasta_ifn) - die("Multiple --ns-ifname options given"); - ret = snprintf(c->pasta_ifn, IFNAMSIZ, "%s", optarg); if (ret <= 0 || ret >= IFNAMSIZ) @@ -1428,28 +1563,19 @@ void conf(struct ctx *c, int argc, char **argv) break; case 'p': - if (*c->pcap) - die("Multiple --pcap options given"); - ret = snprintf(c->pcap, sizeof(c->pcap), "%s", optarg); if (ret <= 0 || ret >= (int)sizeof(c->pcap)) die("Invalid pcap path: %s", optarg); break; case 'P': - if (*c->pid_file) - die("Multiple --pid options given"); - - ret = snprintf(c->pid_file, sizeof(c->pid_file), "%s", + ret = snprintf(c->pidfile, sizeof(c->pidfile), "%s", optarg); - if (ret <= 0 || ret >= (int)sizeof(c->pid_file)) + if (ret <= 0 || ret >= (int)sizeof(c->pidfile)) die("Invalid PID file: %s", optarg); break; case 'm': - if (c->mtu) - die("Multiple --mtu options given"); - errno = 0; c->mtu = strtol(optarg, NULL, 0); @@ -1464,25 +1590,26 @@ void conf(struct ctx *c, int argc, char **argv) break; case 'a': - if (c->mode == MODE_PASTA) - c->no_copy_addrs = 1; - - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr) && - inet_pton(AF_INET6, optarg, &c->ip6.addr) && + 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)) + !IN6_IS_ADDR_MULTICAST(&c->ip6.addr)) { + if (c->mode == MODE_PASTA) + c->ip6.no_copy_addrs = true; break; + } - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr) && - inet_pton(AF_INET, optarg, &c->ip4.addr) && + 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)) + !IN4_IS_ADDR_MULTICAST(&c->ip4.addr)) { + if (c->mode == MODE_PASTA) + c->ip4.no_copy_addrs = true; break; + } die("Invalid address: %s", optarg); break; @@ -1493,45 +1620,34 @@ void conf(struct ctx *c, int argc, char **argv) break; case 'M': - for (i = 0; i < ETH_ALEN; i++) { - errno = 0; - b = strtol(optarg + (intptr_t)i * 3, NULL, 16); - if (b < 0 || b > UCHAR_MAX || errno) - die("Invalid MAC address: %s", optarg); - - c->mac[i] = b; - } + parse_mac(c->our_tap_mac, optarg); break; case 'g': - if (c->mode == MODE_PASTA) - c->no_copy_routes = 1; - - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.gw) && - 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 (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.gw) && - 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; + } die("Invalid gateway address: %s", optarg); break; case 'i': - if (ifi4 || ifi6) - die("Redundant interface: %s", optarg); - if (!(ifi4 = ifi6 = if_nametoindex(optarg))) - die("Invalid interface name %s: %s", optarg, - strerror(errno)); + die_perror("Invalid interface name %s", optarg); break; case 'o': - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out) && - inet_pton(AF_INET6, optarg, &c->ip6.addr_out) && + if (inet_pton(AF_INET6, optarg, &c->ip6.addr_out) && !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr_out) && !IN6_IS_ADDR_LOOPBACK(&c->ip6.addr_out) && !IN6_IS_ADDR_V4MAPPED(&c->ip6.addr_out) && @@ -1539,8 +1655,7 @@ void conf(struct ctx *c, int argc, char **argv) !IN6_IS_ADDR_MULTICAST(&c->ip6.addr_out)) break; - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out) && - inet_pton(AF_INET, optarg, &c->ip4.addr_out) && + if (inet_pton(AF_INET, optarg, &c->ip4.addr_out) && !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr_out) && !IN4_IS_ADDR_BROADCAST(&c->ip4.addr_out) && !IN4_IS_ADDR_MULTICAST(&c->ip4.addr_out)) @@ -1549,49 +1664,16 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid or redundant outbound address: %s", optarg); break; - case 'D': - if (!strcmp(optarg, "none")) { - if (c->no_dns) - die("Redundant DNS options"); - - if (dns4 - c->ip4.dns || dns6 - c->ip6.dns) - die("Conflicting DNS options"); - - c->no_dns = 1; - break; - } - - if (c->no_dns) - die("Conflicting DNS options"); - - if (dns4 - &c->ip4.dns[0] < ARRAY_SIZE(c->ip4.dns) && - inet_pton(AF_INET, optarg, dns4)) { - dns4++; - break; - } - - if (dns6 - &c->ip6.dns[0] < ARRAY_SIZE(c->ip6.dns) && - inet_pton(AF_INET6, optarg, dns6)) { - dns6++; - break; - } - - die("Cannot use DNS address %s", optarg); - break; case 'S': if (!strcmp(optarg, "none")) { - if (c->no_dns_search) - die("Redundant DNS search options"); + c->no_dns_search = 1; - if (dnss != c->dns_search) - die("Conflicting DNS search options"); + memset(c->dns_search, 0, sizeof(c->dns_search)); - c->no_dns_search = 1; break; } - if (c->no_dns_search) - die("Conflicting DNS search options"); + c->no_dns_search = 0; if (dnss - c->dns_search < ARRAY_SIZE(c->dns_search)) { ret = snprintf(dnss->n, sizeof(*c->dns_search), @@ -1607,42 +1689,35 @@ void conf(struct ctx *c, int argc, char **argv) break; case '4': v4_only = true; + v6_only = false; break; case '6': v6_only = true; + v4_only = false; break; case '1': if (c->mode == MODE_PASTA) die("--one-off is for passt mode only"); - if (c->one_off) - die("Redundant --one-off option"); - c->one_off = true; break; case 't': case 'u': case 'T': case 'U': + case 'D': /* Handle these later, once addresses are configured */ break; case 'h': - log_to_stdout = 1; - print_usage(argv[0], EXIT_SUCCESS); + usage(argv[0], stdout, EXIT_SUCCESS); break; case '?': default: - usage(argv[0]); + usage(argv[0], stderr, EXIT_FAILURE); break; } } while (name != -1); - if (v4_only && v6_only) - die("Options ipv4-only and ipv6-only are mutually exclusive"); - - if (*c->sock_path && c->fd_tap >= 0) - die("Options --socket and --fd are mutually exclusive"); - if (c->mode == MODE_PASTA && !c->pasta_conf_ns) { if (copy_routes_opt) die("--no-copy-routes needs --config-net"); @@ -1658,14 +1733,11 @@ void conf(struct ctx *c, int argc, char **argv) conf_ugid(runas, &uid, &gid); - if (logfile) { - logfile_init(c->mode == MODE_PASTA ? "pasta" : "passt", - logfile, logsize); - } + if (logfile) + logfile_init(logname, logfile, logsize); + else + __openlog(logname, 0, LOG_DAEMON); - /* Once the log mask is not LOG_EARLY, we will no longer log to stderr - * if there was a log file specified. - */ if (c->debug) __setlogmask(LOG_UPTO(LOG_DEBUG)); else if (c->quiet) @@ -1673,32 +1745,77 @@ void conf(struct ctx *c, int argc, char **argv) else __setlogmask(LOG_UPTO(LOG_INFO)); + log_conf_parsed = true; /* Stop printing everything */ + nl_sock_init(c, false); if (!v6_only) - c->ifi4 = conf_ip4(ifi4, &c->ip4, c->mac); + c->ifi4 = conf_ip4(ifi4, &c->ip4); if (!v4_only) - c->ifi6 = conf_ip6(ifi6, &c->ip6, c->mac); + c->ifi6 = conf_ip6(ifi6, &c->ip6); if ((!c->ifi4 && !c->ifi6) || (*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->ifi4 && !no_map_gw && + IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback)) + c->ip4.map_host_loopback = c->ip4.guest_gw; - if (c->ifi6 && IN6_IS_ADDR_UNSPECIFIED(&c->ip6.gw)) - c->no_map_gw = 1; + if (c->ifi6 && !no_map_gw && + IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) + c->ip6.map_host_loopback = c->ip6.guest_gw; - /* Inbound port options can be parsed now (after IPv4/IPv6 settings) */ + 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) + */ + fwd_probe_ephemeral(); udp_portmap_clear(); - optind = 1; + optind = 0; do { name = getopt_long(argc, argv, optstring, options, NULL); - if (name == 't') + 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.f); + } 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); + } } while (name != -1); if (c->mode == MODE_PASTA) @@ -1706,6 +1823,8 @@ void conf(struct ctx *c, int argc, char **argv) else if (optind != argc) die("Extra non-option argument: %s", argv[optind]); + conf_open_files(c); /* Before any possible setuid() / setgid() */ + isolate_user(uid, gid, !netns_only, userns, c->mode); if (c->pasta_conf_ns) @@ -1724,14 +1843,14 @@ void conf(struct ctx *c, int argc, char **argv) nl_sock_init(c, true); /* ...and outbound port options now that namespaces are set up. */ - optind = 1; + 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.f); + conf_ports(c, name, optarg, &c->udp.fwd_out); } while (name != -1); if (!c->ifi4) @@ -1758,10 +1877,10 @@ void conf(struct ctx *c, int argc, char **argv) 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.f.mode) - c->udp.fwd_in.f.mode = fwd_default; - if (!c->udp.fwd_out.f.mode) - c->udp.fwd_out.f.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; fwd_scan_ports_init(c); |