From 9a175cc2cea75b98fc3c20381f58dcabf24ef529 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 29 Sep 2021 16:11:06 +0200 Subject: pasta: Allow specifying paths and names of namespaces Based on a patch from Giuseppe Scrivano, this adds the ability to: - specify paths and names of target namespaces to join, instead of a PID, also for user namespaces, with --userns - request to join or create a network namespace only, without entering or creating a user namespace, with --netns-only - specify the base directory for netns mountpoints, with --nsrun-dir Signed-off-by: Giuseppe Scrivano [sbrivio: reworked logic to actually join the given namespaces when they're not created, implemented --netns-only and --nsrun-dir, updated pasta demo script and man page] Signed-off-by: Stefano Brivio --- Makefile | 1 + conf.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++----- passt.1 | 30 ++++++++--- passt.c | 59 +++++++++++++-------- passt.h | 9 +++- pcap.c | 2 +- tap.c | 15 +++--- tcp.c | 10 ++-- test/demo/pasta | 2 +- udp.c | 4 +- util.c | 28 +++------- util.h | 2 +- 12 files changed, 240 insertions(+), 79 deletions(-) diff --git a/Makefile b/Makefile index ac809df..8a1b3c5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ CFLAGS += -Wall -Wextra -pedantic CFLAGS += -DRLIMIT_STACK_VAL=$(shell ulimit -s) CFLAGS += -DPAGE_SIZE=$(shell getconf PAGE_SIZE) +CFLAGS += -DNETNS_RUN_DIR=\"/run/netns\" prefix ?= /usr/local diff --git a/conf.c b/conf.c index 17a97d5..4a888ab 100644 --- a/conf.c +++ b/conf.c @@ -96,7 +96,7 @@ 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_pid || ns_enter(c->pasta_pid)) + if (!c->pasta_netns_fd || ns_enter(c)) return 0; get_bound_ports(c, 1, a->proto); @@ -588,6 +588,104 @@ out: 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 @@ -595,10 +693,10 @@ out: static void usage(const char *name) { if (strstr(name, "pasta") || strstr(name, "passt4netns")) { - info("Usage: %s [OPTION]... [TARGET_PID]", name); + info("Usage: %s [OPTION]... [PID|PATH|NAME]", name); info(""); - info("Without TARGET_PID, enter a user and network namespace,"); - info("run the default shell and connect it via pasta."); + 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); } @@ -670,7 +768,7 @@ static void usage(const char *name) info( " -6, --ipv6-only Enable IPv6 operation only"); if (strstr(name, "pasta") || strstr(name, "passt4netns")) - goto pasta_ports; + goto pasta_opts; info( " -t, --tcp-ports SPEC TCP port forwarding to guest"); info( " can be specified multiple times"); @@ -692,7 +790,7 @@ static void usage(const char *name) exit(EXIT_FAILURE); -pasta_ports: +pasta_opts: info( " -t, --tcp-ports SPEC TCP port forwarding to namespace"); info( " can be specified multiple times"); info( " SPEC can be:"); @@ -721,6 +819,11 @@ pasta_ports: 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); } @@ -839,9 +942,13 @@ void conf(struct ctx *c, int argc, char **argv) {"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; @@ -863,10 +970,7 @@ void conf(struct ctx *c, int argc, char **argv) if ((name == 'p' || name == 'D' || name == 'S') && !optarg && optind < argc && *argv[optind] && *argv[optind] != '-') { if (c->mode == MODE_PASTA) { - char *endptr; - - strtol(argv[optind], &endptr, 10); - if (*endptr) + if (conf_ns_opt(c, nsdir, userns, argv[optind])) optarg = argv[optind++]; } else { optarg = argv[optind++]; @@ -877,6 +981,30 @@ void conf(struct ctx *c, int argc, char **argv) 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"); @@ -1129,9 +1257,14 @@ void conf(struct ctx *c, int argc, char **argv) } while (name != -1); if (c->mode == MODE_PASTA && optind + 1 == argc) { - c->pasta_pid = strtol(argv[optind], NULL, 0); - if (c->pasta_pid < 0 || errno) + 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]); } diff --git a/passt.1 b/passt.1 index d3a5cc5..b21333b 100644 --- a/passt.1 +++ b/passt.1 @@ -12,7 +12,7 @@ [\fIOPTION\fR]... .br .B pasta -[\fIOPTION\fR]... [\fITARGET_PID\fR] +[\fIOPTION\fR]... [\fIPID\fR|\fIPATH\fR|\fINAME\fR] .SH DESCRIPTION @@ -56,11 +56,10 @@ or with the \fBqrap\fR(1) wrapper. equivalent functionality to network namespaces, as the one offered by \fBpasst\fR for virtual machines. -If TARGET_PID is given, \fBpasta\fR associates to the user and network namespace -of the corresponding process. Otherwise, \fBpasta\fR creates a new user and -network namespace, and spawns an interactive shell within this context. A -\fItap\fR device within the network namespace is created to provide network -connectivity. +If PID, PATH or NAME are given, \fBpasta\fR associates to an existing user and +network namespace. Otherwise, \fBpasta\fR creates a new user and network +namespace, and spawns an interactive shell within this context. A \fItap\fR +device within the network namespace is created to provide network connectivity. For local TCP and UDP traffic only, \fBpasta\fR also implements a bypass path directly mapping Layer-4 sockets between \fIinit\fR and target namespaces, @@ -357,6 +356,25 @@ Configure UDP port forwarding from target namespace to init namespace. Default is \fBauto\fR. +.TP +.BR \-\-userns " " \fIspec +Target user namespace to join, as path or name (i.e. suffix for --nsrun-dir). If +PID is given, without this option, the user namespace will be the one of the +corresponding process. + +This option requires PID, PATH or NAME to be specified. + +.TP +.BR \-\-netns-only +Join or create only the network namespace, not a user namespace. This is implied +if PATH or NAME are given without \-\-userns. + +.TP +.BR \-\-nsrun-dir " " \fIpath +Directory for nsfs mountpoints, used as path prefix for names of namespaces. + +The default path is shown with --help. + .SH EXAMPLES .SS \fBpasta diff --git a/passt.c b/passt.c index 77cd48c..26e06d4 100644 --- a/passt.c +++ b/passt.c @@ -242,18 +242,34 @@ static void pasta_child_handler(int signal) /** * pasta_wait_for_ns() - Busy loop until we can enter the target namespace - * @target_pid: PID of process associated with target namespace + * @arg: Execution context * * Return: 0 */ -static int pasta_wait_for_ns(void *target_pid) +static int pasta_wait_for_ns(void *arg) { - while (ns_enter(*(int *)target_pid)); + struct ctx *c = (struct ctx *)arg; + char ns[PATH_MAX]; + + if (c->netns_only) + goto netns; + + snprintf(ns, PATH_MAX, "/proc/%i/ns/user", pasta_child_pid); + do + while ((c->pasta_userns_fd = open(ns, O_RDONLY)) < 0); + while (setns(c->pasta_userns_fd, 0) && !close(c->pasta_userns_fd)); + +netns: + snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid); + do + while ((c->pasta_netns_fd = open(ns, O_RDONLY)) < 0); + while (setns(c->pasta_netns_fd, 0) && !close(c->pasta_netns_fd)); + return 0; } /** - * pasta_start_ns() - Fork shell in new namespace if target PID is not given + * pasta_start_ns() - Fork shell in new namespace if target ns is not given * @c: Execution context */ static void pasta_start_ns(struct ctx *c) @@ -266,34 +282,36 @@ static void pasta_start_ns(struct ctx *c) if (!c->debug) c->quiet = 1; - if ((c->pasta_pid = fork()) == -1) { + if ((pasta_child_pid = fork()) == -1) { perror("fork"); exit(EXIT_FAILURE); } - if ((pasta_child_pid = c->pasta_pid)) { - NS_CALL(pasta_wait_for_ns, &pasta_child_pid); + if (pasta_child_pid) { + NS_CALL(pasta_wait_for_ns, c); return; } - if (unshare(CLONE_NEWNET | CLONE_NEWUSER)) { + if (unshare(CLONE_NEWNET | (c->netns_only ? 0 : CLONE_NEWUSER))) { perror("unshare"); exit(EXIT_FAILURE); } - snprintf(buf, BUFSIZ, "%u %u %u", 0, euid, 1); + if (!c->netns_only) { + snprintf(buf, BUFSIZ, "%u %u %u", 0, euid, 1); - fd = open("/proc/self/uid_map", O_WRONLY); - write(fd, buf, strlen(buf)); - close(fd); + fd = open("/proc/self/uid_map", O_WRONLY); + write(fd, buf, strlen(buf)); + close(fd); - fd = open("/proc/self/setgroups", O_WRONLY); - write(fd, "deny", sizeof("deny")); - close(fd); + fd = open("/proc/self/setgroups", O_WRONLY); + write(fd, "deny", sizeof("deny")); + close(fd); - fd = open("/proc/self/gid_map", O_WRONLY); - write(fd, buf, strlen(buf)); - close(fd); + fd = open("/proc/self/gid_map", O_WRONLY); + write(fd, buf, strlen(buf)); + close(fd); + } fd = open("/proc/sys/net/ipv4/ping_group_range", O_WRONLY); write(fd, "0 0", strlen("0 0")); @@ -352,11 +370,12 @@ int main(int argc, char **argv) if (!c.debug && (c.stderr || isatty(fileno(stdout)))) openlog(log_name, LOG_PERROR, LOG_DAEMON); - if (c.mode == MODE_PASTA && !c.pasta_pid) { + if (c.mode == MODE_PASTA && !c.pasta_netns_fd) { char proc_path[PATH_MAX]; pasta_start_ns(&c); - snprintf(proc_path, PATH_MAX, "/proc/%i/ns/net", c.pasta_pid); + snprintf(proc_path, PATH_MAX, "/proc/%i/ns/net", + pasta_child_pid); readlink(proc_path, pasta_child_ns, PATH_MAX); } diff --git a/passt.h b/passt.h index 895dd91..aef483a 100644 --- a/passt.h +++ b/passt.h @@ -85,13 +85,15 @@ enum passt_modes { /** * struct ctx - Execution context * @mode: Operation mode, qemu/UNIX domain socket or namespace/tap - * @pasta_pid: Target PID of namespace for pasta mode * @debug: Enable debug mode * @quiet: Don't print informational messages * @foreground: Run in foreground, don't log to stderr by default * @stderr: Force logging to stderr * @sock_path: Path for UNIX domain socket * @pcap: Path for packet capture file + * @pasta_netns_fd: File descriptor for network namespace in pasta mode + * @pasta_userns_fd: File descriptor for user namespace in pasta mode + * @netns_only: In pasta mode, don't join or create a user namespace * @epollfd: File descriptor for epoll instance * @fd_tap_listen: File descriptor for listening AF_UNIX socket, if any * @fd_tap: File descriptor for AF_UNIX socket or tuntap device @@ -131,7 +133,6 @@ enum passt_modes { */ struct ctx { enum passt_modes mode; - int pasta_pid; int debug; int quiet; int foreground; @@ -139,6 +140,10 @@ struct ctx { char sock_path[UNIX_PATH_MAX]; char pcap[PATH_MAX]; + int pasta_netns_fd; + int pasta_userns_fd; + int netns_only; + int epollfd; int fd_tap_listen; int fd_tap; diff --git a/pcap.c b/pcap.c index 6b795ff..9cf585f 100644 --- a/pcap.c +++ b/pcap.c @@ -153,7 +153,7 @@ void pcapmm(struct mmsghdr *mmh, unsigned int vlen) /** * pcap_init() - Initialise pcap file * @c: Execution context - * @index: pcap name index: passt instance number or pasta target pid + * @index: pcap name index: passt instance number or pasta netns socket */ void pcap_init(struct ctx *c, int index) { diff --git a/tap.c b/tap.c index 8d8f381..0abf5a0 100644 --- a/tap.c +++ b/tap.c @@ -845,13 +845,13 @@ static int tun_ns_fd = -1; /** * tap_sock_init_tun_ns() - Create tuntap fd in namespace, bring up loopback - * @target_pid: Pointer to target PID + * @c: Execution context */ -static int tap_sock_init_tun_ns(void *target_pid) +static int tap_sock_init_tun_ns(void *c) { int fd; - if (ns_enter(*(int *)target_pid)) + if (ns_enter((struct ctx *)c)) goto fail; if ((fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK)) < 0) @@ -883,12 +883,10 @@ fail: /** * struct tap_sock_if_up_ns_arg - Arguments for tap_sock_if_up_ns() * @c: Execution context - * @target_pid: Target namespace PID * @ifname: Interface name of tap device */ struct tap_sock_if_up_ns_arg { struct ctx *c; - int target_pid; char ifname[IFNAMSIZ]; }; @@ -906,7 +904,7 @@ static int tap_sock_if_up_ns(void *arg) a = (struct tap_sock_if_up_ns_arg *)arg; - if (ns_enter(a->target_pid)) + if (ns_enter(a->c)) return 0; if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { @@ -955,7 +953,7 @@ static void tap_sock_init_tun(struct ctx *c) struct ifreq ifr = { .ifr_flags = IFF_TAP | IFF_NO_PI }; struct tap_sock_if_up_ns_arg ifup_arg; - NS_CALL(tap_sock_init_tun_ns, &c->pasta_pid); + NS_CALL(tap_sock_init_tun_ns, c); if (tun_ns_fd == -1) { err("Failed to open tun socket in namespace"); exit(EXIT_FAILURE); @@ -968,11 +966,10 @@ static void tap_sock_init_tun(struct ctx *c) } strncpy(ifup_arg.ifname, c->pasta_ifn, IFNAMSIZ); - ifup_arg.target_pid = c->pasta_pid; ifup_arg.c = c; NS_CALL(tap_sock_if_up_ns, (void *)&ifup_arg); - pcap_init(c, c->pasta_pid); + pcap_init(c, c->pasta_netns_fd); c->fd_tap = tun_ns_fd; } diff --git a/tcp.c b/tcp.c index 43e3ea3..c862632 100644 --- a/tcp.c +++ b/tcp.c @@ -2828,7 +2828,7 @@ static int tcp_splice_connect_ns(void *arg) struct tcp_splice_connect_ns_arg *a; a = (struct tcp_splice_connect_ns_arg *)arg; - ns_enter(a->c->pasta_pid); + ns_enter(a->c); a->ret = tcp_splice_connect(a->c, a->conn, -1, a->v6, a->port); return 0; } @@ -3431,7 +3431,7 @@ static int tcp_sock_init_ns(void *arg) struct ctx *c = (struct ctx *)arg; in_port_t port; - ns_enter(c->pasta_pid); + ns_enter(c); for (port = 0; port < USHRT_MAX; port++) { if (!bitmap_isset(c->tcp.port_to_init, port)) @@ -3491,7 +3491,7 @@ static int tcp_sock_refill(void *arg) int i, *p4, *p6; if (a->ns) { - if (ns_enter(a->c->pasta_pid)) + if (ns_enter(a->c)) return 0; p4 = ns_sock_pool4; p6 = ns_sock_pool6; @@ -3676,7 +3676,7 @@ static int tcp_port_detect(void *arg) struct tcp_port_detect_arg *a = (struct tcp_port_detect_arg *)arg; if (a->detect_in_ns) { - ns_enter(a->c->pasta_pid); + ns_enter(a->c); get_bound_ports(a->c, 1, IPPROTO_TCP); } else { @@ -3708,7 +3708,7 @@ static int tcp_port_rebind(void *arg) in_port_t port; if (a->bind_in_ns) { - ns_enter(a->c->pasta_pid); + ns_enter(a->c); for (port = 0; port < USHRT_MAX; port++) { if (!bitmap_isset(a->c->tcp.port_to_init, port)) { diff --git a/test/demo/pasta b/test/demo/pasta index 7d64158..ef250d7 100644 --- a/test/demo/pasta +++ b/test/demo/pasta @@ -46,7 +46,7 @@ hostb n sleep 10 nl -say without TARGET_PID, it will create a namespace. +say without PID, it will create a namespace. sleep 3 passt cd __TEMPDIR__/passt passt ./pasta diff --git a/udp.c b/udp.c index 6b2ee36..0837cba 100644 --- a/udp.c +++ b/udp.c @@ -516,7 +516,7 @@ static int udp_splice_connect_ns(void *arg) a = (struct udp_splice_connect_ns_arg *)arg; - ns_enter(a->c->pasta_pid); + ns_enter(a->c); a->s = udp_splice_connect(a->c, a->v6, a->bound_sock, a->src, a->dst, UDP_BACK_TO_INIT); @@ -1004,7 +1004,7 @@ int udp_sock_init_ns(void *arg) struct ctx *c = (struct ctx *)arg; in_port_t dst; - ns_enter(c->pasta_pid); + ns_enter(c); for (dst = 0; dst < USHRT_MAX; dst++) { if (!bitmap_isset(c->udp.port_to_init, dst)) diff --git a/util.c b/util.c index 66b088a..2a5c5ee 100644 --- a/util.c +++ b/util.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "util.h" #include "passt.h" @@ -327,31 +328,18 @@ void procfs_scan_listen(char *name, uint8_t *map, uint8_t *exclude) } /** - * ns_enter() - Enter user and network namespaces of process with given PID - * @target_pid: Process PID + * ns_enter() - Enter configured network and user namespaces + * @c: Execution context * * Return: 0 on success, -1 on failure */ -int ns_enter(int target_pid) +int ns_enter(struct ctx *c) { - char ns[PATH_MAX]; - int fd; - - snprintf(ns, PATH_MAX, "/proc/%i/ns/user", target_pid); - if ((fd = open(ns, O_RDONLY)) < 0 || setns(fd, 0)) - goto fail; - close(fd); + if (!c->netns_only && setns(c->pasta_userns_fd, 0)) + return -errno; - snprintf(ns, PATH_MAX, "/proc/%i/ns/net", target_pid); - if ((fd = open(ns, O_RDONLY)) < 0 || setns(fd, 0)) - goto fail; - close(fd); + if (setns(c->pasta_netns_fd, 0)) + return -errno; return 0; - -fail: - if (fd != -1) - close(fd); - - return -1; } diff --git a/util.h b/util.h index fdb0ef0..03c5ebb 100644 --- a/util.h +++ b/util.h @@ -143,4 +143,4 @@ void bitmap_set(uint8_t *map, int bit); void bitmap_clear(uint8_t *map, int bit); int bitmap_isset(uint8_t *map, int bit); void procfs_scan_listen(char *name, uint8_t *map, uint8_t *exclude); -int ns_enter(int target_pid); +int ns_enter(struct ctx *c); -- cgit v1.2.3