[PATCH v9 00/23] Dynamic configuration update implementation
Changes in v9: * Rework Makefile changes and solve conflicts so that we can drop the dependency on "Improvements to static checker invocation" * In 8/23, drop the "experimental" note from the man page * In 10/23, switch to protocol version 1, add basil to the magic sauce * In 11/23, initialise struct pesto_pif_info sent by the server (details in commit message) * In 15/23, add description for -s / --show to pesto.1 as well * In 18/23, make comments about redundant checks more verbose * In 19/23, make it clear that tables handled by fwd_rule_del() can't refer to any open socket, add a TODO to fwd_rule_clear() in that sense as well, and use pif_conf_by_name() in pesto to find the table we need to clear * Add 19/23 to 23/23 (LSM policies, packaging stuff) to make pesto ready for shipping Changes in v8: * Implement --add, --delete, and --clear in 19/19, to add forwarding rules instead of replacing tables, delete existing rules, and explicitly clear tables * Address Laurent's comments for 15/19 and 17/19 * In 10/19, instead of passing SOCK_NONBLOCK to accept4(), explicitly set O_NONBLOCK on the listening socket. Using SOCK_NONBLOCK doesn't do what we want, as it results in setting O_NONBLOCK on the new socket rather than on the listening one * Note: 18/19 is left as it is, I didn't address pending comments yet * Note: this doesn't include yet changes for AppArmor and SELinux policies, as well as changes for the template Fedora spec file. I'm still working on them Changes in v7: * Addressed comments from Laurent in 6/18, 8/18, 9/18, 10/18, 11/18, 12/18, 14/18, 15/18 (details in commit messages of single patches, before my Signed-off-by) * Note: this doesn't include yet --add and --delete, I'm still working on that Changes in v6: * Addressed comments from Jon in 10/18, 11/18, 14/18, and 16/18 * Dodged all warnings from static checkers (Coverity Scan and clang-tidy) with changes in 10/18, 11/18, 16/18, and with a new patch, 18/18 * This does *not* include yet the implementation of --add and --delete switches for pesto as I originally intended, I'm rather far from being done with those. At the moment I just have a "mode selection" implementation for command line parsing but merging rules to / removing rules from / clearing the current table is something I barely started (and what I have at the moment isn't really valuable anyway) David wrote: --- Here's the next draft of dynamic configuration updates. This now can successfully update rules, though I've not tested it very extensively. Patches 1..8/18 are preliminary reworks that make sense even without pesto - feel free to apply if you're happy with them. I don't think the rest should be applied yet; we need to at least harden it so passt can't be blocked indefinitely by a client which sends a partial update then waits. Based on my earlier series reworking static checking invocation. TODO: - Don't allow a client which sends a partial configuration then blocks also block passt - Allow pesto to clear existing configuration, not just add - Allow pesto selectively delete existing rules, not just add Changes in v5: * If multiple clients connect at once, they're now blocked until the first one finishes, instead of later ones being discarded Changes in v4: * Merged with remainder of forward rule parsing rework series * Fix some bugs in rule checking pointed out by Laurent * Significantly cleaned up option parsing code * Changed from replacing all existing rules to adding new rules (clear and remove still TBD) * Somewhat simplified protocol (pif names and rules sent in a single pass) * pesto is now allocation free * Fixed commit message and style nits pointed out by Stefano Changes in v3: * Removed already applied ASSERT() rename * Renamed serialisation functions * Incorporated Stefano's extensions, reworked and fixed * Several additional cleanups / preliminary reworks Changes in v2: * Removed already applied cleanups * Reworked assert() patch to handle -DNDEBUG properly * Numerous extra patches: * Factored out serialisation helpers and use them for migration as well * Reworked to allow ip.[ch] and inany.[ch] to be shared with pesto * Reworks to share some forwarding rule datatypes with pesto * Implemented sending pif names and current ruleset to pesto --- David Gibson (17): conf, fwd: Stricter rule checking in fwd_rule_add() fwd_rule: Move ephemeral port probing to fwd_rule.c fwd, conf: Move rule parsing code to fwd_rule.[ch] fwd_rule: Move conflict checking back within fwd_rule_add() fwd: Generalise fwd_rules_info() pif: Limit pif names to 128 bytes fwd_rule: Fix some format specifiers pesto: Introduce stub configuration tool pesto, log: Share log.h (but not log.c) with pesto tool pesto, conf: Have pesto connect to passt and check versions pesto: Expose list of pifs to pesto and display them ip: Prepare ip.[ch] for sharing with pesto tool inany: Prepare inany.[ch] for sharing with pesto tool pesto: Read current ruleset from passt/pasta and optionally display it pesto: Parse and add new rules from command line pesto, conf: Send updated rules from pesto back to passt/pasta conf, fwd: Allow switching to new rules received from pesto Stefano Brivio (6): fwd_rule: Fix static checkers warnings in fwd_rule_add() pesto, conf, fwd_rule: Add options and modes to add, delete, clear rules apparmor: Add policy file for pesto selinux: Add file context and type enforcement for pesto fedora: Install pesto, its SELinux policy, and the man page from the spec file hooks: Copy static build of pesto and related man page to server .gitignore | 2 + Makefile | 35 +- common.h | 116 ++++++ conf.c | 696 +++++++++++++++------------------ conf.h | 2 + contrib/apparmor/usr.bin.pesto | 23 ++ contrib/fedora/passt.spec | 14 +- contrib/selinux/pesto.fc | 11 + contrib/selinux/pesto.te | 95 +++++ epoll_type.h | 4 + flow.c | 4 +- fwd.c | 169 ++------ fwd.h | 41 +- fwd_rule.c | 691 ++++++++++++++++++++++++++++++-- fwd_rule.h | 68 +++- hooks/pre-push | 1 + inany.c | 19 +- inany.h | 17 +- ip.c | 56 +-- ip.h | 4 +- lineread.c | 2 +- log.h | 53 ++- passt.1 | 5 + passt.c | 8 + passt.h | 8 + pesto.1 | 275 +++++++++++++ pesto.c | 522 +++++++++++++++++++++++++ pesto.h | 54 +++ pif.c | 2 +- pif.h | 7 +- serialise.c | 7 + serialise.h | 1 + siphash.h | 13 + tap.c | 52 +++ util.h | 110 +----- 35 files changed, 2393 insertions(+), 794 deletions(-) create mode 100644 common.h create mode 100644 contrib/apparmor/usr.bin.pesto create mode 100644 contrib/selinux/pesto.fc create mode 100644 contrib/selinux/pesto.te create mode 100644 pesto.1 create mode 100644 pesto.c create mode 100644 pesto.h -- 2.43.0
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
From: David Gibson
The new checks are actually sufficient but not enough for Coverity
Scan. Now that fwd->sock_count and new->last are affected or supplied
by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
Instead of just being able to add to the existing tables, implement
an explicit --clear option to replace them, which now becomes the
default behaviour, and implement explicit --add and --delete options
to maintain the table and add or delete specific ports.
The option --clear PIF forces the clearing of a table, instead.
These options can be combined arbitrarily and are handled as
sequential commands, as now described in pesto(1).
If no option is given before forwarding specifiers for a matching
table, the command line is interpreted as a replacement of the
existing rules.
To this end:
- there's no protocol change, as pesto is anyway sending updated
copies of the table
- the forwarding table functions now include a new fwd_rule_del(),
which deletes existing rule only if a matching one is found
- a trivial fwd_rule_clear() is factored out from the existing
conf_handler() implementation, so that it can be directly used
in pesto
The entry points for parsing of port specifiers now take an additional
'del' parameter which is passed down all the way before reaching the
fwd_rule_add() implementation. If a rule should be deleted, at that
point, fwd_rule_del() is called instead.
Signed-off-by: Stefano Brivio
It needs to connect to passt and pasta, whether they're started as
root or not, and the control socket can be anywhere.
Signed-off-by: Stefano Brivio
Loosely inspired by passt-repair's policy: pesto needs to be able to
run, check networking entries under /proc (for ip_local_port_range),
talk to passt and pasta, wherever the control socket is.
Signed-off-by: Stefano Brivio
It's time to ship it in packages.
Signed-off-by: Stefano Brivio
Signed-off-by: Stefano Brivio
On 5/6/26 11:22, Stefano Brivio wrote:
From: David Gibson
Build a new "pesto" binary, which will become the tool to update a running passt/pasta's configuration. For now, we just build a stub binary which sets up a basic environment, parses trivial command line options but does nothing else.
Signed-off-by: David Gibson
[sbrivio: Dropped leading _ from comment to include guard endif, reported by Laurent] [sbrivio: Formatting changes in pesto.1: use 80 columns instead of wrapping at about 75. Add description for -d, -h, --version.] [sbrivio: Drop note from man page about pesto being experimental, we're shipping it so that people can actually use it] [sbrivio: Fix conflicts in the Makefile as I'm not applying the previous series reworking it] Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- .gitignore | 2 + Makefile | 25 ++++++---- common.h | 24 ++++++++++ pesto.1 | 59 ++++++++++++++++++++++++ pesto.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pesto.h | 12 +++++ util.h | 12 +---- 7 files changed, 246 insertions(+), 20 deletions(-) create mode 100644 common.h create mode 100644 pesto.1 create mode 100644 pesto.c create mode 100644 pesto.h
diff --git a/.gitignore b/.gitignore index 3c16adc..3e40d9f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,11 @@ /pasta /pasta.avx2 /passt-repair +/pesto /qrap /pasta.1 /seccomp.h +/seccomp_pesto.h /seccomp_repair.h /c*.json README.plain.md diff --git a/Makefile b/Makefile index f697c12..76b9b5c 100644 --- a/Makefile +++ b/Makefile @@ -45,17 +45,18 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \ vhost_user.c virtio.c vu_common.c QRAP_SRCS = qrap.c PASST_REPAIR_SRCS = passt-repair.c -SRCS = $(PASST_SRCS) $(QRAP_SRCS) $(PASST_REPAIR_SRCS) +PESTO_SRCS = pesto.c +SRCS = $(PASST_SRCS) $(QRAP_SRCS) $(PASST_REPAIR_SRCS) $(PESTO_SRCS)
-MANPAGES = passt.1 pasta.1 qrap.1 passt-repair.1 +MANPAGES = passt.1 pasta.1 pesto.1 qrap.1 passt-repair.1
PASST_HEADERS = arch.h arp.h bitmap.h checksum.h conf.h dhcp.h dhcpv6.h \ epoll_ctl.h flow.h fwd.h fwd_rule.h flow_table.h icmp.h icmp_flow.h \ inany.h iov.h ip.h isolation.h lineread.h log.h migrate.h ndp.h \ - netlink.h packet.h passt.h pasta.h pcap.h pif.h repair.h serialise.h \ - siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h \ - tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h vhost_user.h \ - virtio.h vu_common.h + netlink.h packet.h passt.h pasta.h pesto.h pcap.h pif.h repair.h \ + serialise.h siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h \ + tcp_splice.h tcp_vu.h udp.h udp_flow.h udp_internal.h udp_vu.h util.h \ + vhost_user.h virtio.h vu_common.h HEADERS = $(PASST_HEADERS) seccomp.h
C := \#include
\nint main(){int a=getrandom(0, 0, 0);} @@ -76,9 +77,9 @@ mandir ?= $(datarootdir)/man man1dir ?= $(mandir)/man1 ifeq ($(TARGET_ARCH),x86_64) -BIN := passt passt.avx2 pasta pasta.avx2 qrap passt-repair +BIN := passt passt.avx2 pasta pasta.avx2 qrap passt-repair pesto else -BIN := passt pasta qrap passt-repair +BIN := passt pasta qrap passt-repair pesto endif
all: $(BIN) $(MANPAGES) docs @@ -92,6 +93,9 @@ seccomp.h: seccomp.sh $(PASST_SRCS) $(PASST_HEADERS) seccomp_repair.h: seccomp.sh $(PASST_REPAIR_SRCS) @ ARCH="$(TARGET_ARCH)" CC="$(CC)" ./seccomp.sh seccomp_repair.h $(PASST_REPAIR_SRCS)
+seccomp_pesto.h: seccomp.sh $(PESTO_SRCS) + @ ARCH="$(TARGET_ARCH)" CC="$(CC)" ./seccomp.sh seccomp_pesto.h $(PESTO_SRCS) + passt: $(PASST_SRCS) $(HEADERS) $(CC) $(FLAGS) $(CFLAGS) $(CPPFLAGS) $(PASST_SRCS) -o passt $(LDFLAGS)
@@ -111,6 +115,9 @@ qrap: $(QRAP_SRCS) passt.h passt-repair: $(PASST_REPAIR_SRCS) seccomp_repair.h $(CC) $(FLAGS) $(CFLAGS) $(CPPFLAGS) $(PASST_REPAIR_SRCS) -o passt-repair $(LDFLAGS)
+pesto: $(PESTO_SRCS) $(PESTO_HEADERS) seccomp_pesto.h + $(CC) $(FLAGS) $(CFLAGS) $(CPPFLAGS) $(PESTO_SRCS) -o pesto $(LDFLAGS) + valgrind: EXTRA_SYSCALLS += rt_sigprocmask rt_sigtimedwait rt_sigaction \ rt_sigreturn getpid gettid kill clock_gettime \ mmap|mmap2 munmap open unlink gettimeofday futex \ @@ -120,7 +127,7 @@ valgrind: all
.PHONY: clean clean: - $(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h pasta.1 \ + $(RM) $(BIN) *~ *.o seccomp.h seccomp_repair.h seccomp_pesto.h pasta.1 \ passt.tar passt.tar.gz *.deb *.rpm \ passt.pid README.plain.md
diff --git a/common.h b/common.h new file mode 100644 index 0000000..f3506b4 --- /dev/null +++ b/common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright Red Hat + * Author: David Gibson
+ * + * Definitions used by both passt/pasta and other tools + */ + +#ifndef COMMON_H +#define COMMON_H + +#include + +#define VERSION_BLOB \ + VERSION "\n" \ + "Copyright Red Hat\n" \ + "GNU General Public License, version 2 or later\n" \ + " https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\n" \ + "This is free software: you are free to change and redistribute it.\n" \ + "There is NO WARRANTY, to the extent permitted by law.\n\n" + +/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */ +#define FPRINTF(f, ...) (void)fprintf(f, __VA_ARGS__) + +#endif /* COMMON_H */ diff --git a/pesto.1 b/pesto.1 new file mode 100644 index 0000000..9f54362 --- /dev/null +++ b/pesto.1 @@ -0,0 +1,59 @@ +.\" SPDX-License-Identifier: GPL-2.0-or-later +.\" Copyright Red Hat +.\" Author: David Gibson +.TH pesto 1 + +.SH NAME +.B pesto +\- Configure a running \fBpasst\fR(1) or \fBpasta\fR(1) instance. + +.SH SYNOPSIS +.B pesto +[\fIOPTION\fR]... \fIPATH\fR + +.SH DESCRIPTION + +.B pesto +is a client to view and update the port forwarding configuration of a running +\fBpasst\fR(1) or \fBpasta\fR(1) instance. + +\fIPATH\fR gives the path to the UNIX domain socket created by \fBpasst\fR or +\fBpasta\fR. It should match the \fB-c\fR command line option given to that +instance. + +.SH OPTIONS + +.TP +.BR \-d ", " \-\-debug +Be verbose. + +.TP +.BR \-h ", " \-\-help +Display a help message and exit. + +.TP +.BR \-\-version +Show version and exit. + +.SH AUTHORS + +Stefano Brivio , +David Gibson . + +.SH REPORTING BUGS + +Please report issues on the bug tracker at https://bugs.passt.top/, or send a +message to the passt-user@passt.top mailing list, see https://lists.passt.top/. + +.SH COPYRIGHT + +Copyright Red Hat + +\fBpesto\fR is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or (at +your option) any later version. + +.SH SEE ALSO + +\fBpasst\fR(1), \fBpasta\fR(1), \fBunix\fR(7). diff --git a/pesto.c b/pesto.c new file mode 100644 index 0000000..9f2fa5d --- /dev/null +++ b/pesto.c @@ -0,0 +1,132 @@ +// 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 "pesto.h" + +static bool debug_flag = false; + +static char stdout_buf[BUFSIZ]; + +#define die(...) \ + do { \ + FPRINTF(stderr, __VA_ARGS__); \ + FPRINTF(stderr, "\n"); \ + exit(EXIT_FAILURE); \ + } while (0) + +/** + * usage() - Print usage, exit with given status code + * @name: Executable name + * @f: Stream to print usage info to + * @status: Status code for exit(2) + * + * #syscalls:pesto exit_group fstat write + */ +static void usage(const char *name, FILE *f, int status) +{ + FPRINTF(f, "Usage: %s [OPTION]... PATH\n", name); + FPRINTF(f, + "\n" + " -d, --debug Print debugging messages\n" + " -h, --help Display this help message and exit\n" + " --version Show version and exit\n"); + exit(status); +} + +/** + * main() - Dynamic reconfiguration client main program + * @argc: Argument count + * @argv: Arguments: socket path, operation, port specifiers + * + * Return: 0 on success, won't return on failure + * + * #syscalls:pesto exit_group fstat read write + */ +int main(int argc, char **argv) +{ + const struct option options[] = { + {"debug", no_argument, NULL, 'd' }, + {"help", no_argument, NULL, 'h' }, + {"version", no_argument, NULL, 1 }, + { 0 }, + }; + const char *optstring = "dh"; + struct sock_fprog prog; + int optname; + + prctl(PR_SET_DUMPABLE, 0); + + 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"); + + /* Explicitly set stdout buffer, otherwise printf() might allocate, + * breaking our seccomp profile. + */ + if (setvbuf(stdout, stdout_buf, _IOFBF, sizeof(stdout_buf))) + die("Failed to set stdout buffer"); + + 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 'd': + debug_flag = 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); + + printf("debug_flag=%d, path=\"%s\"\n", debug_flag, argv[optind]); + + die("pesto is not implemented yet"); +} diff --git a/pesto.h b/pesto.h new file mode 100644 index 0000000..e9b329f --- /dev/null +++ b/pesto.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright Red Hat + * Author: David Gibson + * + * Definitions and functions used by both client and server of the configuration + * update protocol (pesto). + */ + +#ifndef PESTO_H +#define PESTO_H + +#endif /* PESTO_H */ diff --git a/util.h b/util.h index 92aeabc..770ff93 100644 --- a/util.h +++ b/util.h @@ -19,16 +19,9 @@ #include #include +#include "common.h" #include "log.h"
-#define VERSION_BLOB \ - VERSION "\n" \ - "Copyright Red Hat\n" \ - "GNU General Public License, version 2 or later\n" \ - " https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\n" \ - "This is free software: you are free to change and redistribute it.\n" \ - "There is NO WARRANTY, to the extent permitted by law.\n\n" - #ifndef SECCOMP_RET_KILL_PROCESS #define SECCOMP_RET_KILL_PROCESS SECCOMP_RET_KILL #endif @@ -307,9 +300,6 @@ static inline bool mod_between(unsigned x, unsigned i, unsigned j, unsigned m) return mod_sub(x, i, m) < mod_sub(j, i, m); }
-/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */ -#define FPRINTF(f, ...) (void)fprintf(f, __VA_ARGS__) - void raw_random(void *buf, size_t buflen);
/*
On 5/6/26 11:22, Stefano Brivio wrote:
From: David Gibson
Start implementing pesto in earnest. Create a control/configuration socket in passt. Have pesto connect to it and retrieve a server greeting Perform some basic version checking.
Signed-off-by: David Gibson
[sbrivio: Avoid potential recursive calling between conf_accept() and conf_close(), reported by clang-tidy] [sbrivio: In conf(), check we're not exceeding sizeof(c->control_path) instead of sizeof(c->socket_path), and, in pesto's main(), print argv[optind] instead of argv[1] to indicate an invalid socket path, both reported by Jon Maloy] [sbrivio: In pesto's main(), drop unnecessary newline from error message, reported by Laurent] [sbrivio: Don't use SOCK_NONBLOCK on accept4(), as that only applies to the *new* file descriptor, which we don't want -- set O_NONBLOCK on the listening file descriptor using fcntl()] [sbrivio: Switch to protocol version 1, and reflect the true magic behind pesto, i.e. basil, into the magic string]
But the real value is in pine nuts (110 €/kg here). (I make mine with bear's garlic and almonds)
[sbrivio: Fix conflicts in the Makefile caused by the fact that I'm not merging a previous series reworking it] Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- Makefile | 2 +- conf.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++- conf.h | 2 + epoll_type.h | 4 ++ passt.1 | 5 ++ passt.c | 8 +++ passt.h | 6 ++ pesto.c | 47 ++++++++++++- pesto.h | 22 ++++++ serialise.c | 3 + 10 files changed, 279 insertions(+), 4 deletions(-)
diff --git a/Makefile b/Makefile index 2639472..b1003d8 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \ vhost_user.c virtio.c vu_common.c QRAP_SRCS = qrap.c PASST_REPAIR_SRCS = passt-repair.c -PESTO_SRCS = pesto.c +PESTO_SRCS = pesto.c serialise.c SRCS = $(PASST_SRCS) $(QRAP_SRCS) $(PASST_REPAIR_SRCS) $(PESTO_SRCS)
MANPAGES = passt.1 pasta.1 pesto.1 qrap.1 passt-repair.1 diff --git a/conf.c b/conf.c index 0586107..5ec0072 100644 --- a/conf.c +++ b/conf.c @@ -48,6 +48,10 @@ #include "isolation.h" #include "log.h" #include "vhost_user.h" +#include "epoll_ctl.h" +#include "conf.h" +#include "pesto.h" +#include "serialise.h"
#define NETNS_RUN_DIR "/run/netns"
@@ -541,6 +545,7 @@ static void usage(const char *name, FILE *f, int status) " --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" + " -c, --conf-path PATH Configuration socket path\n" " -h, --help Display this help message and exit\n" " --version Show version and exit\n");
@@ -779,6 +784,9 @@ static void conf_print(const struct ctx *c) char buf[INANY_ADDRSTRLEN]; int i;
+ if (c->fd_control_listen >= 0) + info("Configuration socket: %s", c->control_path); + if (c->ifi4 > 0 || c->ifi6 > 0) { char ifn[IFNAMSIZ];
@@ -1072,6 +1080,19 @@ static void conf_open_files(struct ctx *c) if (c->pidfile_fd < 0) die_perror("Couldn't open PID file %s", c->pidfile); } + + c->fd_control = -1; + if (*c->control_path) { + c->fd_control_listen = sock_unix(c->control_path); + if (c->fd_control_listen < 0) { + die_perror("Couldn't open control socket %s", + c->control_path); + } + if (fcntl(c->fd_control_listen, F_SETFL, O_NONBLOCK)) + die_perror("Couldn't set O_NONBLOCK on control socket"); + } else { + c->fd_control_listen = -1; + } }
/** @@ -1107,6 +1128,25 @@ fail: die("Invalid MAC address: %s", str); }
+/** + * conf_sock_listen() - Start listening for connections on configuration socket + * @c: Execution context + */ +static void conf_sock_listen(const struct ctx *c) +{ + union epoll_ref ref = { .type = EPOLL_TYPE_CONF_LISTEN }; + + if (c->fd_control_listen < 0) + return; + + if (listen(c->fd_control_listen, 0)) + die_perror("Couldn't listen on configuration socket"); + + ref.fd = c->fd_control_listen; + if (epoll_add(c->epollfd, EPOLLIN | EPOLLET, ref)) + die_perror("Couldn't add configuration socket to epoll"); +} + /** * conf() - Process command-line arguments and set configuration * @c: Execution context @@ -1189,9 +1229,10 @@ void conf(struct ctx *c, int argc, char **argv) {"migrate-exit", no_argument, NULL, 29 }, {"migrate-no-linger", no_argument, NULL, 30 }, {"stats", required_argument, NULL, 31 }, + {"conf-path", required_argument, NULL, 'c' }, { 0 }, }; - const char *optstring = "+dqfel:hs:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; + const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt"; bool opt_t = false, opt_T = false, opt_u = false, opt_U = false; char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 }; @@ -1449,6 +1490,13 @@ void conf(struct ctx *c, int argc, char **argv)
c->fd_tap = -1; break; + case 'c': + ret = snprintf(c->control_path, sizeof(c->control_path), + "%s", optarg); + if (ret <= 0 || ret >= (int)sizeof(c->control_path)) + die("Invalid configuration path: %s", optarg); + c->fd_control_listen = c->fd_control = -1; + break; case 'F': errno = 0; fd_tap_opt = strtol(optarg, NULL, 0); @@ -1871,6 +1919,140 @@ void conf(struct ctx *c, int argc, char **argv) fwd_rule_parse('U', "auto", c->fwd[PIF_SPLICE]); }
+ conf_sock_listen(c); + if (!c->quiet) conf_print(c); } + +static void conf_accept(struct ctx *c); + +/** + * conf_close() - Close configuration / control socket and clean up + * @c: Execution context + */ +static void conf_close(struct ctx *c) +{ + debug("Closing configuration socket"); + epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_control, NULL); + close(c->fd_control); + c->fd_control = -1; +} + +/** + * conf_listen_handler() - Handle events on configuration listening socket + * @c: Execution context + * @events: epoll events + */ +void conf_listen_handler(struct ctx *c, uint32_t events) +{ + if (events != EPOLLIN) { + err("Unexpected event 0x%04x on configuration socket", events); + return; + } + + if (c->fd_control >= 0) { + /* Ignore the new connection for now, blocking it until the + * current one finishes. + */ + return; + } + + conf_accept(c); +} + +/** + * conf_accept() - Accept a new control connection + * @c: Execution context + */ +static void conf_accept(struct ctx *c) +{ + struct pesto_hello hello = { + .magic = PESTO_SERVER_MAGIC, + .version = htonl(PESTO_PROTOCOL_VERSION), + }; + union epoll_ref ref = { .type = EPOLL_TYPE_CONF }; + struct ucred uc = { 0 }; + socklen_t len = sizeof(uc); + int fd, rc; + +retry: + err("%s: %i", __func__, __LINE__); + fd = accept4(c->fd_control_listen, NULL, NULL, SOCK_CLOEXEC); + if (fd < 0) { + err("%s: %i", __func__, __LINE__); + if (errno != EAGAIN) + warn_perror("accept4() on configuration listening socket"); + return; + } + + err("%s: %i", __func__, __LINE__); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0) + warn_perror("Can't get configuration client credentials"); + + c->fd_control = ref.fd = fd; + rc = epoll_add(c->epollfd, EPOLLIN | EPOLLET, ref); + if (rc < 0) { + warn_perror("epoll_ctl() on configuration socket"); + goto fail; + } + + rc = write_all_buf(fd, &hello, sizeof(hello)); + if (rc < 0) { + warn_perror("Error writing configuration protocol hello"); + goto fail; + } + + info("Accepted configuration client, PID %i", uc.pid); + if (!PESTO_PROTOCOL_VERSION) { + warn( +"Warning: Using experimental unsupported configuration protocol"); + } + + return; + +fail: + conf_close(c); + goto retry; +} + +/** + * conf_handler() - Handle events on configuration socket + * @c: Execution context + * @events: epoll events + */ +void conf_handler(struct ctx *c, uint32_t events) +{ + if (events & EPOLLIN) { + char discard[BUFSIZ]; + ssize_t n; + + do { + n = read(c->fd_control, discard, sizeof(discard)); + if (n > 0) + debug("Discarded %zd bytes of config data", n); + } while (n > 0); + if (n == 0) { + debug("Configuration client EOF"); + goto close; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) { + err_perror("Error reading config data"); + goto close; + } + } + + if (events & EPOLLHUP) { + debug("Configuration client hangup"); + goto close; + } + + return; + +close: + conf_close(c); + + /* Check if any other clients are waiting to connect */ + conf_accept(c); +} diff --git a/conf.h b/conf.h index b45ad74..16f9718 100644 --- a/conf.h +++ b/conf.h @@ -8,5 +8,7 @@
enum passt_modes conf_mode(int argc, char *argv[]); void conf(struct ctx *c, int argc, char **argv); +void conf_listen_handler(struct ctx *c, uint32_t events); +void conf_handler(struct ctx *c, uint32_t events);
#endif /* CONF_H */ diff --git a/epoll_type.h b/epoll_type.h index a90ffb6..061325a 100644 --- a/epoll_type.h +++ b/epoll_type.h @@ -46,6 +46,10 @@ enum epoll_type { EPOLL_TYPE_REPAIR, /* Netlink neighbour subscription socket */ EPOLL_TYPE_NL_NEIGH, + /* Configuration listening socket */ + EPOLL_TYPE_CONF_LISTEN, + /* Configuration socket */ + EPOLL_TYPE_CONF,
EPOLL_NUM_TYPES, }; diff --git a/passt.1 b/passt.1 index 6303aeb..908fd4a 100644 --- a/passt.1 +++ b/passt.1 @@ -127,6 +127,11 @@ login name and group name can be passed. This requires privileges (either initial effective UID 0 or CAP_SETUID capability) to work. Default is to change to user \fInobody\fR if started as root.
+.TP +.BR \-c ", " \-\-conf-path " " \fIpath " " (EXPERIMENTAL) +Path for configuration and control socket used by \fBpesto\fR(1) to +dynamically update passt or pasta's configuration. + .TP .BR \-h ", " \-\-help Display a help message and exit. diff --git a/passt.c b/passt.c index f84419c..bc42ea3 100644 --- a/passt.c +++ b/passt.c @@ -80,6 +80,8 @@ char *epoll_type_str[] = { [EPOLL_TYPE_REPAIR_LISTEN] = "TCP_REPAIR helper listening socket", [EPOLL_TYPE_REPAIR] = "TCP_REPAIR helper socket", [EPOLL_TYPE_NL_NEIGH] = "netlink neighbour notifier socket", + [EPOLL_TYPE_CONF_LISTEN] = "configuration listening socket", + [EPOLL_TYPE_CONF] = "configuration socket", }; static_assert(ARRAY_SIZE(epoll_type_str) == EPOLL_NUM_TYPES, "epoll_type_str[] doesn't match enum epoll_type"); @@ -303,6 +305,12 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events) case EPOLL_TYPE_NL_NEIGH: nl_neigh_notify_handler(c); break; + case EPOLL_TYPE_CONF_LISTEN: + conf_listen_handler(c, eventmask); + break; + case EPOLL_TYPE_CONF: + conf_handler(c, eventmask); + break; default: /* Can't happen */ assert(0); diff --git a/passt.h b/passt.h index 62b8dcd..b3f049d 100644 --- a/passt.h +++ b/passt.h @@ -158,6 +158,7 @@ struct ip6_ctx { * @foreground: Run in foreground, don't log to stderr by default * @nofile: Maximum number of open files (ulimit -n) * @sock_path: Path for UNIX domain socket + * @control_path: Path for control/configuration UNIX domain socket * @repair_path: TCP_REPAIR helper path, can be "none", empty for default * @pcap: Path for packet capture file * @pidfile: Path to PID file, empty string if not configured @@ -169,6 +170,8 @@ struct ip6_ctx { * @epollfd: File descriptor for epoll instance * @fd_tap_listen: File descriptor for listening AF_UNIX socket, if any * @fd_tap: AF_UNIX socket, tuntap device, or pre-opened socket + * @fd_control_listen: Listening control/configuration socket, if any + * @fd_control: Control/configuration socket, if any * @fd_repair_listen: File descriptor for listening TCP_REPAIR socket, if any * @fd_repair: Connected AF_UNIX socket for TCP_REPAIR helper * @our_tap_mac: Pasta/passt's MAC on the tap link @@ -223,6 +226,7 @@ struct ctx { int foreground; int nofile; char sock_path[UNIX_PATH_MAX]; + char control_path[UNIX_PATH_MAX]; char repair_path[UNIX_PATH_MAX]; char pcap[PATH_MAX];
@@ -240,6 +244,8 @@ struct ctx { int epollfd; int fd_tap_listen; int fd_tap; + int fd_control_listen; + int fd_control; int fd_repair_listen; int fd_repair; unsigned char our_tap_mac[ETH_ALEN]; diff --git a/pesto.c b/pesto.c index f0916e8..ab476c5 100644 --- a/pesto.c +++ b/pesto.c @@ -33,6 +33,7 @@
#include "common.h" #include "seccomp_pesto.h" +#include "serialise.h" #include "pesto.h" #include "log.h"
@@ -66,6 +67,8 @@ static void usage(const char *name, FILE *f, int status) * * Return: 0 on success, won't return on failure * + * #syscalls:pesto socket s390x:socketcall i686:socketcall + * #syscalls:pesto connect shutdown close * #syscalls:pesto exit_group fstat read write */ int main(int argc, char **argv) @@ -76,9 +79,12 @@ int main(int argc, char **argv) {"version", no_argument, NULL, 1 }, { 0 }, }; + struct sockaddr_un a = { AF_UNIX, "" }; const char *optstring = "dh"; + struct pesto_hello hello; struct sock_fprog prog; - int optname; + int optname, ret, s; + uint32_t s_version;
prctl(PR_SET_DUMPABLE, 0);
@@ -122,5 +128,42 @@ int main(int argc, char **argv)
debug("debug_flag=%d, path=\"%s\"", debug_flag, argv[optind]);
- die("pesto is not implemented yet"); + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + die_perror("Failed to create AF_UNIX socket"); + + 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_perror("Failed to connect to %s", a.sun_path); + } + + ret = read_all_buf(s, &hello, sizeof(hello)); + if (ret < 0) + die_perror("Couldn't read server greeting"); + + if (memcmp(hello.magic, PESTO_SERVER_MAGIC, sizeof(hello.magic))) + die("Bad magic number from server"); + + s_version = ntohl(hello.version); + + if (s_version > PESTO_PROTOCOL_VERSION) { + die("Unknown server protocol version %"PRIu32" > %"PRIu32, + 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"); + } + + if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) + die_perror("Error shutting down control socket"); + + exit(0); } diff --git a/pesto.h b/pesto.h index e9b329f..3c93d3e 100644 --- a/pesto.h +++ b/pesto.h @@ -9,4 +9,26 @@ #ifndef PESTO_H #define PESTO_H
+#include
+#include + +#define PESTO_SERVER_MAGIC "basil:s" + +/* Version 0 is reserved for unreleased / unsupported experimental versions */ +#define PESTO_PROTOCOL_VERSION 1 + +/** + * struct pesto_hello - Server introduction message + * @magic: PESTO_SERVER_MAGIC + * @version: Version number + */ +struct pesto_hello { + char magic[8]; + uint32_t version; +} __attribute__ ((__packed__)); + +static_assert(sizeof(PESTO_SERVER_MAGIC) + == sizeof(((struct pesto_hello *)0)->magic), + "PESTO_SERVER_MAGIC has wrong size"); + #endif /* PESTO_H */ diff --git a/serialise.c b/serialise.c index 944e741..346df99 100644 --- a/serialise.c +++ b/serialise.c @@ -6,6 +6,9 @@ * PASTA - Pack A Subtle Tap Abstraction * for network namespace/tap device mode * + * PESTO - Programmable Extensible Socket Translation Orchestrator + * front-end for passt(1) and pasta(1) forwarding configuration + * * serialise.c - Serialisation of data structures over bytestreams * * Copyright Red Hat
On 5/6/26 11:22, Stefano Brivio wrote:
From: David Gibson
This adds parsing of options using fwd_rule_parse(), validates them and adds them to the existing rules. It doesn't yet send those rules back to passt or pasta.
Message-ID: <20260322141843.4095972-3-sbrivio@redhat.com> [dwg: Based on an early draft by Stefano] Signed-off-by: David Gibson
[sbrivio: Recycled usage messages for -T and -U from conf.c as suggested by Laurent, dropped unrelated whitespace change] [sbrivio: Add description of -t, -u, -T, -U to pesto.1] [sbrivio: Fix conflicts in Makefile] [sbrivio: Add description of -s to pesto.1 as well] Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- fwd_rule.c | 2 +- fwd_rule.h | 1 + pesto.1 | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pesto.c | 111 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 240 insertions(+), 5 deletions(-)
diff --git a/fwd_rule.c b/fwd_rule.c index c2824d5..b55e4df 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -187,7 +187,7 @@ static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule * * * Return: 0 on success, negative error code on failure */ -static int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) +int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) { /* Flags which can be set from the caller */ const uint8_t allowed_flags = FWD_WEAK | FWD_SCAN | FWD_DUAL_STACK_ANY; diff --git a/fwd_rule.h b/fwd_rule.h index 330d49e..f43b37d 100644 --- a/fwd_rule.h +++ b/fwd_rule.h @@ -103,6 +103,7 @@ const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size); void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd); int fwd_rule_read(int fd, struct fwd_rule *rule); int fwd_rule_write(int fd, const struct fwd_rule *rule); +int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new);
/** * fwd_rules_dump() - Dump forwarding rules diff --git a/pesto.1 b/pesto.1 index 9f54362..1e1c0f3 100644 --- a/pesto.1 +++ b/pesto.1 @@ -31,6 +31,137 @@ Be verbose. .BR \-h ", " \-\-help Display a help message and exit.
+.TP +.BR \-s ", " \-\-show +Show the forwarding configuration before and after changes are applied. + +.TP +.BR \-t ", " \-\-tcp-ports " " \fIspec +Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of: +.RS + +.TP +.BR none +Don't forward any ports + +.TP +[\fIaddress\fR[\fB%\fR\fIinterface\fR]\fB/\fR]\fIports\fR ... +Specific ports to forward. Optionally, a specific listening address +and interface name (since Linux 5.7) can be specified. \fIports\fR +may be either: +.RS +.TP +\fBall\fR +Forward all unbound, non-ephemeral ports, as permitted by current capabilities. +No failures are reported for unavailable ports, unless no ports could be +forwarded at all. +.RE + +.RS +or a comma-separated list of entries which may be any of: +.TP +\fIfirst\fR[\fB-\fR\fIlast\fR][\fB:\fR\fItofirst\fR[\fB-\fR\fItolast\fR]] +Include range. Forward port numbers between \fIfirst\fR and \fIlast\fR +(inclusive) to ports between \fItofirst\fR and \fItolast\fR. If +\fItofirst\fR and \fItolast\fR are omitted, assume the same as +\fIfirst\fR and \fIlast\fR. If \fIlast\fR is omitted, assume the same +as \fIfirst\fR. + +.TP +\fB~\fR\fIfirst\fR[\fB-\fR\fIlast\fR] +Exclude range. Don't forward port numbers between \fIfirst\fR and +\fIlast\fR. This takes precedences over include ranges. + +.TP +.BR auto +\fBpasta\fR only. Only forward ports in the specified set if the +target ports are bound in the namespace. The list of ports is +periodically derived (every second) from listening sockets reported by +\fI/proc/net/tcp\fR and \fI/proc/net/tcp6\fR, see \fBproc\fR(5). +.RE + +Specifying excluded ranges only implies that all other non-ephemeral +ports are forwarded. Specifying no ranges at all implies forwarding +all non-ephemeral ports permitted by current capabilities. In this +case, no failures are reported for unavailable ports, unless no ports +could be forwarded at all. + +Examples: +.RS +.TP +-t all +Forward all unbound, non-ephemeral ports as permitted by current +capabilities to the corresponding port on the guest or namespace +.TP +-t ::1/all +For the local address ::1, forward all unbound, non-ephemeral ports as +permitted by current capabilities +.TP +-t 22 +Forward local port 22 to port 22 on the guest or namespace +.TP +-t 22:23 +Forward local port 22 to port 23 on the guest or namespace +.TP +-t 22,25 +Forward local ports 22 and 25 to ports 22 and 25 on the guest or namespace +.TP +-t 22-80 +Forward local ports between 22 and 80 to corresponding ports on the guest or +namespace +.TP +-t 22-80:32-90 +Forward local ports between 22 and 80 to ports between 32 and 90 on the guest or +namespace +.TP +-t 192.0.2.1/22 +Forward local port 22, bound to 192.0.2.1, to port 22 on the guest or namespace +.TP +-t 192.0.2.1%eth0/22 +Forward local port 22, bound to 192.0.2.1 and interface eth0, to port 22 +.TP +-t %eth0/22 +Forward local port 22, bound to any address on interface eth0, to port 22 +.TP +-t 2000-5000,~3000-3010 +Forward local ports between 2000 and 5000, except for those between 3000 and +3010 +.TP +-t 192.0.2.1/20-30,~25 +For the local address 192.0.2.1, forward ports between 20 and 24 and between 26 +and 30 +.TP +-t ~20000-20010 +Forward all ports to the guest, except for the range from 20000 to 20010 +.TP +-t auto +Automatically forward any ports which are bound in the namespace +.TP +-t ::1/auto +Automatically forward any ports which are bound in the namespace, +listening only on local port ::1 +.TP +-t 8000-8010,auto +Forward ports in the range 8000-8010 if and only if they are bound in +the namespace +.RE +.RE + +.TP +.BR \-u ", " \-\-udp-ports " " \fIspec +Configure UDP port forwarding to guest. \fIspec\fR is as described for TCP +above. + +.TP +.BR \-T ", " \-\-tcp-ns " " \fIspec +Configure TCP port forwarding from target namespace to init namespace. +\fIspec\fR is as described above. + +.TP +.BR \-U ", " \-\-udp-ns " " \fIspec +Configure UDP port forwarding from target namespace to init namespace. +\fIspec\fR is as described above. + .TP .BR \-\-version Show version and exit. diff --git a/pesto.c b/pesto.c index 92a8cb2..16b3a5a 100644 --- a/pesto.c +++ b/pesto.c @@ -55,6 +55,43 @@ 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" + " [ADDR[%%IFACE]/]PORTS: forward specific ports\n" + " PORTS is either 'all' (forward all unbound, non-ephemeral\n" + " ports), or a comma-separated list of ports, optionally\n" + " ranged with '-' and optional target ports after ':'.\n" + " Ranges can be reduced by excluding ports or ranges\n" + " prefixed by '~'.\n" + " The 'auto' keyword may be given to only forward\n" + " ports which are bound in the target namespace\n" + " Examples:\n" + " -t all Forward all ports\n" + " -t 127.0.0.1/all Forward all ports from local address\n" + " 127.0.0.1\n" + " -t 22 Forward local port 22 to 22\n" + " -t 22:23 Forward local port 22 to 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 ports except for 25\n" + " -t auto Forward all ports bound in namespace\n" + " -t 192.0.2.2/auto Forward ports from 192.0.2.2 if\n" + " they are bound in the namespace\n" + " -t 8000-8010,auto Forward ports 8000-8010 if they\n" + " are bound in the namespace\n" + " -u, --udp-ports SPEC UDP inbound port forwarding\n" + " SPEC is as described for TCP above\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" + " -s, --show Show configuration before and after\n" " -d, --debug Print debugging messages\n" " -h, --help Display this help message and exit\n" " --version Show version and exit\n"); @@ -207,6 +244,8 @@ static void show_conf(const struct configuration *conf) fwd_rules_dump(printf, pc->fwd.rules, pc->fwd.count, " ", "\n"); } + /* Flush stdout, so this doesn't get misordered with later debug()s */ + (void)fflush(stdout); }
/** @@ -218,7 +257,7 @@ static void show_conf(const struct configuration *conf) * * #syscalls:pesto socket s390x:socketcall i686:socketcall * #syscalls:pesto connect shutdown close - * #syscalls:pesto exit_group fstat read write + * #syscalls:pesto exit_group fstat read write openat */ int main(int argc, char **argv) { @@ -226,11 +265,18 @@ int main(int argc, char **argv) {"debug", no_argument, NULL, 'd' }, {"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' }, + {"show", no_argument, NULL, 's' }, { 0 }, }; + struct pif_configuration *inbound, *outbound; struct sockaddr_un a = { AF_UNIX, "" }; + const char *optstring = "dht:u:T:U:s"; struct configuration conf = { 0 }; - const char *optstring = "dh"; + bool update = false, show = false; struct pesto_hello hello; struct sock_fprog prog; int optname, ret, s; @@ -251,6 +297,8 @@ int main(int argc, char **argv) if (setvbuf(stdout, stdout_buf, _IOFBF, sizeof(stdout_buf))) die_perror("Failed to set stdout buffer");
+ fwd_probe_ephemeral(); + do { optname = getopt_long(argc, argv, optstring, options, NULL);
@@ -258,6 +306,16 @@ int main(int argc, char **argv) case -1: case 0: break; + case 't': + case 'u': + case 'T': + case 'U': + /* Parse these options after we've read state from passt/pasta */ + update = true; + break; + case 's': + show = true; + break; case 'h': usage(argv[0], stdout, EXIT_SUCCESS); break; @@ -290,6 +348,8 @@ int main(int argc, char **argv) die_perror("Failed to connect to %s", a.sun_path); }
+ debug("Connected to passt/pasta control socket"); + ret = read_all_buf(s, &hello, sizeof(hello)); if (ret < 0) die_perror("Couldn't read server greeting"); @@ -327,9 +387,52 @@ int main(int argc, char **argv) while (read_pif_conf(s, &conf)) ;
- printf("passt/pasta configuration (%s)\n", a.sun_path); - show_conf(&conf); + if (!update) { + printf("passt/pasta configuration (%s)\n", a.sun_path); + show_conf(&conf); + goto noupdate; + } + + if (show) { + printf("Previous configuration (%s)\n", a.sun_path); + show_conf(&conf); + } + + inbound = pif_conf_by_name(&conf, "HOST"); + outbound = pif_conf_by_name(&conf, "SPLICE"); + + optind = 0; + do { + optname = getopt_long(argc, argv, optstring, options, NULL); + + switch (optname) { + case 't': + case 'u': + if (!inbound) { + die("Can't use -%c, no inbound interface", + optname); + } + fwd_rule_parse(optname, optarg, &inbound->fwd); + break; + case 'T': + case 'U': + if (!outbound) { + die("Can't use -%c, no outbound interface", + optname); + } + fwd_rule_parse(optname, optarg, &outbound->fwd); + break; + default: + continue; + } + } while (optname != -1); + + if (show) { + printf("Updated configuration (%s)\n", a.sun_path); + show_conf(&conf); + }
+noupdate: if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) die_perror("Error shutting down control socket");
On 5/6/26 11:22, Stefano Brivio wrote:
From: David Gibson
Extend pesto to send the updated rule configuration back to passt/pasta. Extend passt/pasta to read the new configuration and store the new rules in a "pending" table. We don't yet attempt to activate them.
Signed-off-by: Stefano Brivio
[dwg: Based on an early draft from Stefano] [sbrivio: Add redundant check on interface names being terminated in conf_recv_rules(), to make static checkers happy] [sbrivio: Make conf_recv_rules() return -1 if fwd_rule_read() fails, as suggested by Jon Maloy] [sbrivio: Fix conflicts in Makefile] Signed-off-by: David Gibson
Reviewed-by: Laurent Vivier
--- conf.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++-------- fwd.c | 10 +++++- passt.h | 2 ++ pesto.c | 35 +++++++++++++++++++++ 4 files changed, 127 insertions(+), 14 deletions(-)
diff --git a/conf.c b/conf.c index 9de0ed3..60448d7 100644 --- a/conf.c +++ b/conf.c @@ -1973,6 +1973,62 @@ static int conf_send_rules(const struct ctx *c, int fd) return 0; }
+/** + * conf_recv_rules() - Receive forwarding rules from configuration client + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + */ +static int conf_recv_rules(const struct ctx *c, int fd) +{ + while (1) { + struct fwd_table *fwd; + struct fwd_rule r; + uint32_t count; + uint8_t pif; + unsigned i; + + if (read_u8(fd, &pif)) + return -1; + + if (pif == PIF_NONE) + break; + + if (pif >= ARRAY_SIZE(c->fwd_pending) || + !(fwd = c->fwd_pending[pif])) { + err("Received rules for non-existent table"); + return -1; + } + + if (read_u32(fd, &count)) + return -1; + + if (count > MAX_FWD_RULES) { + err("Received %"PRIu32" rules (maximum %u)", + count, MAX_FWD_RULES); + return -1; + } + + for (i = 0; i < count; i++) { + if (fwd_rule_read(fd, &r)) + return -1; + + if (r.ifname[sizeof(r.ifname) - 1]) { + err("Interface name was not NULL terminated"); + return -1; + } + /* Redundant, to make static checkers happy */ + r.ifname[sizeof(r.ifname) - 1] = '\0'; + + if (fwd_rule_add(fwd, &r) < 0) + return -1; + } + } + + return 0; +} + /** * conf_close() - Close configuration / control socket and clean up * @c: Execution context @@ -2076,21 +2132,33 @@ fail: void conf_handler(struct ctx *c, uint32_t events) { if (events & EPOLLIN) { - char discard[BUFSIZ]; - ssize_t n; - - do { - n = read(c->fd_control, discard, sizeof(discard)); - if (n > 0) - debug("Discarded %zd bytes of config data", n); - } while (n > 0); - if (n == 0) { - debug("Configuration client EOF"); - goto close; + unsigned pif; + + /* Clear pending tables */ + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + fwd->count = 0; + fwd->sock_count = 0; } - if (errno != EAGAIN && errno != EWOULDBLOCK) { - err_perror("Error reading config data"); + + /* FIXME: this could block indefinitely if the client doesn't + * write as much as it should + */ + if (conf_recv_rules(c, c->fd_control) < 0) goto close; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct fwd_table *fwd = c->fwd_pending[pif]; + + if (!fwd) + continue; + + info("New forwarding rules for %s:", pif_name(pif)); + fwd_rules_dump(info, fwd->rules, fwd->count, + " ", ""); } }
diff --git a/fwd.c b/fwd.c index 8849cfc..d93d2e5 100644 --- a/fwd.c +++ b/fwd.c @@ -247,6 +247,9 @@ void fwd_neigh_table_init(const struct ctx *c) static struct fwd_table fwd_in; static struct fwd_table fwd_out;
+static struct fwd_table fwd_in_pending; +static struct fwd_table fwd_out_pending; + /** * fwd_rule_init() - Initialise forwarding tables * @c: Execution context @@ -269,10 +272,15 @@ void fwd_rule_init(struct ctx *c) caps |= FWD_CAP_IFNAME;
fwd_in.caps = fwd_out.caps = caps; + fwd_in_pending.caps = fwd_out_pending.caps = caps;
c->fwd[PIF_HOST] = &fwd_in; - if (c->mode == MODE_PASTA) + c->fwd_pending[PIF_HOST] = &fwd_in_pending; + + if (c->mode == MODE_PASTA) { c->fwd[PIF_SPLICE] = &fwd_out; + c->fwd_pending[PIF_SPLICE] = &fwd_out_pending; + } }
/** diff --git a/passt.h b/passt.h index b3f049d..1726965 100644 --- a/passt.h +++ b/passt.h @@ -188,6 +188,7 @@ struct ip6_ctx { * @pasta_ifi: Index of namespace interface for pasta * @pasta_conf_ns: Configure namespace after creating it * @fwd: Forwarding tables + * @fwd_pending: Pending forward tables * @no_tcp: Disable TCP operation * @tcp: Context for TCP protocol handler * @no_udp: Disable UDP operation @@ -270,6 +271,7 @@ struct ctx { int pasta_conf_ns;
struct fwd_table *fwd[PIF_NUM_TYPES]; + struct fwd_table *fwd_pending[PIF_NUM_TYPES];
int no_tcp; struct tcp_ctx tcp; diff --git a/pesto.c b/pesto.c index 16b3a5a..73fdc39 100644 --- a/pesto.c +++ b/pesto.c @@ -230,6 +230,39 @@ static bool read_pif_conf(int fd, struct configuration *conf) return true; }
+/** + * send_conf() - Send updated configuration to passt/pasta + * @fd: Control socket + * @conf: Updated configuration + */ +static void send_conf(int fd, const struct configuration *conf) +{ + unsigned i; + + for (i = 0; i < conf->npifs; i++) { + const struct pif_configuration *pc = &conf->pif[i]; + unsigned j; + + if (write_u8(fd, pc->pif) < 0) + goto fail; + + if (write_u32(fd, pc->fwd.count) < 0) + goto fail; + + for (j = 0; j < pc->fwd.count; j++) { + if (fwd_rule_write(fd, &pc->fwd.rules[j]) < 0) + goto fail; + } + } + + if (write_u8(fd, PIF_NONE) < 0) + goto fail; + return; + +fail: + die_perror("Error writing to control socket"); +} + /** * show_conf() - Show current configuration obtained from passt/pasta * @conf: Configuration description @@ -432,6 +465,8 @@ int main(int argc, char **argv) show_conf(&conf); }
+ send_conf(s, &conf); + noupdate: if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) die_perror("Error shutting down control socket");
On 5/6/26 11:22, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- fwd_rule.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..200f4b5 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,24 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant (see check just above), to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant, but not for static checkers, that might be missing that + * due to the check on 'num' above against ARRAY_SIZE(fwd->socks), we + * have a proper upper bound for new->last in the loop below. + */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC; for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
On 5/6/26 11:22, Stefano Brivio wrote:
Instead of just being able to add to the existing tables, implement an explicit --clear option to replace them, which now becomes the default behaviour, and implement explicit --add and --delete options to maintain the table and add or delete specific ports.
The option --clear PIF forces the clearing of a table, instead.
These options can be combined arbitrarily and are handled as sequential commands, as now described in pesto(1).
If no option is given before forwarding specifiers for a matching table, the command line is interpreted as a replacement of the existing rules.
To this end:
- there's no protocol change, as pesto is anyway sending updated copies of the table
- the forwarding table functions now include a new fwd_rule_del(), which deletes existing rule only if a matching one is found
- a trivial fwd_rule_clear() is factored out from the existing conf_handler() implementation, so that it can be directly used in pesto
The entry points for parsing of port specifiers now take an additional 'del' parameter which is passed down all the way before reaching the fwd_rule_add() implementation. If a rule should be deleted, at that point, fwd_rule_del() is called instead.
Signed-off-by: Stefano Brivio
--- conf.c | 26 ++++++-------- fwd_rule.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++------ fwd_rule.h | 4 ++- pesto.1 | 85 +++++++++++++++++++++++++++++++++++++++++++++ pesto.c | 55 ++++++++++++++++++++++++++--- 5 files changed, 238 insertions(+), 32 deletions(-) diff --git a/conf.c b/conf.c index d7c837d..929a889 100644 --- a/conf.c +++ b/conf.c @@ -1849,16 +1849,16 @@ void conf(struct ctx *c, int argc, char **argv)
if (name == 't') { opt_t = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'u') { opt_u = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'T') { opt_T = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } else if (name == 'U') { opt_U = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } } while (name != -1);
@@ -1910,13 +1910,13 @@ void conf(struct ctx *c, int argc, char **argv)
if (c->mode == MODE_PASTA) { if (!opt_t) - fwd_rule_parse('t', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('t', false, "auto", c->fwd[PIF_HOST]); if (!opt_T) - fwd_rule_parse('T', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('T', false, "auto", c->fwd[PIF_SPLICE]); if (!opt_u) - fwd_rule_parse('u', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('u', false, "auto", c->fwd[PIF_HOST]); if (!opt_U) - fwd_rule_parse('U', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('U', false, "auto", c->fwd[PIF_SPLICE]); }
conf_sock_listen(c); @@ -2135,14 +2135,8 @@ void conf_handler(struct ctx *c, uint32_t events) unsigned pif;
/* Clear pending tables */ - for (pif = 0; pif < PIF_NUM_TYPES; pif++) { - struct fwd_table *fwd = c->fwd_pending[pif]; - - if (!fwd) - continue; - fwd->count = 0; - fwd->sock_count = 0; - } + for (pif = 0; pif < PIF_NUM_TYPES; pif++) + fwd_rule_clear(c->fwd_pending[pif]);
/* FIXME: this could block indefinitely if the client doesn't * write as much as it should diff --git a/fwd_rule.c b/fwd_rule.c index 200f4b5..c886ef3 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -180,6 +180,75 @@ static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule * return true; }
+/** + * fwd_rule_clear() - Clear a forwarding table + * @fwd: Table to clear (might be NULL) + */ +void fwd_rule_clear(struct fwd_table *fwd) +{ + if (!fwd) + return; + + /* TODO: check that there are no open sockets in the table before + * going on. See also a related item in fwd_rule_del(). + */ + + fwd->count = 0; + fwd->sock_count = 0; +} + +/** + * fwd_rule_del() - Partially validate and delete a rule from a forwarding table + * @fwd: Table to delete from + * @rule: Rule to delete (must conflict with an existing rule) + * + * Return: 0 on success, negative error code on failure (-ENOENT if not found) + * + * NOTE: This function can't be used for a forwarding table with any open socket + * stored in fwd->rulesocks. + */ +static int fwd_rule_del(struct fwd_table *fwd, const struct fwd_rule *rule) +{ + unsigned num, i; + + for (i = 0; i < fwd->count; i++) { + /* FIXME: This isn't entirely correct, as ideally we would like + * to match only *matching* rules here, not just conflicting + * ones. This is convenient at the moment for the current + * implementation, though. + */ + if (fwd_rule_conflicts(rule, &fwd->rules[i])) + break; + } + + if (i == fwd->count) { + char newstr[FWD_RULE_STRLEN]; + + warn("Couldn't find forwarding rule to delete: %s", + fwd_rule_fmt(rule, newstr, sizeof(newstr))); + return -ENOENT; + } + + /* Don't use anything else from 'rule' as passed, it's not validated */ + rule = &fwd->rules[i];
So we remove the entire rule that matches, not only the part of the new rule that matches? And what if a new rule conflicts with two existing rules?
+ num = (unsigned)rule->last - rule->first + 1; + + fwd->count--; + + memmove((void *)(fwd->rulesocks + i), (void *)(fwd->rulesocks + i + 1), + (fwd->count - i) * sizeof(*fwd->rulesocks)); + + /* TODO: move sockets stored starting from fwd->rulesocks[i + i], should
In comment ^ "[i + 1]"
+ * we ever need to delete rules from a table with open sockets. + */ + fwd->sock_count -= num; + + memmove(fwd->rules + i, fwd->rules + i + 1, + (fwd->count - i) * sizeof(*fwd->rules)); + + return 0; +} + /** * fwd_rule_add() - Validate and add a rule to a forwarding table * @fwd: Table to add to @@ -370,6 +439,7 @@ static int parse_keyword(const char *s, const char **endptr, const char *kw) * fwd_rule_range_except() - Set up forwarding for a range of ports minus a * bitmap of exclusions * @fwd: Forwarding table to be updated + * @del: Delete resulting rules from forwarding table, instead of adding * @proto: Protocol to forward * @addr: Listening address * @ifname: Listening interface @@ -379,8 +449,8 @@ static int parse_keyword(const char *s, const char **endptr, const char *kw) * @to: Port to translate @first to when forwarding * @flags: Flags for forwarding entries */ -static void fwd_rule_range_except(struct fwd_table *fwd, uint8_t proto, - const union inany_addr *addr, +static void fwd_rule_range_except(struct fwd_table *fwd, bool del, + uint8_t proto, const union inany_addr *addr, const char *ifname, uint16_t first, uint16_t last, const uint8_t *exclude, uint16_t to, @@ -420,8 +490,13 @@ static void fwd_rule_range_except(struct fwd_table *fwd, uint8_t proto, rule.last = i - 1; rule.to = base + delta;
- if (fwd_rule_add(fwd, &rule) < 0) - goto fail; + if (del) { + if (fwd_rule_del(fwd, &rule) < 0) + goto fail; + } else { + if (fwd_rule_add(fwd, &rule) < 0) + goto fail; + }
base = i - 1; } @@ -447,12 +522,13 @@ fail: /** * fwd_rule_parse_ports() - Parse port range(s) specifier * @fwd: Forwarding table to be updated + * @del: Delete resulting rules from forwarding table, instead of adding * @proto: Protocol to forward * @addr: Listening address for forwarding * @ifname: Interface name for listening * @spec: Port range(s) specifier */ -static void fwd_rule_parse_ports(struct fwd_table *fwd, uint8_t proto, +static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, const union inany_addr *addr, const char *ifname, const char *spec) @@ -509,7 +585,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, uint8_t proto, /* Exclude ephemeral ports */ fwd_port_map_ephemeral(exclude);
- fwd_rule_range_except(fwd, proto, addr, ifname, + fwd_rule_range_except(fwd, del, proto, addr, ifname, 1, NUM_PORTS - 1, exclude, 1, flags | FWD_WEAK); return; @@ -539,7 +615,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, uint8_t proto, if (p != ep) /* Garbage after the ranges */ goto bad;
- fwd_rule_range_except(fwd, proto, addr, ifname, + fwd_rule_range_except(fwd, del, proto, addr, ifname, orig_range.first, orig_range.last, exclude, mapped_range.first, flags); @@ -553,10 +629,12 @@ bad: /** * fwd_rule_parse() - Parse port configuration option * @optname: Short option name, t, T, u, or U + * @del: Delete resulting rules from forwarding table, instead of adding * @optarg: Option argument (port specification) * @fwd: Forwarding table to be updated */ -void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd) +void fwd_rule_parse(char optname, bool del, const char *optarg, + struct fwd_table *fwd) { union inany_addr addr_buf = inany_any6, *addr = &addr_buf; char buf[BUFSIZ], *spec, *ifname = NULL; @@ -634,12 +712,12 @@ void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd) optname, optarg);
if (fwd->caps & FWD_CAP_IPV4) { - fwd_rule_parse_ports(fwd, proto, + fwd_rule_parse_ports(fwd, del, proto, &inany_loopback4, NULL, spec); } if (fwd->caps & FWD_CAP_IPV6) { - fwd_rule_parse_ports(fwd, proto, + fwd_rule_parse_ports(fwd, del, proto, &inany_loopback6, NULL, spec); } @@ -655,7 +733,7 @@ void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd) optname, optarg); }
- fwd_rule_parse_ports(fwd, proto, addr, ifname, spec); + fwd_rule_parse_ports(fwd, del, proto, addr, ifname, spec); }
/** diff --git a/fwd_rule.h b/fwd_rule.h index f43b37d..ae9a3cb 100644 --- a/fwd_rule.h +++ b/fwd_rule.h @@ -100,9 +100,11 @@ void fwd_probe_ephemeral(void);
const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule); const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size); -void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd); +void fwd_rule_parse(char optname, bool del, const char *optarg, + struct fwd_table *fwd); int fwd_rule_read(int fd, struct fwd_rule *rule); int fwd_rule_write(int fd, const struct fwd_rule *rule); +void fwd_rule_clear(struct fwd_table *fwd); int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new);
/** diff --git a/pesto.1 b/pesto.1 index 1e1c0f3..c684dc6 100644 --- a/pesto.1 +++ b/pesto.1 @@ -35,6 +35,42 @@ Display a help message and exit. .BR \-s ", " \-\-show Show the forwarding configuration before and after changes are applied.
+.TP +.BR \-A ", " \-\-add +Add the port forwarding specifiers following this option to the current +forwarding table, rather than replacing it. + +This option can be given multiple times, as it might follow previous deletions +(see \fB--delete\fR below), and implies that all the specifiers following it, +before a further \fB--delete\fR option occurs, will be handled as additions. + +See the section \fBAdding, deleting, clearing rules\fR in the \fBNOTES\fR for +more details. + +.TP +.BR \-D ", " \-\-delete +Delete the port forwarding specifiers following this option from the current +forwarding table, rather than adding them it.
"adding them to it"?
+ +This option can be given multiple times, as it might follow previous additions +(see \fB--add\fR above), and implies that all the specifiers following it, +before a further \fB--add\fR option occurs, will be handled as deletions. + +See the section \fBAdding, deleting, clearing rules\fR in the \fBNOTES\fR for +more details. + +.TP +.BR \-C ", " \-\-clear " " \fIpif +Clear the forwarding table associated to a given \fIpif\fR, that is, a +conceptual type of interface in \fBpasst\fR(1) or \fBpasta\fR(1) representing a +specific data path and direction. + +The available \fIpif\fR names can be obtained by querying the current forwarding +configuration, which can be done by calling \fBpesto\fR(1) without options. + +See the section \fBAdding, deleting, clearing rules\fR in the \fBNOTES\fR for +more details. + .TP .BR \-t ", " \-\-tcp-ports " " \fIspec Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of: @@ -166,6 +202,55 @@ Configure UDP port forwarding from target namespace to init namespace. .BR \-\-version Show version and exit.
+.SH NOTES + +.SS Adding, deleting, clearing rules + +The options \fB--add\fR, \fB--delete\fR, and \fB--clear\fR are handled as +sequential commands to manipulate the current forwarding tables. If none of them +is given, forwarding specifiers for a given table are intended as replacement of +the corresponding table. That is: + +.nf + pesto -t 1024 -U 1025 +.fi + +will \fBreplace\fR the current TCP inbound port forwarding table with a single +rule, forwarding port 1024, and will similarly replace the UDP outbound +forwarding table with a single forwarding rule for port 1025. This usage is a +short-hand form for: + +.nf + pesto -C HOST -t 1024 -C SPLICE -U 1025 +.fi + +The options \fB--add\fR and \fB--delete\fR are used to \fBadd new specific +rules or delete existing ones\fR, instead of replacing tables. For example: + +.nf + pesto -A -t 2000 -D -t 3000 -U 5000 +.fi + +will add a forwarding rule for inbound TCP port 2000, and delete inbound TCP +port 3000 as well as outbound UDP port 5000 from the existing set of rules. + +All these options are interpreted as sequential commands and can be arbitrarily +combined. For example: + +.nf + pesto -A -t 2000 -C HOST -A -T 3000 -t 2001 -D -u 5000 +.fi + +will, in order: + +.RS +- add inbound TCP port 2000 +- clear inbound ports, reverting the addition above +- add outbound TCP port 3000 +- add inbound TCP port 2001 +- delete inbound UDP port 5000 +.RE + .SH AUTHORS
Stefano Brivio
, diff --git a/pesto.c b/pesto.c index 73fdc39..c5ffbb5 100644 --- a/pesto.c +++ b/pesto.c @@ -55,6 +55,9 @@ static void usage(const char *name, FILE *f, int status) FPRINTF(f, "Usage: %s [OPTION]... PATH\n", name); FPRINTF(f, "\n" + " -A, --add Add following specifiers to forwards\n" + " -D, --delete Delete following specifiers instead\n" + " -C, --clear PIF Clear forwarding table for given PIF\n" " -t, --tcp-ports SPEC TCP inbound port forwarding\n" " can be specified multiple times\n" " SPEC can be:\n" @@ -298,6 +301,9 @@ int main(int argc, char **argv) {"debug", no_argument, NULL, 'd' }, {"help", no_argument, NULL, 'h' }, {"version", no_argument, NULL, 1 }, + {"add", no_argument, NULL, 'A' }, + {"delete", no_argument, NULL, 'D' }, + {"clear", required_argument, NULL, 'C' }, {"tcp-ports", required_argument, NULL, 't' }, {"udp-ports", required_argument, NULL, 'u' }, {"tcp-ns", required_argument, NULL, 'T' }, @@ -305,9 +311,11 @@ int main(int argc, char **argv) {"show", no_argument, NULL, 's' }, { 0 }, }; + enum { MODE_CLEAR, MODE_ADD, MODE_DEL } mode = MODE_CLEAR; + bool inbound_cleared = false, outbound_cleared = false; struct pif_configuration *inbound, *outbound; + const char *optstring = "dhADC:t:u:T:U:s"; struct sockaddr_un a = { AF_UNIX, "" }; - const char *optstring = "dht:u:T:U:s"; struct configuration conf = { 0 }; bool update = false, show = false; struct pesto_hello hello; @@ -339,11 +347,16 @@ int main(int argc, char **argv) case -1: case 0: break; + case 'C': case 't': case 'u': case 'T': case 'U': - /* Parse these options after we've read state from passt/pasta */ + case 'A': + case 'D': + /* Parse these options after we've read state from + * passt/pasta + */ update = true; break; case 's': @@ -436,16 +449,43 @@ int main(int argc, char **argv) optind = 0; do { + struct pif_configuration *pif_to_clear; + optname = getopt_long(argc, argv, optstring, options, NULL);
switch (optname) { + case 'A': + mode = MODE_ADD; + break; + case 'D': + mode = MODE_DEL; + break; + case 'C': + if (!(pif_to_clear = pif_conf_by_name(&conf, optarg))) + die("Unsupported pif name %s", optarg); + + fwd_rule_clear(&pif_to_clear->fwd); + + if (!strcmp(optarg, "HOST")) + inbound_cleared = true; + else if (!strcmp(optarg, "SPLICE")) + outbound_cleared = true; + + break; case 't': case 'u': if (!inbound) { die("Can't use -%c, no inbound interface", optname); } - fwd_rule_parse(optname, optarg, &inbound->fwd); + + if (mode == MODE_CLEAR && !inbound_cleared) { + fwd_rule_clear(&inbound->fwd); + inbound_cleared = true; + } + + fwd_rule_parse(optname, mode == MODE_DEL, optarg, + &inbound->fwd); break; case 'T': case 'U': @@ -453,7 +493,14 @@ int main(int argc, char **argv) die("Can't use -%c, no outbound interface", optname); } - fwd_rule_parse(optname, optarg, &outbound->fwd); + + if (mode == MODE_CLEAR && !outbound_cleared) { + fwd_rule_clear(&outbound->fwd); + outbound_cleared = true; + } + + fwd_rule_parse(optname, mode == MODE_DEL, optarg, + &inbound->fwd);
Should be &outbound->fwd
break; default: continue;
On Wed, 6 May 2026 12:57:21 +0200
Laurent Vivier
On 5/6/26 11:22, Stefano Brivio wrote:
From: David Gibson
Start implementing pesto in earnest. Create a control/configuration socket in passt. Have pesto connect to it and retrieve a server greeting Perform some basic version checking.
Signed-off-by: David Gibson
[sbrivio: Avoid potential recursive calling between conf_accept() and conf_close(), reported by clang-tidy] [sbrivio: In conf(), check we're not exceeding sizeof(c->control_path) instead of sizeof(c->socket_path), and, in pesto's main(), print argv[optind] instead of argv[1] to indicate an invalid socket path, both reported by Jon Maloy] [sbrivio: In pesto's main(), drop unnecessary newline from error message, reported by Laurent] [sbrivio: Don't use SOCK_NONBLOCK on accept4(), as that only applies to the *new* file descriptor, which we don't want -- set O_NONBLOCK on the listening file descriptor using fcntl()] [sbrivio: Switch to protocol version 1, and reflect the true magic behind pesto, i.e. basil, into the magic string] But the real value is in pine nuts (110 €/kg here).
Wow, I hadn't realised. They seem to be half that price here, but still:
(I make mine with bear's garlic and almonds)
...yeah, we should definitely aim at compatibility. -- Stefano
On Wed, 6 May 2026 13:43:07 +0200
Laurent Vivier
On 5/6/26 11:22, Stefano Brivio wrote:
Instead of just being able to add to the existing tables, implement an explicit --clear option to replace them, which now becomes the default behaviour, and implement explicit --add and --delete options to maintain the table and add or delete specific ports.
The option --clear PIF forces the clearing of a table, instead.
These options can be combined arbitrarily and are handled as sequential commands, as now described in pesto(1).
If no option is given before forwarding specifiers for a matching table, the command line is interpreted as a replacement of the existing rules.
To this end:
- there's no protocol change, as pesto is anyway sending updated copies of the table
- the forwarding table functions now include a new fwd_rule_del(), which deletes existing rule only if a matching one is found
- a trivial fwd_rule_clear() is factored out from the existing conf_handler() implementation, so that it can be directly used in pesto
The entry points for parsing of port specifiers now take an additional 'del' parameter which is passed down all the way before reaching the fwd_rule_add() implementation. If a rule should be deleted, at that point, fwd_rule_del() is called instead.
Signed-off-by: Stefano Brivio
--- conf.c | 26 ++++++-------- fwd_rule.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++------ fwd_rule.h | 4 ++- pesto.1 | 85 +++++++++++++++++++++++++++++++++++++++++++++ pesto.c | 55 ++++++++++++++++++++++++++--- 5 files changed, 238 insertions(+), 32 deletions(-) diff --git a/conf.c b/conf.c index d7c837d..929a889 100644 --- a/conf.c +++ b/conf.c @@ -1849,16 +1849,16 @@ void conf(struct ctx *c, int argc, char **argv)
if (name == 't') { opt_t = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'u') { opt_u = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'T') { opt_T = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } else if (name == 'U') { opt_U = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } } while (name != -1);
@@ -1910,13 +1910,13 @@ void conf(struct ctx *c, int argc, char **argv)
if (c->mode == MODE_PASTA) { if (!opt_t) - fwd_rule_parse('t', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('t', false, "auto", c->fwd[PIF_HOST]); if (!opt_T) - fwd_rule_parse('T', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('T', false, "auto", c->fwd[PIF_SPLICE]); if (!opt_u) - fwd_rule_parse('u', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('u', false, "auto", c->fwd[PIF_HOST]); if (!opt_U) - fwd_rule_parse('U', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('U', false, "auto", c->fwd[PIF_SPLICE]); }
conf_sock_listen(c); @@ -2135,14 +2135,8 @@ void conf_handler(struct ctx *c, uint32_t events) unsigned pif;
/* Clear pending tables */ - for (pif = 0; pif < PIF_NUM_TYPES; pif++) { - struct fwd_table *fwd = c->fwd_pending[pif]; - - if (!fwd) - continue; - fwd->count = 0; - fwd->sock_count = 0; - } + for (pif = 0; pif < PIF_NUM_TYPES; pif++) + fwd_rule_clear(c->fwd_pending[pif]);
/* FIXME: this could block indefinitely if the client doesn't * write as much as it should diff --git a/fwd_rule.c b/fwd_rule.c index 200f4b5..c886ef3 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -180,6 +180,75 @@ static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule * return true; }
+/** + * fwd_rule_clear() - Clear a forwarding table + * @fwd: Table to clear (might be NULL) + */ +void fwd_rule_clear(struct fwd_table *fwd) +{ + if (!fwd) + return; + + /* TODO: check that there are no open sockets in the table before + * going on. See also a related item in fwd_rule_del(). + */ + + fwd->count = 0; + fwd->sock_count = 0; +} + +/** + * fwd_rule_del() - Partially validate and delete a rule from a forwarding table + * @fwd: Table to delete from + * @rule: Rule to delete (must conflict with an existing rule) + * + * Return: 0 on success, negative error code on failure (-ENOENT if not found) + * + * NOTE: This function can't be used for a forwarding table with any open socket + * stored in fwd->rulesocks. + */ +static int fwd_rule_del(struct fwd_table *fwd, const struct fwd_rule *rule) +{ + unsigned num, i; + + for (i = 0; i < fwd->count; i++) { + /* FIXME: This isn't entirely correct, as ideally we would like + * to match only *matching* rules here, not just conflicting + * ones. This is convenient at the moment for the current + * implementation, though. + */ + if (fwd_rule_conflicts(rule, &fwd->rules[i])) + break; + } + + if (i == fwd->count) { + char newstr[FWD_RULE_STRLEN]; + + warn("Couldn't find forwarding rule to delete: %s", + fwd_rule_fmt(rule, newstr, sizeof(newstr))); + return -ENOENT; + } + + /* Don't use anything else from 'rule' as passed, it's not validated */ + rule = &fwd->rules[i];
So we remove the entire rule that matches, not only the part of the new rule that matches?
We could / should split ranges derived from a rule, yes, but it's not implemented here. For now, for simplicity, we'll just drop the whole thing. Podman doesn't allow overlapping ranges between different containers anyway, so this isn't a practical problem anybody is going to hit in the short term. In the slightly longer term, we should definitely do better than this.
And what if a new rule conflicts with two existing rules?
David raised that concern in:
+ num = (unsigned)rule->last - rule->first + 1; + + fwd->count--; + + memmove((void *)(fwd->rulesocks + i), (void *)(fwd->rulesocks + i + 1), + (fwd->count - i) * sizeof(*fwd->rulesocks)); + + /* TODO: move sockets stored starting from fwd->rulesocks[i + i], should
In comment ^ "[i + 1]"
Oops, I'll fix that in v10.
+ * we ever need to delete rules from a table with open sockets. + */ + fwd->sock_count -= num; + + memmove(fwd->rules + i, fwd->rules + i + 1, + (fwd->count - i) * sizeof(*fwd->rules)); + + return 0; +} + /** * fwd_rule_add() - Validate and add a rule to a forwarding table * @fwd: Table to add to @@ -370,6 +439,7 @@ static int parse_keyword(const char *s, const char **endptr, const char *kw) * fwd_rule_range_except() - Set up forwarding for a range of ports minus a * bitmap of exclusions * @fwd: Forwarding table to be updated + * @del: Delete resulting rules from forwarding table, instead of adding * @proto: Protocol to forward * @addr: Listening address * @ifname: Listening interface @@ -379,8 +449,8 @@ static int parse_keyword(const char *s, const char **endptr, const char *kw) * @to: Port to translate @first to when forwarding * @flags: Flags for forwarding entries */ -static void fwd_rule_range_except(struct fwd_table *fwd, uint8_t proto, - const union inany_addr *addr, +static void fwd_rule_range_except(struct fwd_table *fwd, bool del, + uint8_t proto, const union inany_addr *addr, const char *ifname, uint16_t first, uint16_t last, const uint8_t *exclude, uint16_t to, @@ -420,8 +490,13 @@ static void fwd_rule_range_except(struct fwd_table *fwd, uint8_t proto, rule.last = i - 1; rule.to = base + delta;
- if (fwd_rule_add(fwd, &rule) < 0) - goto fail; + if (del) { + if (fwd_rule_del(fwd, &rule) < 0) + goto fail; + } else { + if (fwd_rule_add(fwd, &rule) < 0) + goto fail; + }
base = i - 1; } @@ -447,12 +522,13 @@ fail: /** * fwd_rule_parse_ports() - Parse port range(s) specifier * @fwd: Forwarding table to be updated + * @del: Delete resulting rules from forwarding table, instead of adding * @proto: Protocol to forward * @addr: Listening address for forwarding * @ifname: Interface name for listening * @spec: Port range(s) specifier */ -static void fwd_rule_parse_ports(struct fwd_table *fwd, uint8_t proto, +static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, const union inany_addr *addr, const char *ifname, const char *spec) @@ -509,7 +585,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, uint8_t proto, /* Exclude ephemeral ports */ fwd_port_map_ephemeral(exclude);
- fwd_rule_range_except(fwd, proto, addr, ifname, + fwd_rule_range_except(fwd, del, proto, addr, ifname, 1, NUM_PORTS - 1, exclude, 1, flags | FWD_WEAK); return; @@ -539,7 +615,7 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, uint8_t proto, if (p != ep) /* Garbage after the ranges */ goto bad;
- fwd_rule_range_except(fwd, proto, addr, ifname, + fwd_rule_range_except(fwd, del, proto, addr, ifname, orig_range.first, orig_range.last, exclude, mapped_range.first, flags); @@ -553,10 +629,12 @@ bad: /** * fwd_rule_parse() - Parse port configuration option * @optname: Short option name, t, T, u, or U + * @del: Delete resulting rules from forwarding table, instead of adding * @optarg: Option argument (port specification) * @fwd: Forwarding table to be updated */ -void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd) +void fwd_rule_parse(char optname, bool del, const char *optarg, + struct fwd_table *fwd) { union inany_addr addr_buf = inany_any6, *addr = &addr_buf; char buf[BUFSIZ], *spec, *ifname = NULL; @@ -634,12 +712,12 @@ void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd) optname, optarg);
if (fwd->caps & FWD_CAP_IPV4) { - fwd_rule_parse_ports(fwd, proto, + fwd_rule_parse_ports(fwd, del, proto, &inany_loopback4, NULL, spec); } if (fwd->caps & FWD_CAP_IPV6) { - fwd_rule_parse_ports(fwd, proto, + fwd_rule_parse_ports(fwd, del, proto, &inany_loopback6, NULL, spec); } @@ -655,7 +733,7 @@ void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd) optname, optarg); }
- fwd_rule_parse_ports(fwd, proto, addr, ifname, spec); + fwd_rule_parse_ports(fwd, del, proto, addr, ifname, spec); }
/** diff --git a/fwd_rule.h b/fwd_rule.h index f43b37d..ae9a3cb 100644 --- a/fwd_rule.h +++ b/fwd_rule.h @@ -100,9 +100,11 @@ void fwd_probe_ephemeral(void);
const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule); const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size); -void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd); +void fwd_rule_parse(char optname, bool del, const char *optarg, + struct fwd_table *fwd); int fwd_rule_read(int fd, struct fwd_rule *rule); int fwd_rule_write(int fd, const struct fwd_rule *rule); +void fwd_rule_clear(struct fwd_table *fwd); int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new);
/** diff --git a/pesto.1 b/pesto.1 index 1e1c0f3..c684dc6 100644 --- a/pesto.1 +++ b/pesto.1 @@ -35,6 +35,42 @@ Display a help message and exit. .BR \-s ", " \-\-show Show the forwarding configuration before and after changes are applied.
+.TP +.BR \-A ", " \-\-add +Add the port forwarding specifiers following this option to the current +forwarding table, rather than replacing it. + +This option can be given multiple times, as it might follow previous deletions +(see \fB--delete\fR below), and implies that all the specifiers following it, +before a further \fB--delete\fR option occurs, will be handled as additions. + +See the section \fBAdding, deleting, clearing rules\fR in the \fBNOTES\fR for +more details. + +.TP +.BR \-D ", " \-\-delete +Delete the port forwarding specifiers following this option from the current +forwarding table, rather than adding them it.
"adding them to it"?
Fixing in v10.
+ +This option can be given multiple times, as it might follow previous additions +(see \fB--add\fR above), and implies that all the specifiers following it, +before a further \fB--add\fR option occurs, will be handled as deletions. + +See the section \fBAdding, deleting, clearing rules\fR in the \fBNOTES\fR for +more details. + +.TP +.BR \-C ", " \-\-clear " " \fIpif +Clear the forwarding table associated to a given \fIpif\fR, that is, a +conceptual type of interface in \fBpasst\fR(1) or \fBpasta\fR(1) representing a +specific data path and direction. + +The available \fIpif\fR names can be obtained by querying the current forwarding +configuration, which can be done by calling \fBpesto\fR(1) without options. + +See the section \fBAdding, deleting, clearing rules\fR in the \fBNOTES\fR for +more details. + .TP .BR \-t ", " \-\-tcp-ports " " \fIspec Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of: @@ -166,6 +202,55 @@ Configure UDP port forwarding from target namespace to init namespace. .BR \-\-version Show version and exit.
+.SH NOTES + +.SS Adding, deleting, clearing rules + +The options \fB--add\fR, \fB--delete\fR, and \fB--clear\fR are handled as +sequential commands to manipulate the current forwarding tables. If none of them +is given, forwarding specifiers for a given table are intended as replacement of +the corresponding table. That is: + +.nf + pesto -t 1024 -U 1025 +.fi + +will \fBreplace\fR the current TCP inbound port forwarding table with a single +rule, forwarding port 1024, and will similarly replace the UDP outbound +forwarding table with a single forwarding rule for port 1025. This usage is a +short-hand form for: + +.nf + pesto -C HOST -t 1024 -C SPLICE -U 1025 +.fi + +The options \fB--add\fR and \fB--delete\fR are used to \fBadd new specific +rules or delete existing ones\fR, instead of replacing tables. For example: + +.nf + pesto -A -t 2000 -D -t 3000 -U 5000 +.fi + +will add a forwarding rule for inbound TCP port 2000, and delete inbound TCP +port 3000 as well as outbound UDP port 5000 from the existing set of rules. + +All these options are interpreted as sequential commands and can be arbitrarily +combined. For example: + +.nf + pesto -A -t 2000 -C HOST -A -T 3000 -t 2001 -D -u 5000 +.fi + +will, in order: + +.RS +- add inbound TCP port 2000 +- clear inbound ports, reverting the addition above +- add outbound TCP port 3000 +- add inbound TCP port 2001 +- delete inbound UDP port 5000 +.RE + .SH AUTHORS
Stefano Brivio
, diff --git a/pesto.c b/pesto.c index 73fdc39..c5ffbb5 100644 --- a/pesto.c +++ b/pesto.c @@ -55,6 +55,9 @@ static void usage(const char *name, FILE *f, int status) FPRINTF(f, "Usage: %s [OPTION]... PATH\n", name); FPRINTF(f, "\n" + " -A, --add Add following specifiers to forwards\n" + " -D, --delete Delete following specifiers instead\n" + " -C, --clear PIF Clear forwarding table for given PIF\n" " -t, --tcp-ports SPEC TCP inbound port forwarding\n" " can be specified multiple times\n" " SPEC can be:\n" @@ -298,6 +301,9 @@ int main(int argc, char **argv) {"debug", no_argument, NULL, 'd' }, {"help", no_argument, NULL, 'h' }, {"version", no_argument, NULL, 1 }, + {"add", no_argument, NULL, 'A' }, + {"delete", no_argument, NULL, 'D' }, + {"clear", required_argument, NULL, 'C' }, {"tcp-ports", required_argument, NULL, 't' }, {"udp-ports", required_argument, NULL, 'u' }, {"tcp-ns", required_argument, NULL, 'T' }, @@ -305,9 +311,11 @@ int main(int argc, char **argv) {"show", no_argument, NULL, 's' }, { 0 }, }; + enum { MODE_CLEAR, MODE_ADD, MODE_DEL } mode = MODE_CLEAR; + bool inbound_cleared = false, outbound_cleared = false; struct pif_configuration *inbound, *outbound; + const char *optstring = "dhADC:t:u:T:U:s"; struct sockaddr_un a = { AF_UNIX, "" }; - const char *optstring = "dht:u:T:U:s"; struct configuration conf = { 0 }; bool update = false, show = false; struct pesto_hello hello; @@ -339,11 +347,16 @@ int main(int argc, char **argv) case -1: case 0: break; + case 'C': case 't': case 'u': case 'T': case 'U': - /* Parse these options after we've read state from passt/pasta */ + case 'A': + case 'D': + /* Parse these options after we've read state from + * passt/pasta + */ update = true; break; case 's': @@ -436,16 +449,43 @@ int main(int argc, char **argv) optind = 0; do { + struct pif_configuration *pif_to_clear; + optname = getopt_long(argc, argv, optstring, options, NULL);
switch (optname) { + case 'A': + mode = MODE_ADD; + break; + case 'D': + mode = MODE_DEL; + break; + case 'C': + if (!(pif_to_clear = pif_conf_by_name(&conf, optarg))) + die("Unsupported pif name %s", optarg); + + fwd_rule_clear(&pif_to_clear->fwd); + + if (!strcmp(optarg, "HOST")) + inbound_cleared = true; + else if (!strcmp(optarg, "SPLICE")) + outbound_cleared = true; + + break; case 't': case 'u': if (!inbound) { die("Can't use -%c, no inbound interface", optname); } - fwd_rule_parse(optname, optarg, &inbound->fwd); + + if (mode == MODE_CLEAR && !inbound_cleared) { + fwd_rule_clear(&inbound->fwd); + inbound_cleared = true; + } + + fwd_rule_parse(optname, mode == MODE_DEL, optarg, + &inbound->fwd); break; case 'T': case 'U': @@ -453,7 +493,14 @@ int main(int argc, char **argv) die("Can't use -%c, no outbound interface", optname); } - fwd_rule_parse(optname, optarg, &outbound->fwd); + + if (mode == MODE_CLEAR && !outbound_cleared) { + fwd_rule_clear(&outbound->fwd); + outbound_cleared = true; + } + + fwd_rule_parse(optname, mode == MODE_DEL, optarg, + &inbound->fwd);
Should be &outbound->fwd
Ouch, nice catch, I rewrote this part after testing -T / -U and forgot to check it again. :( I'll fix this in v10 as well. -- Stefano
On 5/6/26 11:22, Stefano Brivio wrote:
It's time to ship it in packages.
Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- contrib/fedora/passt.spec | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/contrib/fedora/passt.spec b/contrib/fedora/passt.spec index 38b06b0..34838af 100644 --- a/contrib/fedora/passt.spec +++ b/contrib/fedora/passt.spec @@ -4,6 +4,9 @@ # PASTA - Pack A Subtle Tap Abstraction # for network namespace/tap device mode # +# PESTO - Programmable Extensible Socket Translation Orchestrator +# front-end for passt(1) and pasta(1) forwarding configuration +# # Copyright (c) 2022 Red Hat GmbH # Author: Stefano Brivio
@@ -51,7 +54,8 @@ Requires(post): container-selinux Requires(post): selinux-policy-%{selinuxtype}
%description selinux -This package adds SELinux enforcement to passt(1), pasta(1), passt-repair(1). +This package adds SELinux enforcement to passt(1), pasta(1), passt-repair(1), +pesto(1).
%prep %setup -q -n passt-%{git_hash} @@ -90,17 +94,18 @@ install -p -m 644 -D passt.pp %{buildroot}%{_datadir}/selinux/packages/%{selinux install -p -m 644 -D passt.if %{buildroot}%{_datadir}/selinux/devel/include/distributed/passt.if install -p -m 644 -D pasta.pp %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/pasta.pp install -p -m 644 -D passt-repair.pp %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/passt-repair.pp +install -p -m 644 -D pesto.pp %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype}/pesto.pp popd
%pre selinux %selinux_relabel_pre -s %{selinuxtype}
%post selinux -%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/passt.pp %{_datadir}/selinux/packages/%{selinuxtype}/pasta.pp %{_datadir}/selinux/packages/%{selinuxtype}/passt-repair.pp +%selinux_modules_install -s %{selinuxtype} %{_datadir}/selinux/packages/%{selinuxtype}/passt.pp %{_datadir}/selinux/packages/%{selinuxtype}/pasta.pp %{_datadir}/selinux/packages/%{selinuxtype}/passt-repair.pp %{_datadir}/selinux/packages/%{selinuxtype}/pesto.pp
%postun selinux if [ $1 -eq 0 ]; then - %selinux_modules_uninstall -s %{selinuxtype} passt pasta passt-repair + %selinux_modules_uninstall -s %{selinuxtype} passt pasta passt-repair pesto fi
%posttrans selinux @@ -115,10 +120,12 @@ fi %{_bindir}/pasta %{_bindir}/qrap %{_bindir}/passt-repair +%{_bindir}/pesto %{_mandir}/man1/passt.1* %{_mandir}/man1/pasta.1* %{_mandir}/man1/qrap.1* %{_mandir}/man1/passt-repair.1* +%{_mandir}/man1/pesto.1* %ifarch x86_64 %{_bindir}/passt.avx2 %{_mandir}/man1/passt.avx2.1* @@ -131,6 +138,7 @@ fi %{_datadir}/selinux/devel/include/distributed/passt.if %{_datadir}/selinux/packages/%{selinuxtype}/pasta.pp %{_datadir}/selinux/packages/%{selinuxtype}/passt-repair.pp +%{_datadir}/selinux/packages/%{selinuxtype}/pesto.pp
%changelog {{{ passt_git_changelog }}}
On 5/6/26 11:22, Stefano Brivio wrote:
Signed-off-by: Stefano Brivio
Reviewed-by: Laurent Vivier
--- hooks/pre-push | 1 + 1 file changed, 1 insertion(+)
diff --git a/hooks/pre-push b/hooks/pre-push index 839b310..3e2d15f 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -57,6 +57,7 @@ make pkgs scp passt passt.avx2 passt.1 qrap qrap.1 "${USER_HOST}:${BIN}" scp pasta pasta.avx2 pasta.1 "${USER_HOST}:${BIN}" scp passt-repair passt-repair.1 "${USER_HOST}:${BIN}" +scp pesto pesto.1 "${USER_HOST}:${BIN}"
ssh "${USER_HOST}" "rm -f ${BIN}/*.deb" ssh "${USER_HOST}" "rm -f ${BIN}/*.rpm"
On Wed, 6 May 2026 14:07:37 +0200
Stefano Brivio
On Wed, 6 May 2026 13:43:07 +0200 Laurent Vivier
wrote: On 5/6/26 11:22, Stefano Brivio wrote:
Instead of just being able to add to the existing tables, implement an explicit --clear option to replace them, which now becomes the default behaviour, and implement explicit --add and --delete options to maintain the table and add or delete specific ports.
The option --clear PIF forces the clearing of a table, instead.
These options can be combined arbitrarily and are handled as sequential commands, as now described in pesto(1).
If no option is given before forwarding specifiers for a matching table, the command line is interpreted as a replacement of the existing rules.
To this end:
- there's no protocol change, as pesto is anyway sending updated copies of the table
- the forwarding table functions now include a new fwd_rule_del(), which deletes existing rule only if a matching one is found
- a trivial fwd_rule_clear() is factored out from the existing conf_handler() implementation, so that it can be directly used in pesto
The entry points for parsing of port specifiers now take an additional 'del' parameter which is passed down all the way before reaching the fwd_rule_add() implementation. If a rule should be deleted, at that point, fwd_rule_del() is called instead.
Signed-off-by: Stefano Brivio
--- conf.c | 26 ++++++-------- fwd_rule.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++------ fwd_rule.h | 4 ++- pesto.1 | 85 +++++++++++++++++++++++++++++++++++++++++++++ pesto.c | 55 ++++++++++++++++++++++++++--- 5 files changed, 238 insertions(+), 32 deletions(-) diff --git a/conf.c b/conf.c index d7c837d..929a889 100644 --- a/conf.c +++ b/conf.c @@ -1849,16 +1849,16 @@ void conf(struct ctx *c, int argc, char **argv)
if (name == 't') { opt_t = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'u') { opt_u = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_HOST]); } else if (name == 'T') { opt_T = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } else if (name == 'U') { opt_U = true; - fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, false, optarg, c->fwd[PIF_SPLICE]); } } while (name != -1);
@@ -1910,13 +1910,13 @@ void conf(struct ctx *c, int argc, char **argv)
if (c->mode == MODE_PASTA) { if (!opt_t) - fwd_rule_parse('t', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('t', false, "auto", c->fwd[PIF_HOST]); if (!opt_T) - fwd_rule_parse('T', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('T', false, "auto", c->fwd[PIF_SPLICE]); if (!opt_u) - fwd_rule_parse('u', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('u', false, "auto", c->fwd[PIF_HOST]); if (!opt_U) - fwd_rule_parse('U', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('U', false, "auto", c->fwd[PIF_SPLICE]); }
conf_sock_listen(c); @@ -2135,14 +2135,8 @@ void conf_handler(struct ctx *c, uint32_t events) unsigned pif;
/* Clear pending tables */ - for (pif = 0; pif < PIF_NUM_TYPES; pif++) { - struct fwd_table *fwd = c->fwd_pending[pif]; - - if (!fwd) - continue; - fwd->count = 0; - fwd->sock_count = 0; - } + for (pif = 0; pif < PIF_NUM_TYPES; pif++) + fwd_rule_clear(c->fwd_pending[pif]);
/* FIXME: this could block indefinitely if the client doesn't * write as much as it should diff --git a/fwd_rule.c b/fwd_rule.c index 200f4b5..c886ef3 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -180,6 +180,75 @@ static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule * return true; }
+/** + * fwd_rule_clear() - Clear a forwarding table + * @fwd: Table to clear (might be NULL) + */ +void fwd_rule_clear(struct fwd_table *fwd) +{ + if (!fwd) + return; + + /* TODO: check that there are no open sockets in the table before + * going on. See also a related item in fwd_rule_del(). + */ + + fwd->count = 0; + fwd->sock_count = 0; +} + +/** + * fwd_rule_del() - Partially validate and delete a rule from a forwarding table + * @fwd: Table to delete from + * @rule: Rule to delete (must conflict with an existing rule) + * + * Return: 0 on success, negative error code on failure (-ENOENT if not found) + * + * NOTE: This function can't be used for a forwarding table with any open socket + * stored in fwd->rulesocks. + */ +static int fwd_rule_del(struct fwd_table *fwd, const struct fwd_rule *rule) +{ + unsigned num, i; + + for (i = 0; i < fwd->count; i++) { + /* FIXME: This isn't entirely correct, as ideally we would like + * to match only *matching* rules here, not just conflicting + * ones. This is convenient at the moment for the current + * implementation, though. + */ + if (fwd_rule_conflicts(rule, &fwd->rules[i])) + break; + } + + if (i == fwd->count) { + char newstr[FWD_RULE_STRLEN]; + + warn("Couldn't find forwarding rule to delete: %s", + fwd_rule_fmt(rule, newstr, sizeof(newstr))); + return -ENOENT; + } + + /* Don't use anything else from 'rule' as passed, it's not validated */ + rule = &fwd->rules[i];
So we remove the entire rule that matches, not only the part of the new rule that matches?
We could / should split ranges derived from a rule, yes, but it's not implemented here. For now, for simplicity, we'll just drop the whole thing.
Podman doesn't allow overlapping ranges between different containers anyway, so this isn't a practical problem anybody is going to hit in the short term. In the slightly longer term, we should definitely do better than this.
And what if a new rule conflicts with two existing rules?
David raised that concern in:
https://archives.passt.top/passt-dev/afsASt0TWXFQovjJ@zatzit/ ...we'll delete the first one which conflicts, at the moment. It's not great but again it's fine for Podman usage, and we can improve the implementation in a bit.
...on the other hand, I just checked things with a trivial function just doing memcmp(), to implement David's proposal about sticking to exact matches only, and it all seems to work (with or without interface names, with or without IPv4 / IPv6 mix-ups, etc.). I didn't expect it to be so simple. Let me change that in v10 as well. -- Stefano
On Wed, May 06, 2026 at 11:22:36AM +0200, Stefano Brivio wrote:
The new checks are actually sufficient but not enough for Coverity Scan. Now that fwd->sock_count and new->last are affected or supplied by clients, we need explicit (albeit redundant) checks on them.
Signed-off-by: Stefano Brivio
Thanks for the extra details. I still think this can probably be done
more neatly. but it's good enough for the time being.
Reviewed-by: David Gibson
--- fwd_rule.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/fwd_rule.c b/fwd_rule.c index b55e4df..200f4b5 100644 --- a/fwd_rule.c +++ b/fwd_rule.c @@ -271,13 +271,24 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new) warn("Too many rules (maximum %d)", ARRAY_SIZE(fwd->rules)); return -ENOSPC; } + if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) { warn("Rules require too many listening sockets (maximum %d)", ARRAY_SIZE(fwd->socks)); return -ENOSPC; } + /* Redundant (see check just above), to make static checkers happy */ + if (fwd->sock_count > ARRAY_SIZE(fwd->socks)) + return -ENOSPC;
fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count]; + + /* Redundant, but not for static checkers, that might be missing that + * due to the check on 'num' above against ARRAY_SIZE(fwd->socks), we + * have a proper upper bound for new->last in the loop below. + */ + if (new->last > ARRAY_SIZE(fwd->socks) + new->first) + return -ENOSPC; for (port = new->first; port <= new->last; port++) fwd->rulesocks[fwd->count][port - new->first] = -1;
-- 2.43.0
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Wed, May 06, 2026 at 11:22:29AM +0200, Stefano Brivio wrote:
From: David Gibson
Extend the dynamic update protocol to expose the pif indices and names from a running passt/pasta to the pesto tool. pesto records that data and prints it out.
Signed-off-by: David Gibson
Reviewed-by: Laurent Vivier [sbrivio: In read_pif_conf(), force a redundant termination of the interface name, the existing check isn't obvious enough for static checkers] [sbrivio: Drop @resv_ left-over in description of struct pesto_pif_info, reported by Jon Maloy] [sbrivio: Fix minor nits reported by Laurent] [sbrivio: Initialise struct pesto_pif_info in conf_send_rules() with zeroes, otherwise the pif name might be seen as not terminated, and we'll expose memory from the back-end]
Oops, good catch.
[sbrivio: Fix conflicts in Makefile] Signed-off-by: Stefano Brivio
--- common.h | 2 + conf.c | 41 ++++++++++++++++ pesto.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pesto.h | 18 ++++++- pif.h | 5 +- serialise.c | 4 ++ serialise.h | 1 + util.h | 2 - 8 files changed, 200 insertions(+), 7 deletions(-) diff --git a/common.h b/common.h index 4251781..68573b4 100644 --- a/common.h +++ b/common.h @@ -53,4 +53,6 @@ static inline const char *strerror_(int errnum)
#define strerror(x) @ "Don't call strerror() directly, use strerror_() instead"
+#define ARRAY_SIZE(a) ((int)(sizeof(a) / sizeof((a)[0]))) + #endif /* COMMON_H */ diff --git a/conf.c b/conf.c index 5ec0072..f8c2134 100644 --- a/conf.c +++ b/conf.c @@ -1927,6 +1927,43 @@ void conf(struct ctx *c, int argc, char **argv)
static void conf_accept(struct ctx *c);
+/** + * conf_send_rules() - Send current forwarding rules to config client (pesto) + * @c: Execution context + * @fd: Socket to the client + * + * Return: 0 on success, -1 on failure + * + * FIXME: So far only sends pif ids and names + */ +static int conf_send_rules(const struct ctx *c, int fd) +{ + unsigned pif; + + for (pif = 0; pif < PIF_NUM_TYPES; pif++) { + struct pesto_pif_info info = { 0 }; + int rc; + + if (!c->fwd[pif]) + continue; + + assert(pif != PIF_NONE); + + rc = snprintf(info.name, sizeof(info.name), "%s", pif_name(pif)); + assert(rc >= 0 && (size_t)rc < sizeof(info.name)); + + if (write_u8(fd, pif) < 0) + return -1; + if (write_all_buf(fd, &info, sizeof(info)) < 0) + return -1; + } + + if (write_u8(fd, PIF_NONE) < 0) + return -1; + + return 0; +} + /** * conf_close() - Close configuration / control socket and clean up * @c: Execution context @@ -1970,6 +2007,7 @@ static void conf_accept(struct ctx *c) struct pesto_hello hello = { .magic = PESTO_SERVER_MAGIC, .version = htonl(PESTO_PROTOCOL_VERSION), + .pif_name_size = htonl(PIF_NAME_SIZE), }; union epoll_ref ref = { .type = EPOLL_TYPE_CONF }; struct ucred uc = { 0 }; @@ -2010,6 +2048,9 @@ retry: "Warning: Using experimental unsupported configuration protocol"); }
+ if (conf_send_rules(c, fd) < 0) + goto fail; + return;
fail: diff --git a/pesto.c b/pesto.c index ab476c5..b33492a 100644 --- a/pesto.c +++ b/pesto.c @@ -60,6 +60,127 @@ static void usage(const char *name, FILE *f, int status) exit(status); }
+/* Maximum number of pifs with rule tables */ +#define MAX_PIFS 3 + +struct pif_configuration { + uint8_t pif; + char name[PIF_NAME_SIZE]; +}; + +struct configuration { + uint32_t npifs; + struct pif_configuration pif[MAX_PIFS]; +}; + +/** + * pif_conf_by_num() - Find a pif's configuration by pif id + * @conf: Configuration description + * @pif: pif id + * + * Return: pointer to the pif_configuration for @pif, or NULL if not found + */ +static struct pif_configuration *pif_conf_by_num(struct configuration *conf, + uint8_t pif) +{ + unsigned i; + + for (i = 0; i < conf->npifs; i++) { + if (conf->pif[i].pif == pif) + return &conf->pif[i]; + } + + return NULL; +} + +/** + * pif_conf_by_name() - Find a pif's configuration by name + * @conf: Configuration description + * @name: Interface name + * + * Return: pif_configuration for pif named @name, or NULL if not found + */ +static struct pif_configuration *pif_conf_by_name(struct configuration *conf, + const char *name) +{ + unsigned i; + + for (i = 0; i < conf->npifs; i++) { + if (strcmp(conf->pif[i].name, name) == 0) + return &conf->pif[i]; + } + + return NULL; +} + +/** + * pesto_read_rules() - Read rulestate from passt/pasta + * @fd: Control socket + * @conf: Configuration description to update + */ +static bool read_pif_conf(int fd, struct configuration *conf) +{ + struct pif_configuration *pc; + struct pesto_pif_info info; + uint8_t pif; + + if (read_u8(fd, &pif) < 0) + die("Error reading from control socket"); + + if (pif == PIF_NONE) + return false; + + debug("Receiving config for PIF %"PRIu8, pif); + + if (conf->npifs >= ARRAY_SIZE(conf->pif)) { + die("passt has more pifs than pesto can manage (max %d)", + ARRAY_SIZE(conf->pif)); + } + + pc = &conf->pif[conf->npifs]; + pc->pif = pif; + + if (read_all_buf(fd, &info, sizeof(info)) < 0) + die("Error reading from control socket"); + + if (info.name[sizeof(info.name)-1]) + die("Interface name was not NULL terminated"); + /* Redundant, to make static checkers happy */ + info.name[sizeof(info.name) - 1] = '\0'; + + static_assert(sizeof(info.name) == sizeof(pc->name), + "Mismatching pif name lengths"); + memcpy(pc->name, info.name, sizeof(pc->name)); + + debug("PIF %"PRIu8": %s", pc->pif, pc->name); + + /* O(n^2), but n is bounded by MAX_PIFS */ + if (pif_conf_by_num(conf, pc->pif)) + die("Received duplicate interface identifier"); + + /* O(n^2), but n is bounded by MAX_PIFS */ + if (pif_conf_by_name(conf, pc->name)) + die("Received duplicate interface name"); + + conf->npifs++; + return true; +} + +/** + * show_conf() - Show current configuration obtained from passt/pasta + * @conf: Configuration description + */ +static void show_conf(const struct configuration *conf) +{ + unsigned i; + + for (i = 0; i < conf->npifs; i++) { + const struct pif_configuration *pc = &conf->pif[i]; + printf(" %s\n", pc->name); + printf(" TBD\n"); + } +} + /** * main() - Dynamic reconfiguration client main program * @argc: Argument count @@ -80,6 +201,7 @@ int main(int argc, char **argv) { 0 }, }; struct sockaddr_un a = { AF_UNIX, "" }; + struct configuration conf = { 0 }; const char *optstring = "dh"; struct pesto_hello hello; struct sock_fprog prog; @@ -162,6 +284,18 @@ int main(int argc, char **argv) "Warning: Using experimental protocol version, client and server must match\n"); }
+ if (ntohl(hello.pif_name_size) != PIF_NAME_SIZE) { + die("Server has unexpected pif name size (%" + PRIu32" not %"PRIu32 ")", + ntohl(hello.pif_name_size), PIF_NAME_SIZE); + } + + while (read_pif_conf(s, &conf)) + ; + + printf("passt/pasta configuration (%s)\n", a.sun_path); + show_conf(&conf); + if (shutdown(s, SHUT_RDWR) < 0 || close(s) < 0) die_perror("Error shutting down control socket");
diff --git a/pesto.h b/pesto.h index 3c93d3e..fda0ef6 100644 --- a/pesto.h +++ b/pesto.h @@ -17,18 +17,32 @@ /* Version 0 is reserved for unreleased / unsupported experimental versions */ #define PESTO_PROTOCOL_VERSION 1
+/* Maximum size of a pif name, including \0 */ +#define PIF_NAME_SIZE (128) +#define PIF_NONE 0 + /** * struct pesto_hello - Server introduction message - * @magic: PESTO_SERVER_MAGIC - * @version: Version number + * @magic: PESTO_SERVER_MAGIC + * @version: Version number + * @pif_name_size: Server's value for PIF_NAME_SIZE */ struct pesto_hello { char magic[8]; uint32_t version; + uint32_t pif_name_size; } __attribute__ ((__packed__));
static_assert(sizeof(PESTO_SERVER_MAGIC) == sizeof(((struct pesto_hello *)0)->magic), "PESTO_SERVER_MAGIC has wrong size");
+/** + * struct pesto_pif_info - Message with basic metadata about a pif + * @name: Name (\0 terminated) + */ +struct pesto_pif_info { + char name[PIF_NAME_SIZE]; +} __attribute__ ((__packed__)); + #endif /* PESTO_H */ diff --git a/pif.h b/pif.h index 553c742..48d4919 100644 --- a/pif.h +++ b/pif.h @@ -11,6 +11,7 @@
#include
+#include "pesto.h" #include "epoll_type.h"
union inany_addr; @@ -24,7 +25,7 @@ union sockaddr_inany; */ enum pif_type { /* Invalid or not present pif */ - PIF_NONE = 0, + PIF_NONE_ = PIF_NONE, /* Host socket interface */ PIF_HOST, /* Qemu socket or namespace tuntap interface */ @@ -35,8 +36,6 @@ enum pif_type { PIF_NUM_TYPES, };
-/* Maximum size of a pif name, including \0 */ -#define PIF_NAME_SIZE (128) extern const char pif_type_str[][PIF_NAME_SIZE];
static inline const char *pif_type(enum pif_type pt) diff --git a/serialise.c b/serialise.c index 346df99..e083112 100644 --- a/serialise.c +++ b/serialise.c @@ -121,6 +121,10 @@ int write_all_buf(int fd, const void *buf, size_t len) return write_all_buf(fd, &beval, sizeof(beval)); \ }
+#define be8toh(x) (x) +#define htobe8(x) (x) + +SERIALISE_UINT(8) SERIALISE_UINT(32)
#undef SERIALISE_UINT diff --git a/serialise.h b/serialise.h index a88f3de..4714f4c 100644 --- a/serialise.h +++ b/serialise.h @@ -16,6 +16,7 @@ int write_all_buf(int fd, const void *buf, size_t len); int read_u##bits(int fd, uint##bits##_t *val); \ int write_u##bits(int fd, uint##bits##_t val);
+SERIALISE_UINT_DECL(8) SERIALISE_UINT_DECL(32)
#endif /* SERIALISE_H */ diff --git a/util.h b/util.h index e90be47..c788382 100644 --- a/util.h +++ b/util.h @@ -87,8 +87,6 @@ void abort_with_msg(const char *fmt, ...) #define V6 1 #define IP_VERSIONS 2
-#define ARRAY_SIZE(a) ((int)(sizeof(a) / sizeof((a)[0]))) - #define foreach(item, array) \ for ((item) = (array); (item) - (array) < ARRAY_SIZE(array); (item)++)
-- 2.43.0
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
participants (3)
-
David Gibson
-
Laurent Vivier
-
Stefano Brivio