// SPDX-License-Identifier: GPL-2.0-or-later /* PESTO - Programmable Extensible Socket Translation Orchestrator * front-end for passt(1) and pasta(1) forwarding configuration * * pesto.c - Main program (it's not actually extensible) * * Copyright (c) 2026 Red Hat GmbH * Author: Stefano Brivio */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "seccomp_pesto.h" #include "serialise.h" #include "ports.h" #include "fwd_rule.h" #include "pesto.h" #include "log.h" #include "pif.h" int verbosity = 1; /** * xmalloc() - Allocate memory, with fatal error on failure * @size: Number of bytes to allocate */ static void *xmalloc(size_t size) { void *p = malloc(size); if (!p) die("Memory allocation failure"); return p; } /** * usage() - Print usage, exit with given status code * @name: Executable name * @f: Stream to print usage info to * @status: Status code for exit(2) */ static void usage(const char *name, FILE *f, int status) { FPRINTF(f, "Usage: %s [OPTION]... PATH\n", name); FPRINTF(f, "\n" " -t, --tcp-ports SPEC TCP inbound port forwarding\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" " '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 '%%'. 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\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" " IPv6 bound ports are also forwarded for IPv4\n" " -u, --udp-ports SPEC UDP inbound port forwarding\n" " SPEC is as described for TCP above\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" " -U, --udp-ns SPEC UDP port forwarding to init namespace\n" " SPEC is as described above\n" " -v, --verbose Be more verbose\n" " -q, --quiet Be less verbose\n" " -h, --help Display this help message and exit\n" " --version Show version and exit\n"); exit(status); } /** * pesto_recv_str() - Receive a string from passt/pasta * @fd: Control socket * * Return: pointer to malloc()ed string */ static const char *pesto_recv_str(int fd) { uint32_t len; char *buf; if (seread_u32(fd, &len) < 0) die("Error reading from control socket"); buf = xmalloc(len); if (seread_buf(fd, buf, len) < 0) die("Error reading from control socket"); return buf; } struct pif_state { uint8_t pif; const char *name; uint32_t count; struct fwd_rule *rule; }; struct conf_state { uint32_t npifs; struct pif_state pif[]; }; /** * pesto_read_pifs() - Read pif names and IDs from passt/pasta * @fd: Control socket */ static struct conf_state *pesto_read_pifs(int fd) { uint32_t num; struct conf_state *state; unsigned i; if (seread_u32(fd, &num) < 0) die("Error reading from control socket"); debug("Receiving %"PRIu32" interface names", num); state = xmalloc(sizeof(*state) + num * sizeof(struct pif_state)); state->npifs = num; for (i = 0; i < num; i++) { struct pif_state *ps = &state->pif[i]; if (seread_u8(fd, &ps->pif) < 0) die("Error reading from control socket"); ps->name = pesto_recv_str(fd); debug("%u: %s", ps->pif, ps->name); } return state; } /** * find_pif_state() - Find the pif state structure for a given pif id * @state: Rule state information * @pif: pif id * * Return: pointer to the pif_state for @pif, or NULL if not found */ static struct pif_state *find_pif_state(struct conf_state *state, uint8_t pif) { unsigned i; for (i = 0; i < state->npifs; i++) { if (state->pif[i].pif == pif) return &state->pif[i]; } return NULL; } /** * pesto_read_rules() - Read a set of rules for one pif * @fd: Control socket * @state: Rule state information to update * * Return: true if there may be more rules to read, false if finished */ static bool pesto_read_rules(int fd, struct conf_state *state) { struct pif_state *ps; uint8_t pif; unsigned i; if (seread_u8(fd, &pif) < 0) die("Error reading from control socket"); if (!pif) return false; ps = find_pif_state(state, pif); if (!ps) die("Received rules for an unknown pif"); if (seread_u32(fd, &ps->count) < 0) die("Error reading from control socket"); debug("Receiving rules %"PRIu32" rules for %s", ps->count, ps->name); ps->rule = xmalloc(sizeof(*ps->rule) * ps->count); for (i = 0; i < ps->count; i++) { if (fwd_rule_seread(fd, &ps->rule[i]) < 0) die("Error reading from control socket"); } return true; } /** * show_state() - Show current rule state obtained from passt/pasta * @pifs: PIF name information */ static void show_state(const struct conf_state *state) { unsigned i, j; for (i = 0; i < state->npifs; i++) { const struct pif_state *ps = &state->pif[i]; printf("Forwarding rules for %s interface\n", ps->name); for (j = 0; j < ps->count; j++) { const struct fwd_rule *rule = &ps->rule[j]; char rulestr[FWD_RULE_STRLEN]; printf(" %s\n", fwd_rule_ntop(rule, rulestr, sizeof(rulestr))); } } } /** * main() - Entry point and whole program with loop * @argc: Argument count * @argv: Arguments: socket path, operation, port specifiers * * Return: 0 on success, won't return on failure * * #syscalls:pesto connect write close exit_group fstat brk getrandom openat * #syscalls:pesto socket s390x:socketcall i686:socketcall * #syscalls:pesto recvfrom recvmsg arm:recv ppc64le:recv * #syscalls:pesto sendto sendmsg arm:send ppc64le:send */ int main(int argc, char **argv) { const struct option options[] = { {"quiet", no_argument, NULL, 'q' }, {"verbose", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, {"version", 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' }, { 0 }, }; struct fwd_table fwd_in = { .count = 0 }, fwd_out = { .count = 0 }; struct sockaddr_un a = { AF_UNIX, "" }; const char *optstring = "vht:u:T:U:"; enum fwd_mode fwd_default; struct pesto_hello hello; struct conf_state *state; struct sock_fprog prog; int optname, ret, s; uint32_t s_version; unsigned i; prog.len = (unsigned short)sizeof(filter_pesto) / sizeof(filter_pesto[0]); prog.filter = filter_pesto; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) die("Failed to apply seccomp filter"); fwd_probe_ephemeral(); do { optname = getopt_long(argc, argv, optstring, options, NULL); switch (optname) { case -1: case 0: break; case 'h': usage(argv[0], stdout, EXIT_SUCCESS); break; case 'q': verbosity--; break; case 'v': verbosity++; break; case 't': case 'u': fwd_default = FWD_MODE_UNSET; conf_ports(optname, optarg, &fwd_in, &fwd_default, true, true, true); break; case 'T': case 'U': fwd_default = FWD_MODE_UNSET; conf_ports(optname, optarg, &fwd_out, &fwd_default, true, true, true); break; case 1: FPRINTF(stdout, "pesto "); FPRINTF(stdout, VERSION_BLOB); exit(EXIT_SUCCESS); default: usage(argv[0], stderr, EXIT_FAILURE); } } while (optname != -1); if (argc - optind != 1) usage(argv[0], stderr, EXIT_FAILURE); if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) die("Failed to create AF_UNIX socket: %s", strerror(errno)); ret = snprintf(a.sun_path, sizeof(a.sun_path), "%s", argv[optind]); if (ret <= 0 || ret >= (int)sizeof(a.sun_path)) die("Invalid socket path \"%s\"", argv[optind]); ret = connect(s, (struct sockaddr *)&a, sizeof(a)); if (ret < 0) { die("Failed to connect to %s: %s", a.sun_path, strerror(errno)); } debug("Inbound rules from command line:"); for (i = 0; i < fwd_in.count; i++) { const struct fwd_rule *rule = &fwd_in.rules[i].rule; char rulestr[FWD_RULE_STRLEN]; debug(" %s", fwd_rule_ntop(rule, rulestr, sizeof(rulestr))); } debug("Outbound rules from command line:"); for (i = 0; i < fwd_out.count; i++) { const struct fwd_rule *rule = &fwd_out.rules[i].rule; char rulestr[FWD_RULE_STRLEN]; debug(" %s", fwd_rule_ntop(rule, rulestr, sizeof(rulestr))); } debug("Connected to passt/pasta control socket"); ret = seread_var(s, &hello); if (ret < 0) die("Couldn't read server greeting: %s", strerror(errno)); if (memcmp(hello.magic, PESTO_SERVER_MAGIC, sizeof(hello.magic))) die("Bad magic number from server"); s_version = ntohl(hello.version); debug("Server protocol version: %"PRIu32, s_version); if (s_version > PESTO_PROTOCOL_VERSION) { die("Unknown server protocol version %"PRIu32" > %"PRIu32"\n", s_version, PESTO_PROTOCOL_VERSION); } /* cppcheck-suppress knownConditionTrueFalse */ if (!s_version) { if (PESTO_PROTOCOL_VERSION) die("Unsupported experimental server protocol"); FPRINTF(stderr, "Warning: Using experimental protocol version, client and server must match\n"); } state = pesto_read_pifs(s); while (pesto_read_rules(s, state)) ; show_state(state); if (fwd_in.count) { sewrite_u8(s, PIF_HOST); sewrite_u32(s, fwd_in.count); for (i = 0; i < fwd_in.count; i++) fwd_rule_sewrite(s, &fwd_in.rules[i].rule); } if (fwd_out.count) { sewrite_u8(s, PIF_SPLICE); sewrite_u32(s, fwd_out.count); for (i = 0; i < fwd_out.count; i++) fwd_rule_sewrite(s, &fwd_out.rules[i].rule); } sewrite_u8(s, PIF_NONE); exit(0); }