// SPDX-License-Identifier: AGPL-3.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>
*/
#define _GNU_SOURCE
#include <sched.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <syslog.h>
#include <time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/if_ether.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "util.h"
#include "passt.h"
#include "udp.h"
#include "tcp.h"
/**
* get_bound_ports() - Get maps of ports with bound sockets
* @c: Execution context
* @ns: If set, set bitmaps for ports to tap/ns -- to init otherwise
* @proto: Protocol number (IPPROTO_TCP or IPPROTO_UDP)
*/
void get_bound_ports(struct ctx *c, int ns, uint8_t proto)
{
uint8_t *udp_map, *udp_exclude, *tcp_map, *tcp_exclude;
if (ns) {
udp_map = c->udp.port_to_tap;
udp_exclude = c->udp.port_to_init;
tcp_map = c->tcp.port_to_tap;
tcp_exclude = c->tcp.port_to_init;
} else {
udp_map = c->udp.port_to_init;
udp_exclude = c->udp.port_to_tap;
tcp_map = c->tcp.port_to_init;
tcp_exclude = c->tcp.port_to_tap;
}
if (proto == IPPROTO_UDP) {
memset(udp_map, 0, USHRT_MAX / 8);
procfs_scan_listen("udp", udp_map, udp_exclude);
procfs_scan_listen("udp6", udp_map, udp_exclude);
procfs_scan_listen("tcp", udp_map, udp_exclude);
procfs_scan_listen("tcp6", udp_map, udp_exclude);
} else if (proto == IPPROTO_TCP) {
memset(tcp_map, 0, USHRT_MAX / 8);
procfs_scan_listen("tcp", tcp_map, tcp_exclude);
procfs_scan_listen("tcp6", tcp_map, tcp_exclude);
}
}
/**
* struct get_bound_ports_ns_arg - Arguments for get_bound_ports_ns()
* @c: Execution context
* @proto: Protocol number (IPPROTO_TCP or IPPROTO_UDP)
*/
struct get_bound_ports_ns_arg {
struct ctx *c;
uint8_t proto;
};
/**
* get_bound_ports_ns() - Get maps of ports in namespace with bound sockets
* @arg: See struct get_bound_ports_ns_arg
*
* Return: 0
*/
static int get_bound_ports_ns(void *arg)
{
struct get_bound_ports_ns_arg *a = (struct get_bound_ports_ns_arg *)arg;
struct ctx *c = a->c;
if (!c->pasta_netns_fd || ns_enter(c))
return 0;
get_bound_ports(c, 1, a->proto);
return 0;
}
enum conf_port_type {
PORT_SPEC = 1,
PORT_NONE,
PORT_AUTO,
PORT_ALL,
};
static int conf_ports(struct ctx *c, char optname, const char *optarg,
enum conf_port_type *set)
{
int start_src = -1, end_src = -1, start_dst = -1, end_dst = -1;
void (*remap)(in_port_t port, in_port_t delta);
const char *p;
uint8_t *map;
char *sep;
if (optname == 't') {
map = c->tcp.port_to_tap;
remap = tcp_remap_to_tap;
} else if (optname == 'T') {
map = c->tcp.port_to_init;
remap = tcp_remap_to_init;
} else if (optname == 'u') {
map = c->udp.port_to_tap;
remap = udp_remap_to_tap;
} else if (optname == 'U') {
map = c->udp.port_to_init;
remap = udp_remap_to_init;
} else { /* For gcc -O3 */
return 0;
}
if (!strcmp(optarg, "none")) {
if (*set)
return -EINVAL;
*set = PORT_NONE;
return 0;
}
if (!strcmp(optarg, "auto")) {
if (*set || c->mode != MODE_PASTA)
return -EINVAL;
*set = PORT_AUTO;
return 0;
}
if (!strcmp(optarg, "all")) {
if (*set || c->mode != MODE_PASST)
return -EINVAL;
*set = PORT_ALL;
memset(map, 0xff, PORT_EPHEMERAL_MIN / 8);
return 0;
}
if (*set > PORT_SPEC)
return -EINVAL;
*set = PORT_SPEC;
if (strspn(optarg, "0123456789-,:") != strlen(optarg)) {
err("Invalid port specifier %s", optarg);
return -EINVAL;
}
p = optarg;
do {
int i, port;
port = strtol(p, &sep, 10);
if (sep == p)
break;
if (port < 0 || port > USHRT_MAX || errno)
goto bad;
/* -p 22
* ^ start_src end_src == start_dst == end_dst == -1
*
* -p 22-25
* | ^ end_src
* ` start_src start_dst == end_dst == -1
*
* -p 80:8080
* | ^ start_dst
* ` start_src end_src == end_dst == -1
*
* -p 22-80:8022-8080
* | | | ^ end_dst
* | | ` start_dst
* | ` end_dst
* ` start_src
*/
switch (*sep) {
case '-':
if (start_src == -1) { /* 22-... */
start_src = port;
} else {
if (!end_src) /* 22:8022-8080 */
goto bad;
start_dst = port; /* 22-80:8022-... */
}
break;
case ':':
if (start_src == -1) /* 80:... */
start_src = end_src = port;
else if (end_src == -1) /* 22-80:... */
end_src = port;
else /* 22-80:8022:... */
goto bad;
break;
case ',':
case 0:
if (start_src == -1) /* 80 */
start_src = end_src = port;
else if (end_src == -1) /* 22-25 */
end_src = port;
else if (start_dst == -1) /* 80:8080 */
start_dst = end_dst = port;
else if (end_dst == -1) /* 22-80:8022-8080 */
end_dst = port;
else
goto bad;
if (start_src > end_src) /* 80-22 */
goto bad;
if (start_dst > end_dst) /* 22-80:8080:8022 */
goto bad;
if (end_dst != -1 &&
end_dst - start_dst != end_src - start_src)
goto bad; /* 22-81:8022:8080 */
for (i = start_src; i <= end_src; i++) {
if (bitmap_isset(map, i))
goto overlap;
bitmap_set(map, i);
if (start_dst == -1) /* 22 or 22-80 */
continue;
/* 80:8080 or 22-80:8080:8080 */
remap(i, (in_port_t)(start_dst - start_src));
}
start_src = end_src = start_dst = end_dst = -1;
break;
}
p = sep + 1;
} while (*sep);
return 0;
bad:
err("Invalid port specifier %s", optarg);
return -EINVAL;
overlap:
err("Overlapping port specifier %s", optarg);
return -EINVAL;
}
/**
* nl_req() - Send netlink request and read response, doesn't return on failure
* @buf: Buffer for response (BUFSIZ long)
* @req: Request with netlink header
* @len: Request length
*/
static void nl_req(char *buf, void *req, ssize_t len)
{
int s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE), v = 1;
struct sockaddr_nl addr = { .nl_family = AF_NETLINK, };
if (s < 0 ||
setsockopt(s, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &v, sizeof(v)) ||
bind(s, (struct sockaddr *)&addr, sizeof(addr)) ||
(send(s, req, len, 0) < len) ||
(recv(s, buf, BUFSIZ, 0) < 0)) {
perror("netlink recv");
exit(EXIT_FAILURE);
}
close(s);
}
/**
* get_routes() - Get default route and fill in routable interface name
* @c: Execution context
*/
static void get_routes(struct ctx *c)
{
struct { struct nlmsghdr nlh; struct rtmsg rtm; } req = {
.nlh.nlmsg_type = RTM_GETROUTE,
.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_EXCL,
.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
.nlh.nlmsg_seq = 1,
.rtm.rtm_family = AF_INET,
.rtm.rtm_table = RT_TABLE_MAIN,
.rtm.rtm_scope = RT_SCOPE_UNIVERSE,
.rtm.rtm_type = RTN_UNICAST,
};
char ifn[IFNAMSIZ], buf[BUFSIZ];
struct nlmsghdr *nh;
struct rtattr *rta;
struct rtmsg *rtm;
int n, na, v4, v6;
if (!c->v4 && !c->v6)
v4 = v6 = -1;
else
v6 = -!(v4 = -c->v4);
v6:
nl_req(buf, &req, sizeof(req));
nh = (struct nlmsghdr *)buf;
for ( ; NLMSG_OK(nh, n); nh = NLMSG_NEXT(nh, n)) {
rtm = (struct rtmsg *)NLMSG_DATA(nh);
if (rtm->rtm_dst_len ||
(rtm->rtm_family != AF_INET && rtm->rtm_family != AF_INET6))
continue;
/* Filter on interface only if already given */
if (*c->ifn) {
*ifn = 0;
for (rta = (struct rtattr *)RTM_RTA(rtm),
na = RTM_PAYLOAD(nh);
RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) {
if (rta->rta_type != RTA_OIF)
continue;
if_indextoname(*(unsigned *)RTA_DATA(rta), ifn);
break;
}
if (strcmp(ifn, c->ifn))
goto next;
}
for (rta = (struct rtattr *)RTM_RTA(rtm), na = RTM_PAYLOAD(nh);
RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) {
if (!*c->ifn && rta->rta_type == RTA_OIF)
if_indextoname(*(unsigned *)RTA_DATA(rta), ifn);
if (v4 && rta->rta_type == RTA_GATEWAY &&
rtm->rtm_family == AF_INET) {
if (!c->gw4) {
memcpy(&c->gw4, RTA_DATA(rta),
sizeof(c->gw4));
}
v4 = 1;
}
if (v6 && rta->rta_type == RTA_GATEWAY &&
rtm->rtm_family == AF_INET6) {
if (IN6_IS_ADDR_UNSPECIFIED(&c->gw6)) {
memcpy(&c->gw6, RTA_DATA(rta),
sizeof(c->gw6));
}
v6 = 1;
}
}
next:
if (nh->nlmsg_type == NLMSG_DONE)
break;
}
if (v6 < 0 && req.rtm.rtm_family == AF_INET) {
req.rtm.rtm_family = AF_INET6;
req.nlh.nlmsg_seq++;
goto v6;
} else if (v6 < 0) {
v6 = 0;
}
if ((v4 <= 0 && v6 <= 0) || (!*c->ifn && !*ifn)) {
err("No routing information");
exit(EXIT_FAILURE);
}
if (!*c->ifn)
strncpy(c->ifn, ifn, IFNAMSIZ);
c->v4 = v4;
c->v6 = v6;
}
/**
* get_l3_addrs() - Fetch IP addresses of external routable interface
* @c: Execution context
*/
static void get_l3_addrs(struct ctx *c)
{
struct { struct nlmsghdr nlh; struct ifaddrmsg ifa; } req = {
.nlh.nlmsg_type = RTM_GETADDR,
.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
.nlh.nlmsg_seq = 1,
.ifa.ifa_family = AF_INET,
.ifa.ifa_index = if_nametoindex(c->ifn),
};
struct ifaddrmsg *ifa;
struct nlmsghdr *nh;
struct rtattr *rta;
int n, na, v4, v6;
char buf[BUFSIZ];
if (c->v4) {
v4 = -1;
if ((c->addr4_seen = c->addr4))
v4 = 1;
}
if (c->v6) {
v6 = -2;
if (!IN6_IS_ADDR_UNSPECIFIED(&c->addr6)) {
memcpy(&c->addr6_seen, &c->addr6, sizeof(c->addr6));
memcpy(&c->addr6_ll_seen, &c->addr6, sizeof(c->addr6));
v6 = -1;
}
}
next_v:
if (v4 < 0)
req.ifa.ifa_family = AF_INET;
else if (v6 < 0)
req.ifa.ifa_family = AF_INET6;
else
goto mask_only;
nl_req(buf, &req, sizeof(req));
nh = (struct nlmsghdr *)buf;
for ( ; NLMSG_OK(nh, n); nh = NLMSG_NEXT(nh, n)) {
if (nh->nlmsg_type != RTM_NEWADDR)
goto next;
ifa = (struct ifaddrmsg *)NLMSG_DATA(nh);
for (rta = (struct rtattr *)IFA_RTA(ifa), na = RTM_PAYLOAD(nh);
RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) {
if (rta->rta_type != IFA_ADDRESS)
continue;
if (v4 < 0) {
memcpy(&c->addr4, RTA_DATA(rta),
sizeof(c->addr4));
memcpy(&c->addr4_seen, RTA_DATA(rta),
sizeof(c->addr4_seen));
v4 = 1;
} else if (v6 < 0) {
if (v6 == -2 &&
ifa->ifa_scope == RT_SCOPE_UNIVERSE) {
memcpy(&c->addr6, RTA_DATA(rta),
sizeof(c->addr6));
memcpy(&c->addr6_seen, RTA_DATA(rta),
sizeof(c->addr6_seen));
memcpy(&c->addr6_ll_seen, RTA_DATA(rta),
sizeof(c->addr6_ll_seen));
} else if (ifa->ifa_scope == RT_SCOPE_LINK) {
memcpy(&c->addr6_ll, RTA_DATA(rta),
sizeof(c->addr6_ll));
}
if (!IN6_IS_ADDR_UNSPECIFIED(&c->addr6) &&
!IN6_IS_ADDR_UNSPECIFIED(&c->addr6_ll))
v6 = 1;
}
}
next:
if (nh->nlmsg_type == NLMSG_DONE)
break;
}
if (v4 >= 0 && v6 < 0)
goto next_v;
if (v4 < c->v4 || v6 < c->v6)
goto out;
mask_only:
if (v4 && !c->mask4) {
if (IN_CLASSA(ntohl(c->addr4)))
c->mask4 = htonl(IN_CLASSA_NET);
else if (IN_CLASSB(ntohl(c->addr4)))
c->mask4 = htonl(IN_CLASSB_NET);
else if (IN_CLASSC(ntohl(c->addr4)))
c->mask4 = htonl(IN_CLASSC_NET);
else
c->mask4 = 0xffffffff;
}
return;
out:
err("Couldn't get addresses for routable interface");
exit(EXIT_FAILURE);
}
/**
* get_l2_addr() - Fetch hardware addresses of external routable interface
* @c: Execution context
*/
static void get_l2_addr(struct ctx *c)
{
struct { struct nlmsghdr nlh; struct ifinfomsg ifi; } req = {
.nlh.nlmsg_type = RTM_GETLINK,
.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP_FILTERED,
.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
.nlh.nlmsg_seq = 1,
.ifi.ifi_family = AF_UNSPEC,
.ifi.ifi_index = if_nametoindex(c->ifn),
};
struct ifinfomsg *ifi;
struct nlmsghdr *nh;
struct rtattr *rta;
char buf[BUFSIZ];
int n, na;
if (memcmp(c->mac, ((uint8_t [ETH_ALEN]){ 0 }), ETH_ALEN))
goto mac_guest;
nl_req(buf, &req, sizeof(req));
nh = (struct nlmsghdr *)buf;
for ( ; NLMSG_OK(nh, n); nh = NLMSG_NEXT(nh, n)) {
if (nh->nlmsg_type != RTM_NEWLINK)
goto next;
ifi = (struct ifinfomsg *)NLMSG_DATA(nh);
for (rta = (struct rtattr *)IFLA_RTA(ifi), na = RTM_PAYLOAD(nh);
RTA_OK(rta, na); rta = RTA_NEXT(rta, na)) {
if (rta->rta_type != IFLA_ADDRESS)
continue;
memcpy(c->mac, RTA_DATA(rta), ETH_ALEN);
break;
}
next:
if (nh->nlmsg_type == NLMSG_DONE)
break;
}
if (!memcmp(c->mac, ((uint8_t [ETH_ALEN]){ 0 }), ETH_ALEN))
goto out;
mac_guest:
if (memcmp(c->mac_guest, ((uint8_t [ETH_ALEN]){ 0 }), ETH_ALEN))
memset(&c->mac_guest, 0xff, sizeof(c->mac_guest));
return;
out:
err("Couldn't get hardware address for routable interface");
exit(EXIT_FAILURE);
}
/**
* 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;
struct in6_addr *dns6 = &c->dns6[0];
struct fqdn *s = c->dns_search;
uint32_t *dns4 = &c->dns4[0];
char buf[BUFSIZ], *p, *end;
FILE *r;
dns4_set = !c->v4 || !!*dns4;
dns6_set = !c->v6 || !IN6_IS_ADDR_UNSPECIFIED(dns6);
dnss_set = !!*s->n || c->no_dns_search;
dns_set = dns4_set || dns6_set || c->no_dns;
if (dns_set && dnss_set)
return;
r = fopen("/etc/resolv.conf", "r");
if (!r)
goto out;
while (fgets(buf, BUFSIZ, r)) {
if (!dns_set && strstr(buf, "nameserver ") == buf) {
p = strrchr(buf, ' ');
if (!p)
continue;
end = strpbrk(buf, "%\n");
if (end)
*end = 0;
if (!dns4_set &&
dns4 - &c->dns4[0] < ARRAY_SIZE(c->dns4) - 1 &&
inet_pton(AF_INET, p + 1, dns4)) {
dns4++;
*dns4 = 0;
}
if (!dns6_set &&
dns6 - &c->dns6[0] < ARRAY_SIZE(c->dns6) - 1 &&
inet_pton(AF_INET6, p + 1, dns6)) {
dns6++;
memset(dns6, 0, sizeof(*dns6));
}
} else if (!dnss_set && strstr(buf, "search ") == buf &&
s == c->dns_search) {
end = strpbrk(buf, "\n");
if (end)
*end = 0;
p = strtok(buf, " \t");
while (s - c->dns_search < ARRAY_SIZE(c->dns_search) - 1
&& (p = strtok(NULL, " \t"))) {
strncpy(s->n, p, sizeof(c->dns_search[0]));
s++;
*s->n = 0;
}
}
}
fclose(r);
out:
if (!dns_set && dns4 == c->dns4 && dns6 == c->dns6)
warn("Couldn't get any nameserver address");
}
/**
* conf_ns_check() - Check if we can enter configured namespaces
* @arg: Execution context
*
* Return: 0
*/
static int conf_ns_check(void *arg)
{
struct ctx *c = (struct ctx *)arg;
if ((!c->netns_only && setns(c->pasta_userns_fd, 0)) ||
setns(c->pasta_netns_fd, 0))
c->pasta_userns_fd = c->pasta_netns_fd = -1;
return 0;
}
/**
* conf_ns_opt() - Open network, user namespaces descriptors from configuration
* @c: Execution context
* @nsdir: --nsrun-dir argument, can be an empty string
* @conf_userns: --userns argument, can be an empty string
* @optarg: PID, path or name of namespace
*
* Return: 0 on success, negative error code otherwise
*/
static int conf_ns_opt(struct ctx *c,
char *nsdir, char *conf_userns, const char *optarg)
{
char userns[PATH_MAX], netns[PATH_MAX];
int ufd = 0, nfd = 0, try, ret;
char *endptr;
pid_t pid;
if (c->netns_only && *conf_userns) {
err("Both --userns and --netns-only given");
return -EINVAL;
}
/* It might be a PID, a netns path, or a netns name */
for (try = 0; try < 3; try++) {
if (try == 0) {
pid = strtol(optarg, &endptr, 10);
if (*endptr || pid > INT_MAX)
continue;
if (!*conf_userns && !c->netns_only) {
ret = snprintf(userns, PATH_MAX,
"/proc/%i/ns/user", pid);
if (ret <= 0 || ret > (int)sizeof(userns))
continue;
}
ret = snprintf(netns, PATH_MAX, "/proc/%i/ns/net", pid);
if (ret <= 0 || ret > (int)sizeof(netns))
continue;
} else if (try == 1) {
if (!*conf_userns)
c->netns_only = 1;
ret = snprintf(netns, PATH_MAX, "%s", optarg);
if (ret <= 0 || ret > (int)sizeof(userns))
continue;
} else if (try == 2) {
ret = snprintf(netns, PATH_MAX, "%s/%s",
*nsdir ? nsdir : NETNS_RUN_DIR, optarg);
if (ret <= 0 || ret > (int)sizeof(netns))
continue;
}
if (!c->netns_only) {
if (*conf_userns)
ufd = open(conf_userns, O_RDONLY);
else if (*userns)
ufd = open(userns, O_RDONLY);
}
nfd = open(netns, O_RDONLY);
if (nfd >= 0 && ufd >= 0) {
c->pasta_netns_fd = nfd;
c->pasta_userns_fd = ufd;
NS_CALL(conf_ns_check, c);
if (c->pasta_netns_fd >= 0)
return 0;
}
if (nfd > 0)
close(nfd);
if (ufd > 0)
close(ufd);
}
return -ENOENT;
}
/**
* usage() - Print usage and exit
* @name: Executable name
*/
static void usage(const char *name)
{
if (strstr(name, "pasta") || strstr(name, "passt4netns")) {
info("Usage: %s [OPTION]... [PID|PATH|NAME]", name);
info("");
info("Without PID|PATH|NAME, run the default shell in a new");
info("network and user namespace, and connect it via pasta.");
} else {
info("Usage: %s [OPTION]...", name);
}
info("");
info( " -d, --debug Be verbose, don't run in background");
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( " -h, --help Display this help message and exit");
if (strstr(name, "pasta") || strstr(name, "passt4netns")) {
info( " -I, --ns-ifname NAME namespace interface name");
info( " default: same interface name as external one");
} else {
info( " -s, --socket PATH UNIX domain socket path");
info( " default: probe free path starting from "
UNIX_SOCK_PATH, 1);
}
info( " -p, --pcap [FILE] Log tap-facing traffic to pcap file");
info( " if FILE is not given, log to:");
if (strstr(name, "pasta") || strstr(name, "passt4netns"))
info(" /tmp/pasta_ISO8601-TIMESTAMP_INSTANCE-NUMBER.pcap");
else
info(" /tmp/passt_ISO8601-TIMESTAMP_INSTANCE-NUMBER.pcap");
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: interface with first default route");
info( " -D, --dns ADDR Pass IPv4 or IPv6 address as DNS");
info( " can be specified multiple times");
info( " a single, empty option disables DNS information");
if (strstr(name, "pasta") || strstr(name, "passt4netns"))
info( " default: don't send any addresses");
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");
if (strstr(name, "pasta") || strstr(name, "passt4netns"))
info( " default: don't send any search list");
else
info( " default: use search list from /etc/resolv.conf");
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( " -4, --ipv4-only Enable IPv4 operation only");
info( " -6, --ipv6-only Enable IPv6 operation only");
if (strstr(name, "pasta") || strstr(name, "passt4netns"))
goto pasta_opts;
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 ':'. 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( " default: none");
info( " -u, --udp-ports SPEC UDP port forwarding to guest");
info( " SPEC is as described for TCP above");
info( " default: none");
exit(EXIT_FAILURE);
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 ':'. 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( " 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-only Don't join or create user namespace");
info( " implied if PATH or NAME are given without --userns");
info( " --nsrun-dir Directory for nsfs mountpoints");
info( " default: " NETNS_RUN_DIR);
exit(EXIT_FAILURE);
}
void conf_print(struct ctx *c)
{
char buf6[INET6_ADDRSTRLEN], buf4[INET_ADDRSTRLEN];
int i;
if (c->mode == MODE_PASTA) {
info("Outbound interface: %s, namespace interface: %s",
c->ifn, c->pasta_ifn);
} else {
info("Outbound interface: %s", c->ifn);
}
if (c->v4) {
info("ARP:");
info(" address: %02x:%02x:%02x:%02x:%02x:%02x",
c->mac[0], c->mac[1], c->mac[2],
c->mac[3], c->mac[4], c->mac[5]);
if (!c->no_dhcp) {
info("DHCP:");
info(" assign: %s",
inet_ntop(AF_INET, &c->addr4, buf4, sizeof(buf4)));
info(" mask: %s",
inet_ntop(AF_INET, &c->mask4, buf4, sizeof(buf4)));
info(" router: %s",
inet_ntop(AF_INET, &c->gw4, buf4, sizeof(buf4)));
}
}
if (!c->no_dns && !(c->no_dhcp && c->no_ndp && c->no_dhcpv6)) {
for (i = 0; c->dns4[i]; i++) {
if (!i)
info(" DNS:");
inet_ntop(AF_INET, &c->dns4[i], buf4, sizeof(buf4));
info(" %s", buf4);
}
}
if (!c->no_dns_search && !(c->no_dhcp && c->no_ndp && c->no_dhcpv6)) {
for (i = 0; *c->dns_search[i].n; i++) {
if (!i)
info(" search:");
info(" %s", c->dns_search[i].n);
}
}
if (c->v6) {
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
return;
info(" assign: %s",
inet_ntop(AF_INET6, &c->addr6, buf6, sizeof(buf6)));
info(" router: %s",
inet_ntop(AF_INET6, &c->gw6, buf6, sizeof(buf6)));
info(" our link-local: %s",
inet_ntop(AF_INET6, &c->addr6_ll, buf6, sizeof(buf6)));
for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->dns6[i]); i++) {
if (!i)
info(" DNS:");
inet_ntop(AF_INET6, &c->dns6[i], buf6, sizeof(buf6));
info(" %s", buf6);
}
for (i = 0; *c->dns_search[i].n; i++) {
if (!i)
info(" search:");
info(" %s", c->dns_search[i].n);
}
}
}
/**
* 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)
{
struct option options[] = {
{"debug", no_argument, NULL, 'd' },
{"quiet", no_argument, NULL, 'q' },
{"foreground", no_argument, NULL, 'f' },
{"stderr", no_argument, &c->stderr, 1 },
{"help", no_argument, NULL, 'h' },
{"socket", required_argument, NULL, 's' },
{"ns-ifname", required_argument, NULL, 'I' },
{"pcap", optional_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' },
{"dns", optional_argument, NULL, 'D' },
{"search", optional_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 },
{"ipv4-only", no_argument, &c->v4, '4' },
{"ipv6-only", no_argument, &c->v6, '6' },
{"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-only", no_argument, &c->netns_only, 1 },
{"nsrun-dir", required_argument, NULL, 3 },
{ 0 },
};
struct get_bound_ports_ns_arg ns_ports_arg = { .c = c };
char nsdir[PATH_MAX] = { 0 }, userns[PATH_MAX] = { 0 };
enum conf_port_type tcp_tap = 0, tcp_init = 0;
enum conf_port_type udp_tap = 0, udp_init = 0;
struct fqdn *dnss = c->dns_search;
struct in6_addr *dns6 = c->dns6;
int name, ret, mask, b, i;
uint32_t *dns4 = c->dns4;
do {
enum conf_port_type *set = NULL;
const char *optstring;
if (c->mode == MODE_PASST)
optstring = "dqfehs:p::m:a:n:M:g:i:D::S::46t:u:";
else
optstring = "dqfehI:p::m:a:n:M:g:i:D::S::46t:u:T:U:";
name = getopt_long(argc, argv, optstring, options, NULL);
if ((name == 'p' || name == 'D' || name == 'S') && !optarg &&
optind < argc && *argv[optind] && *argv[optind] != '-') {
if (c->mode == MODE_PASTA) {
if (conf_ns_opt(c, nsdir, userns, argv[optind]))
optarg = argv[optind++];
} else {
optarg = argv[optind++];
}
}
switch (name) {
case -1:
case 0:
break;
case 2:
if (c->mode != MODE_PASTA) {
err("--userns is for pasta mode only");
usage(argv[0]);
}
ret = snprintf(userns, sizeof(userns), "%s", optarg);
if (ret <= 0 || ret >= (int)sizeof(userns)) {
err("Invalid userns: %s", optarg);
usage(argv[0]);
}
break;
case 3:
if (c->mode != MODE_PASTA) {
err("--nsrun-dir is for pasta mode only");
usage(argv[0]);
}
ret = snprintf(nsdir, sizeof(nsdir), "%s", optarg);
if (ret <= 0 || ret >= (int)sizeof(nsdir)) {
err("Invalid nsrun-dir: %s", optarg);
usage(argv[0]);
}
break;
case 'd':
if (c->debug) {
err("Multiple --debug options given");
usage(argv[0]);
}
if (c->quiet) {
err("Either --debug or --quiet");
usage(argv[0]);
}
c->debug = 1;
c->foreground = 1;
break;
case 'q':
if (c->quiet) {
err("Multiple --quiet options given");
usage(argv[0]);
}
if (c->debug) {
err("Either --debug or --quiet");
usage(argv[0]);
}
c->quiet = 1;
break;
case 'f':
if (c->foreground && !c->debug) {
err("Multiple --foreground options given");
usage(argv[0]);
}
c->foreground = 1;
break;
case '?':
case 'h':
usage(argv[0]);
break;
case 's':
if (*c->sock_path) {
err("Multiple --socket options given");
usage(argv[0]);
}
ret = snprintf(c->sock_path, sizeof(c->sock_path), "%s",
optarg);
if (ret <= 0 || ret >= (int)sizeof(c->pcap)) {
err("Invalid socket path: %s", optarg);
usage(argv[0]);
}
break;
case 'I':
if (*c->pasta_ifn) {
err("Multiple --ns-ifname options given");
usage(argv[0]);
}
ret = snprintf(c->pasta_ifn, sizeof(c->pasta_ifn), "%s",
optarg);
if (ret <= 0 || ret >= (int)sizeof(c->pasta_ifn)) {
err("Invalid interface name: %s", optarg);
usage(argv[0]);
}
break;
case 'p':
if (*c->pcap) {
err("Multiple --pcap options given");
usage(argv[0]);
}
if (!optarg) {
*c->pcap = 1;
break;
}
ret = snprintf(c->pcap, sizeof(c->pcap), "%s", optarg);
if (ret <= 0 || ret >= (int)sizeof(c->pcap)) {
err("Invalid pcap path: %s", optarg);
usage(argv[0]);
}
break;
case 'm':
if (c->mtu) {
err("Multiple --mtu options given");
usage(argv[0]);
}
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) {
err("Invalid MTU: %s", optarg);
usage(argv[0]);
}
break;
case 'a':
if (IN6_IS_ADDR_UNSPECIFIED(&c->addr6) &&
inet_pton(AF_INET6, optarg, &c->addr6) &&
!IN6_IS_ADDR_UNSPECIFIED(&c->addr6) &&
!IN6_IS_ADDR_LOOPBACK(&c->addr6) &&
!IN6_IS_ADDR_V4MAPPED(&c->addr6) &&
!IN6_IS_ADDR_V4COMPAT(&c->addr6) &&
!IN6_IS_ADDR_MULTICAST(&c->addr6))
break;
if (c->addr4 == INADDR_ANY &&
inet_pton(AF_INET, optarg, &c->addr4) &&
c->addr4 != INADDR_ANY &&
c->addr4 != INADDR_BROADCAST &&
c->addr4 != INADDR_LOOPBACK &&
!IN_MULTICAST(c->addr4))
break;
err("Invalid address: %s", optarg);
usage(argv[0]);
break;
case 'n':
if (inet_pton(AF_INET, optarg, &c->mask4))
break;
errno = 0;
mask = strtol(optarg, NULL, 0);
if (mask >= 0 && mask <= 32 && !errno) {
c->mask4 = htonl(0xffffffff << (32 - mask));
break;
}
err("Invalid netmask: %s", optarg);
usage(argv[0]);
break;
case 'M':
for (i = 0; i < ETH_ALEN; i++) {
errno = 0;
b = strtol(optarg + i * 3, NULL, 16);
if (b < 0 || b > UCHAR_MAX || errno) {
err("Invalid MAC address: %s", optarg);
usage(argv[0]);
}
c->mac[i] = b;
}
break;
case 'g':
if (IN6_IS_ADDR_UNSPECIFIED(&c->gw6) &&
inet_pton(AF_INET6, optarg, &c->gw6) &&
!IN6_IS_ADDR_UNSPECIFIED(&c->gw6) &&
!IN6_IS_ADDR_LOOPBACK(&c->gw6))
break;
if (c->gw4 == INADDR_ANY &&
inet_pton(AF_INET, optarg, &c->gw4) &&
c->gw4 != INADDR_ANY &&
c->gw4 != INADDR_BROADCAST &&
c->gw4 != INADDR_LOOPBACK)
break;
err("Invalid gateway address: %s", optarg);
usage(argv[0]);
break;
case 'i':
if (*c->ifn) {
err("Redundant interface: %s", optarg);
usage(argv[0]);
}
strncpy(c->ifn, optarg, IFNAMSIZ - 1);
break;
case 'D':
if (c->no_dns ||
(!optarg && (dns4 - c->dns4 || dns6 - c->dns6))) {
err("Empty and non-empty DNS options given");
usage(argv[0]);
}
if (!optarg) {
c->no_dns = 1;
break;
}
if (dns4 - &c->dns4[0] < ARRAY_SIZE(c->dns4) &&
inet_pton(AF_INET, optarg, dns4)) {
dns4++;
break;
}
if (dns6 - &c->dns6[0] < ARRAY_SIZE(c->dns6) &&
inet_pton(AF_INET6, optarg, dns6)) {
dns6++;
break;
}
err("Cannot use DNS address %s", optarg);
usage(argv[0]);
break;
case 'S':
if (c->no_dns_search ||
(!optarg && dnss != c->dns_search)) {
err("Empty and non-empty DNS search given");
usage(argv[0]);
}
if (!optarg) {
c->no_dns_search = 1;
break;
}
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;
}
err("Cannot use DNS search domain %s", optarg);
usage(argv[0]);
break;
case '4':
c->v4 = 1;
break;
case '6':
c->v6 = 1;
break;
case 't':
case 'u':
case 'T':
case 'U':
if (name == 't')
set = &tcp_tap;
else if (name == 'T')
set = &tcp_init;
else if (name == 'u')
set = &udp_tap;
else if (name == 'U')
set = &udp_init;
if (conf_ports(c, name, optarg, set))
usage(argv[0]);
break;
}
} while (name != -1);
if (c->mode == MODE_PASTA && optind + 1 == argc) {
ret = conf_ns_opt(c, nsdir, userns, argv[optind]);
if (ret == -ENOENT)
err("Namespace %s not found", argv[optind]);
if (ret < 0)
usage(argv[0]);
} else if (c->mode == MODE_PASTA && *userns && optind == argc) {
err("--userns requires PID, PATH or NAME");
usage(argv[0]);
} else if (optind != argc) {
usage(argv[0]);
}
if (c->v4 && c->v6) {
err("Options ipv4-only and ipv6-only are mutually exclusive");
usage(argv[0]);
}
if (c->v4 || c->v6) {
if (!c->v4)
c->no_dhcp = 1;
if (!c->v6) {
c->no_ndp = 1;
c->no_dhcpv6 = 1;
}
}
if (!c->mtu) {
c->mtu = (ETH_MAX_MTU - ETH_HLEN) /
sizeof(uint32_t) * sizeof(uint32_t);
}
get_routes(c);
get_l3_addrs(c);
if (c->v4)
get_l2_addr(c);
if (c->mode == MODE_PASTA && dns4 == c->dns4 && dns6 == c->dns6)
c->no_dns = 1;
if (c->mode == MODE_PASTA && dnss == c->dns_search)
c->no_dns_search = 1;
get_dns(c);
if (!*c->pasta_ifn)
strncpy(c->pasta_ifn, c->ifn, IFNAMSIZ);
#ifdef PASST_LEGACY_NO_OPTIONS
if (c->mode == MODE_PASST) {
c->foreground = 1;
c->stderr = 1;
if (!tcp_tap) {
memset(c->tcp.port_to_tap, 0xff,
PORT_EPHEMERAL_MIN / 8);
}
}
#endif
c->tcp.ns_detect_ports = c->udp.ns_detect_ports = 0;
c->tcp.init_detect_ports = c->udp.init_detect_ports = 0;
if (c->mode == MODE_PASTA) {
if (!tcp_tap || tcp_tap == PORT_AUTO) {
c->tcp.ns_detect_ports = 1;
ns_ports_arg.proto = IPPROTO_TCP;
NS_CALL(get_bound_ports_ns, &ns_ports_arg);
}
if (!udp_tap || udp_tap == PORT_AUTO) {
c->udp.ns_detect_ports = 1;
ns_ports_arg.proto = IPPROTO_UDP;
NS_CALL(get_bound_ports_ns, &ns_ports_arg);
}
if (!tcp_init || tcp_init == PORT_AUTO) {
c->tcp.init_detect_ports = 1;
get_bound_ports(c, 0, IPPROTO_TCP);
}
if (!udp_init || udp_init == PORT_AUTO) {
c->udp.init_detect_ports = 1;
get_bound_ports(c, 0, IPPROTO_UDP);
}
}
conf_print(c);
}