passt/conf.c
David Gibson 93bce404c1 Makefile: Move NETNS_RUN_DIR definition to C code
NETNS_RUN_DIR is set in the Makefile, then passed into the C code with
-D.  But NETNS_RUN_DIR is just a fixed string, it doesn't depend on any
make probes or variables, so there's really no reason to handle it via the
Makefile.  Just move it to a plain #define in conf.c.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-07 12:46:52 +01:00

1870 lines
50 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/* PASST - Plug A Simple Socket Transport
* for qemu/UNIX domain socket mode
*
* PASTA - Pack A Subtle Tap Abstraction
* for network namespace/tap device mode
*
* conf.c - Configuration settings and option parsing
*
* Copyright (c) 2020-2021 Red Hat GmbH
* Author: Stefano Brivio <sbrivio@redhat.com>
*/
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <string.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <syslog.h>
#include <time.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include "util.h"
#include "ip.h"
#include "passt.h"
#include "netlink.h"
#include "tap.h"
#include "udp.h"
#include "tcp.h"
#include "pasta.h"
#include "lineread.h"
#include "isolation.h"
#include "log.h"
#define NETNS_RUN_DIR "/run/netns"
/**
* next_chunk - Return the next piece of a string delimited by a character
* @s: String to search
* @c: Delimiter character
*
* Return: If another @c is found in @s, returns a pointer to the
* character *after* the delimiter, if no further @c is in @s,
* return NULL
*/
static char *next_chunk(const char *s, char c)
{
char *sep = strchr(s, c);
return sep ? sep + 1 : NULL;
}
/**
* port_range - Represents a non-empty range of ports
* @first: First port number in the range
* @last: Last port number in the range (inclusive)
*
* Invariant: @last >= @first
*/
struct port_range {
in_port_t first, last;
};
/**
* parse_port_range() - Parse a range of port numbers '<first>[-<last>]'
* @s: String to parse
* @endptr: Update to the character after the parsed range (similar to
* strtol() etc.)
* @range: Update with the parsed values on success
*
* Return: -EINVAL on parsing error, -ERANGE on out of range port
* numbers, 0 on success
*/
static int parse_port_range(const char *s, char **endptr,
struct port_range *range)
{
unsigned long first, last;
last = first = strtoul(s, endptr, 10);
if (*endptr == s) /* Parsed nothing */
return -EINVAL;
if (**endptr == '-') { /* we have a last value too */
const char *lasts = *endptr + 1;
last = strtoul(lasts, endptr, 10);
if (*endptr == lasts) /* Parsed nothing */
return -EINVAL;
}
if ((last < first) || (last >= NUM_PORTS))
return -ERANGE;
range->first = first;
range->last = last;
return 0;
}
/**
* conf_ports() - Parse port configuration options, initialise UDP/TCP sockets
* @c: Execution context
* @optname: Short option name, t, T, u, or U
* @optarg: Option argument (port specification)
* @fwd: Pointer to @fwd_ports to be updated
*/
static void conf_ports(const struct ctx *c, char optname, const char *optarg,
struct fwd_ports *fwd)
{
union inany_addr addr_buf = inany_any6, *addr = &addr_buf;
char buf[BUFSIZ], *spec, *ifname = NULL, *p;
bool exclude_only = true, bound_one = false;
uint8_t exclude[PORT_BITMAP_SIZE] = { 0 };
unsigned i;
int ret;
if (!strcmp(optarg, "none")) {
if (fwd->mode)
goto mode_conflict;
fwd->mode = FWD_NONE;
return;
}
if ((optname == 't' || optname == 'T') && c->no_tcp)
die("TCP port forwarding requested but TCP is disabled");
if ((optname == 'u' || optname == 'U') && c->no_udp)
die("UDP port forwarding requested but UDP is disabled");
if (!strcmp(optarg, "auto")) {
if (fwd->mode)
goto mode_conflict;
if (c->mode != MODE_PASTA)
die("'auto' port forwarding is only allowed for pasta");
fwd->mode = FWD_AUTO;
return;
}
if (!strcmp(optarg, "all")) {
if (fwd->mode)
goto mode_conflict;
if (c->mode == MODE_PASTA)
die("'all' port forwarding is only allowed for passt");
fwd->mode = FWD_ALL;
/* Skip port 0. It has special meaning for many socket APIs, so
* trying to bind it is not really safe.
*/
for (i = 1; i < NUM_PORTS; i++) {
if (fwd_port_is_ephemeral(i))
continue;
bitmap_set(fwd->map, i);
if (optname == 't') {
ret = tcp_sock_init(c, NULL, NULL, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
bound_one = true;
} else if (optname == 'u') {
ret = udp_sock_init(c, 0, NULL, NULL, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
bound_one = true;
}
}
if (!bound_one)
goto bind_all_fail;
return;
}
if (fwd->mode > FWD_SPEC)
die("Specific ports cannot be specified together with all/none/auto");
fwd->mode = FWD_SPEC;
strncpy(buf, optarg, sizeof(buf) - 1);
if ((spec = strchr(buf, '/'))) {
*spec = 0;
spec++;
if (optname != 't' && optname != 'u')
goto bad;
if ((ifname = strchr(buf, '%'))) {
*ifname = 0;
ifname++;
/* spec is already advanced one past the '/',
* so the length of the given ifname is:
* (spec - ifname - 1)
*/
if (spec - ifname - 1 >= IFNAMSIZ)
goto bad;
}
if (ifname == buf + 1) { /* Interface without address */
addr = NULL;
} else {
p = buf;
/* Allow square brackets for IPv4 too for convenience */
if (*p == '[' && p[strlen(p) - 1] == ']') {
p[strlen(p) - 1] = '\0';
p++;
}
if (!inany_pton(p, addr))
goto bad;
}
} else {
spec = buf;
addr = NULL;
}
/* Mark all exclusions first, they might be given after base ranges */
p = spec;
do {
struct port_range xrange;
if (*p != '~') {
/* Not an exclude range, parse later */
exclude_only = false;
continue;
}
p++;
if (parse_port_range(p, &p, &xrange))
goto bad;
if ((*p != '\0') && (*p != ',')) /* Garbage after the range */
goto bad;
for (i = xrange.first; i <= xrange.last; i++) {
if (bitmap_isset(exclude, i))
die("Overlapping excluded ranges %s", optarg);
bitmap_set(exclude, i);
}
} while ((p = next_chunk(p, ',')));
if (exclude_only) {
/* Skip port 0. It has special meaning for many socket APIs, so
* trying to bind it is not really safe.
*/
for (i = 1; i < NUM_PORTS; i++) {
if (fwd_port_is_ephemeral(i) ||
bitmap_isset(exclude, i))
continue;
bitmap_set(fwd->map, i);
if (optname == 't') {
ret = tcp_sock_init(c, addr, ifname, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
bound_one = true;
} else if (optname == 'u') {
ret = udp_sock_init(c, 0, addr, ifname, i);
if (ret == -ENFILE || ret == -EMFILE)
goto enfile;
if (!ret)
bound_one = true;
} else {
/* No way to check in advance for -T and -U */
bound_one = true;
}
}
if (!bound_one)
goto bind_all_fail;
return;
}
/* Now process base ranges, skipping exclusions */
p = spec;
do {
struct port_range orig_range, mapped_range;
if (*p == '~')
/* Exclude range, already parsed */
continue;
if (parse_port_range(p, &p, &orig_range))
goto bad;
if (*p == ':') { /* There's a range to map to as well */
if (parse_port_range(p + 1, &p, &mapped_range))
goto bad;
if ((mapped_range.last - mapped_range.first) !=
(orig_range.last - orig_range.first))
goto bad;
} else {
mapped_range = orig_range;
}
if ((*p != '\0') && (*p != ',')) /* Garbage after the ranges */
goto bad;
for (i = orig_range.first; i <= orig_range.last; i++) {
if (bitmap_isset(fwd->map, i))
warn(
"Altering mapping of already mapped port number: %s", optarg);
if (bitmap_isset(exclude, i))
continue;
bitmap_set(fwd->map, i);
fwd->delta[i] = mapped_range.first - orig_range.first;
ret = 0;
if (optname == 't')
ret = tcp_sock_init(c, addr, ifname, i);
else if (optname == 'u')
ret = udp_sock_init(c, 0, addr, ifname, i);
if (ret)
goto bind_fail;
}
} while ((p = next_chunk(p, ',')));
return;
enfile:
die("Can't open enough sockets for port specifier: %s", optarg);
bad:
die("Invalid port specifier %s", optarg);
mode_conflict:
die("Port forwarding mode '%s' conflicts with previous mode", optarg);
bind_fail:
die("Failed to bind port %u (%s) for option '-%c %s', exiting",
i, strerror(-ret), optname, optarg);
bind_all_fail:
die("Failed to bind any port for '-%c %s', exiting", optname, optarg);
}
/**
* 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: Number of entries added (0 or 1)
*/
static unsigned add_dns4(struct ctx *c, const struct in_addr *addr,
unsigned idx)
{
if (idx >= ARRAY_SIZE(c->ip4.dns))
return 0;
c->ip4.dns[idx] = *addr;
return 1;
}
/**
* 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: Number of entries added (0 or 1)
*/
static unsigned add_dns6(struct ctx *c, const struct in6_addr *addr,
unsigned idx)
{
if (idx >= ARRAY_SIZE(c->ip6.dns))
return 0;
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;
}
*idx4 += add_dns4(c, &ns4, *idx4);
}
if (idx6 && inet_pton(AF_INET6, nameserver, &ns6)) {
if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns_host))
c->ip6.dns_host = ns6;
/* 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);
}
}
/**
* get_dns() - Get nameserver addresses from local /etc/resolv.conf
* @c: Execution context
*/
static void get_dns(struct ctx *c)
{
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;
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;
if (dns_set && dnss_set)
return;
if ((fd = open("/etc/resolv.conf", O_RDONLY | O_CLOEXEC)) < 0)
goto out;
lineread_init(&resolvconf, fd);
while ((line_len = lineread_get(&resolvconf, &line)) > 0) {
if (!dns_set && strstr(line, "nameserver ") == line) {
p = strrchr(line, ' ');
if (!p)
continue;
end = strpbrk(line, "%\n");
if (end)
*end = 0;
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");
if (end)
*end = 0;
/* cppcheck-suppress strtokCalled */
if (!strtok(line, " \t"))
continue;
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]) - 1);
s++;
*s->n = 0;
}
}
}
if (line_len < 0)
warn_perror("Error reading /etc/resolv.conf");
close(fd);
out:
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");
}
}
/**
* conf_netns_opt() - Parse --netns option
* @netns: buffer of size PATH_MAX, updated with netns path
* @arg: --netns argument
*/
static void conf_netns_opt(char *netns, const char *arg)
{
int ret;
if (!strchr(arg, '/')) {
/* looks like a netns name */
ret = snprintf(netns, PATH_MAX, "%s/%s", NETNS_RUN_DIR, arg);
} else {
/* otherwise assume it's a netns path */
ret = snprintf(netns, PATH_MAX, "%s", arg);
}
if (ret <= 0 || ret > PATH_MAX)
die("Network namespace name/path %s too long", arg);
}
/**
* conf_pasta_ns() - Validate all pasta namespace options
* @netns_only: Don't use userns, may be updated
* @userns: buffer of size PATH_MAX, initially contains --userns
* argument (may be empty), updated with userns path
* @netns: buffer of size PATH_MAX, initial contains --netns
* argument (may be empty), updated with netns path
* @optind: Index of first non-option argument
* @argc: Number of arguments
* @argv: Command line arguments
*/
static void conf_pasta_ns(int *netns_only, char *userns, char *netns,
int optind, int argc, char *argv[])
{
if (*netns && optind != argc)
die("Both --netns and PID or command given");
if (optind + 1 == argc) {
char *endptr;
long pidval;
pidval = strtol(argv[optind], &endptr, 10);
if (!*endptr) {
/* Looks like a pid */
if (pidval < 0 || pidval > INT_MAX)
die("Invalid PID %s", argv[optind]);
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");
}
}
}
/* Attaching to a netns/PID, with no userns given */
if (*netns && !*userns)
*netns_only = 1;
}
/** 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
*/
static int conf_ip4_prefix(const char *arg)
{
struct in_addr mask;
unsigned long len;
if (inet_pton(AF_INET, arg, &mask)) {
in_addr_t hmask = ntohl(mask.s_addr);
len = __builtin_popcount(hmask);
if ((hmask << len) != 0)
return -1;
} else {
errno = 0;
len = strtoul(optarg, NULL, 0);
if (len > 32 || errno)
return -1;
}
return len;
}
/**
* 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)
*
* Return: Interface index for IPv4, or 0 on failure.
*/
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");
return 0;
}
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));
return 0;
}
}
if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) {
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));
return 0;
}
}
if (!ip4->prefix_len) {
in_addr_t addr = ntohl(ip4->addr.s_addr);
if (IN_CLASSA(addr))
ip4->prefix_len = (32 - IN_CLASSA_NSHIFT);
else if (IN_CLASSB(addr))
ip4->prefix_len = (32 - IN_CLASSB_NSHIFT);
else if (IN_CLASSC(addr))
ip4->prefix_len = (32 - IN_CLASSC_NSHIFT);
else
ip4->prefix_len = 32;
}
ip4->addr_seen = ip4->addr;
ip4->our_tap_addr = ip4->guest_gw;
if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr))
return 0;
return 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)
*
* Return: Interface index for IPv6, or 0 on failure.
*/
static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6)
{
int prefix_len = 0;
int rc;
if (!ifi)
ifi = nl_get_ext_if(nl_sock, AF_INET6);
if (!ifi) {
info("Couldn't pick external interface: disabling IPv6");
return 0;
}
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));
return 0;
}
}
rc = nl_addr_get(nl_sock, ifi, AF_INET6,
IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ? &ip6->addr : NULL,
&prefix_len, &ip6->our_tap_ll);
if (rc < 0) {
err("Couldn't discover IPv6 address: %s", strerror(-rc));
return 0;
}
ip6->addr_seen = ip6->addr;
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->our_tap_ll))
return 0;
return ifi;
}
/**
* 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 usage(const char *name, FILE *f, int status)
{
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,
"\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 {
FPRINTF(f, "Usage: %s [OPTION]...\n", name);
}
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")) {
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"
" default: probe free path starting from "
UNIX_SOCK_PATH "\n", 1);
}
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"))
FPRINTF(f, " default: don't use any addresses\n");
else
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"))
FPRINTF(f, " default: don't use any search list\n");
else
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");
else
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");
else
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;
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:
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);
}
/**
* conf_print() - Print fundamental configuration parameters
* @c: Execution context
*/
static void conf_print(const struct ctx *c)
{
char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN];
char bufmac[ETH_ADDRSTRLEN], ifn[IFNAMSIZ];
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->ip4.ifname_out || *c->ip6.ifname_out) {
info("Outbound interface: %s%s%s%s%s",
*c->ip4.ifname_out ? c->ip4.ifname_out : "",
*c->ip4.ifname_out ? " (IPv4)" : "",
(*c->ip4.ifname_out && *c->ip6.ifname_out) ? ", " : "",
*c->ip6.ifname_out ? c->ip6.ifname_out : "",
*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 (c->mode == MODE_PASTA)
info("Namespace interface: %s", c->pasta_ifn);
info("MAC:");
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;
mask = htonl(0xffffffff << (32 - c->ip4.prefix_len));
info("DHCP:");
info(" assign: %s",
inet_ntop(AF_INET, &c->ip4.addr, buf4, sizeof(buf4)));
info(" mask: %s",
inet_ntop(AF_INET, &mask, buf4, sizeof(buf4)));
info(" router: %s",
inet_ntop(AF_INET, &c->ip4.guest_gw,
buf4, sizeof(buf4)));
}
for (i = 0; !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i]); i++) {
if (!i)
info("DNS:");
inet_ntop(AF_INET, &c->ip4.dns[i], buf4, sizeof(buf4));
info(" %s", buf4);
}
for (i = 0; *c->dns_search[i].n; i++) {
if (!i)
info("DNS search list:");
info(" %s", c->dns_search[i].n);
}
}
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)
info("DHCPv6:");
else if (!c->no_dhcpv6)
info("NDP:");
else
goto dns6;
info(" assign: %s",
inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6)));
info(" router: %s",
inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6)));
info(" our link-local: %s",
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++) {
if (!i)
info("DNS:");
inet_ntop(AF_INET6, &c->ip6.dns[i], buf6, sizeof(buf6));
info(" %s", buf6);
}
for (i = 0; *c->dns_search[i].n; i++) {
if (!i)
info("DNS search list:");
info(" %s", c->dns_search[i].n);
}
}
}
/**
* conf_runas() - Handle --runas: look up desired UID and GID
* @opt: Passed option value
* @uid: User ID, set on return if valid
* @gid: Group ID, set on return if valid
*
* Return: 0 on success, negative error code on failure
*/
static int conf_runas(char *opt, unsigned int *uid, unsigned int *gid)
{
const char *uopt, *gopt = NULL;
char *sep = strchr(opt, ':');
char *endptr;
if (sep) {
*sep = '\0';
gopt = sep + 1;
}
uopt = opt;
*gid = *uid = strtol(uopt, &endptr, 0);
if (*endptr) {
#ifndef GLIBC_NO_STATIC_NSS
/* Not numeric, look up as a username */
const struct passwd *pw;
/* cppcheck-suppress getpwnamCalled */
if (!(pw = getpwnam(uopt)) || !(*uid = pw->pw_uid))
return -ENOENT;
*gid = pw->pw_gid;
#else
return -EINVAL;
#endif
}
if (!gopt)
return 0;
*gid = strtol(gopt, &endptr, 0);
if (*endptr) {
#ifndef GLIBC_NO_STATIC_NSS
/* Not numeric, look up as a group name */
const struct group *gr;
/* cppcheck-suppress getgrnamCalled */
if (!(gr = getgrnam(gopt)))
return -ENOENT;
*gid = gr->gr_gid;
#else
return -EINVAL;
#endif
}
return 0;
}
/**
* conf_ugid() - Determine UID and GID to run as
* @runas: --runas option, may be NULL
* @uid: User ID, set on success
* @gid: Group ID, set on success
*/
static void conf_ugid(char *runas, uid_t *uid, gid_t *gid)
{
/* If user has specified --runas, that takes precedence... */
if (runas) {
if (conf_runas(runas, uid, gid))
die("Invalid --runas option: %s", runas);
return;
}
/* ...otherwise default to current user and group... */
*uid = geteuid();
*gid = getegid();
/* ...as long as it's not root... */
if (*uid)
return;
/* ...or at least not root in the init namespace... */
if (!ns_is_init())
return;
/* ...otherwise use nobody: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)
die_perror("Can't get password file entry for nobody");
*uid = pw->pw_uid;
*gid = pw->pw_gid;
#else
/* Common value for 'nobody', not really specified */
*uid = *gid = 65534;
#endif
}
}
/**
* 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
* @argv: Options, plus target PID for pasta mode
*/
void conf(struct ctx *c, int argc, char **argv)
{
int netns_only = 0, no_map_gw = 0;
const struct option options[] = {
{"debug", no_argument, NULL, 'd' },
{"quiet", no_argument, NULL, 'q' },
{"foreground", no_argument, NULL, 'f' },
{"stderr", no_argument, NULL, 'e' },
{"log-file", required_argument, NULL, 'l' },
{"help", no_argument, NULL, 'h' },
{"socket", required_argument, NULL, 's' },
{"fd", required_argument, NULL, 'F' },
{"ns-ifname", required_argument, NULL, 'I' },
{"pcap", required_argument, NULL, 'p' },
{"pid", required_argument, NULL, 'P' },
{"mtu", required_argument, NULL, 'm' },
{"address", required_argument, NULL, 'a' },
{"netmask", required_argument, NULL, 'n' },
{"mac-addr", required_argument, NULL, 'M' },
{"gateway", required_argument, NULL, 'g' },
{"interface", required_argument, NULL, 'i' },
{"outbound", required_argument, NULL, 'o' },
{"dns", required_argument, NULL, 'D' },
{"search", required_argument, NULL, 'S' },
{"no-tcp", no_argument, &c->no_tcp, 1 },
{"no-udp", no_argument, &c->no_udp, 1 },
{"no-icmp", no_argument, &c->no_icmp, 1 },
{"no-dhcp", no_argument, &c->no_dhcp, 1 },
{"no-dhcpv6", no_argument, &c->no_dhcpv6, 1 },
{"no-ndp", no_argument, &c->no_ndp, 1 },
{"no-ra", no_argument, &c->no_ra, 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' },
{"tcp-ports", required_argument, NULL, 't' },
{"udp-ports", required_argument, NULL, 'u' },
{"tcp-ns", required_argument, NULL, 'T' },
{"udp-ns", required_argument, NULL, 'U' },
{"userns", required_argument, NULL, 2 },
{"netns", required_argument, NULL, 3 },
{"ns-mac-addr", required_argument, NULL, 4 },
{"dhcp-dns", no_argument, NULL, 5 },
{"no-dhcp-dns", no_argument, NULL, 6 },
{"dhcp-search", no_argument, NULL, 7 },
{"no-dhcp-search", no_argument, NULL, 8 },
{"dns-forward", required_argument, NULL, 9 },
{"no-netns-quit", no_argument, NULL, 10 },
{"trace", no_argument, NULL, 11 },
{"runas", required_argument, NULL, 12 },
{"log-size", required_argument, NULL, 13 },
{"version", no_argument, NULL, 14 },
{"outbound-if4", required_argument, NULL, 15 },
{"outbound-if6", required_argument, NULL, 16 },
{"config-net", no_argument, NULL, 17 },
{"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 },
{ 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;
unsigned dns4_idx = 0, dns6_idx = 0;
struct fqdn *dnss = c->dns_search;
unsigned int ifi4 = 0, ifi6 = 0;
const char *logfile = NULL;
const char *optstring;
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:";
} 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;
memcpy(c->our_tap_mac, MAC_OUR_LAA, ETH_ALEN);
optind = 0;
do {
name = getopt_long(argc, argv, optstring, options, NULL);
switch (name) {
case -1:
case 0:
break;
case 2:
if (c->mode != MODE_PASTA)
die("--userns is for pasta mode only");
ret = snprintf(userns, sizeof(userns), "%s", optarg);
if (ret <= 0 || ret >= (int)sizeof(userns))
die("Invalid userns: %s", optarg);
netns_only = 0;
break;
case 3:
if (c->mode != MODE_PASTA)
die("--netns is for pasta mode only");
conf_netns_opt(netns, optarg);
break;
case 4:
if (c->mode != MODE_PASTA)
die("--ns-mac-addr is for pasta mode only");
parse_mac(c->guest_mac, optarg);
break;
case 5:
if (c->mode != MODE_PASTA)
die("--dhcp-dns is for pasta mode only");
c->no_dhcp_dns = 0;
break;
case 6:
if (c->mode == MODE_PASTA)
die("--no-dhcp-dns is for passt mode only");
c->no_dhcp_dns = 1;
break;
case 7:
if (c->mode != MODE_PASTA)
die("--dhcp-search is for pasta mode only");
c->no_dhcp_dns_search = 0;
break;
case 8:
if (c->mode == MODE_PASTA)
die("--no-dhcp-search is for passt mode only");
c->no_dhcp_dns_search = 1;
break;
case 9:
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 (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))
break;
die("Invalid DNS forwarding address: %s", optarg);
break;
case 10:
if (c->mode != MODE_PASTA)
die("--no-netns-quit is for pasta mode only");
c->no_netns_quit = 1;
break;
case 11:
c->trace = c->debug = 1;
c->quiet = 0;
break;
case 12:
runas = optarg;
break;
case 13:
errno = 0;
logsize = strtol(optarg, NULL, 0);
if (logsize < LOGFILE_SIZE_MIN || errno)
die("Invalid --log-size: %s", optarg);
break;
case 14:
FPRINTF(stdout,
c->mode == MODE_PASTA ? "pasta " : "passt ");
FPRINTF(stdout, VERSION_BLOB);
exit(EXIT_SUCCESS);
case 15:
ret = snprintf(c->ip4.ifname_out,
sizeof(c->ip4.ifname_out), "%s", optarg);
if (ret <= 0 || ret >= (int)sizeof(c->ip4.ifname_out))
die("Invalid interface name: %s", optarg);
break;
case 16:
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)
die("--config-net is for pasta mode only");
c->pasta_conf_ns = 1;
break;
case 18:
if (c->mode != MODE_PASTA)
die("--no-copy-routes is for pasta mode only");
warn("--no-copy-routes will be dropped soon");
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->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);
break;
case 'd':
c->debug = 1;
c->quiet = 0;
break;
case 'e':
warn("--stderr will be dropped soon");
break;
case 'l':
logfile = optarg;
break;
case 'q':
c->quiet = 1;
c->debug = c->trace = 0;
break;
case 'f':
c->foreground = 1;
break;
case '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':
errno = 0;
fd_tap_opt = strtol(optarg, NULL, 0);
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':
ret = snprintf(c->pasta_ifn, IFNAMSIZ, "%s",
optarg);
if (ret <= 0 || ret >= IFNAMSIZ)
die("Invalid interface name: %s", optarg);
break;
case 'p':
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':
ret = snprintf(c->pidfile, sizeof(c->pidfile), "%s",
optarg);
if (ret <= 0 || ret >= (int)sizeof(c->pidfile))
die("Invalid PID file: %s", optarg);
break;
case 'm':
errno = 0;
c->mtu = strtol(optarg, NULL, 0);
if (!c->mtu) {
c->mtu = -1;
break;
}
if (c->mtu < ETH_MIN_MTU || c->mtu > (int)ETH_MAX_MTU ||
errno)
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 (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)) {
if (c->mode == MODE_PASTA)
c->ip4.no_copy_addrs = true;
break;
}
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);
break;
case 'M':
parse_mac(c->our_tap_mac, optarg);
break;
case 'g':
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.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 = if_nametoindex(optarg)))
die_perror("Invalid interface name %s", optarg);
break;
case 'o':
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) &&
!IN6_IS_ADDR_V4COMPAT(&c->ip6.addr_out) &&
!IN6_IS_ADDR_MULTICAST(&c->ip6.addr_out))
break;
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))
break;
die("Invalid or redundant outbound address: %s",
optarg);
break;
case 'S':
if (!strcmp(optarg, "none")) {
c->no_dns_search = 1;
memset(c->dns_search, 0, sizeof(c->dns_search));
break;
}
c->no_dns_search = 0;
if (dnss - c->dns_search < ARRAY_SIZE(c->dns_search)) {
ret = snprintf(dnss->n, sizeof(*c->dns_search),
"%s", optarg);
dnss++;
if (ret > 0 &&
ret < (int)sizeof(*c->dns_search))
break;
}
die("Cannot use DNS search domain %s", optarg);
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");
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':
usage(argv[0], stdout, EXIT_SUCCESS);
break;
case '?':
default:
usage(argv[0], stderr, EXIT_FAILURE);
break;
}
} while (name != -1);
if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
if (copy_routes_opt)
die("--no-copy-routes needs --config-net");
if (copy_addrs_opt)
die("--no-copy-addrs needs --config-net");
}
if (!ifi4 && *c->ip4.ifname_out)
ifi4 = if_nametoindex(c->ip4.ifname_out);
if (!ifi6 && *c->ip6.ifname_out)
ifi6 = if_nametoindex(c->ip6.ifname_out);
conf_ugid(runas, &uid, &gid);
if (logfile)
logfile_init(logname, logfile, logsize);
else
__openlog(logname, 0, LOG_DAEMON);
if (c->debug)
__setlogmask(LOG_UPTO(LOG_DEBUG));
else if (c->quiet)
__setlogmask(LOG_UPTO(LOG_WARNING));
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);
if (!v4_only)
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 && !no_map_gw &&
IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
c->ip4.map_host_loopback = c->ip4.guest_gw;
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 (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 = 0;
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;
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)
conf_pasta_ns(&netns_only, userns, netns, optind, argc, 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)
c->no_ra = 1;
if (c->mode == MODE_PASTA) {
if (*netns) {
pasta_open_ns(c, netns);
} else {
pasta_start_ns(c, uid, gid,
argc - optind, argv + optind);
}
}
if (c->mode == MODE_PASTA)
nl_sock_init(c, true);
/* ...and outbound port options now that namespaces are set up. */
optind = 0;
do {
name = getopt_long(argc, argv, optstring, options, NULL);
if (name == 'T')
conf_ports(c, name, optarg, &c->tcp.fwd_out);
else if (name == 'U')
conf_ports(c, name, optarg, &c->udp.fwd_out);
} while (name != -1);
if (!c->ifi4)
c->no_dhcp = 1;
if (!c->ifi6) {
c->no_ndp = 1;
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_indextoname(c->ifi4, c->pasta_ifn);
else
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;
fwd_scan_ports_init(c);
if (!c->quiet)
conf_print(c);
}