public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH 0/5] RFC: Stub dynamic update implementation
@ 2026-03-16  5:46 David Gibson
  2026-03-16  5:46 ` [PATCH 1/5] Makefile: Use $^ to avoid duplication in static checker rules David Gibson
                   ` (5 more replies)
  0 siblings, 6 replies; 13+ messages in thread
From: David Gibson @ 2026-03-16  5:46 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

I've taken Stefano's draft implementation of dynamic updates, and
polished it up to have a stub implementation of the dynamic update
protocol.  So far it doesn't actually do anything, beyond establishing
the connection and checking versions.  I'm continuing to work on the
actual guts of it.

Patches 1..3/5 are trivial cleanups I happened across while working on
this.  Feel free to apply if you like.  4/5 clears up a more specific
problem that caused problems sharing code between client and server.
5/5 is the implementation proper.

David Gibson (5):
  Makefile: Use $^ to avoid duplication in static checker rules
  doc: Fix formatting of (DEPRECATED) notes in man page
  pif: Remove unused PIF_NAMELEN
  treewide: Spell ASSERT() as assert()
  pesto: Introduce stub configuration interface and tool

 .gitignore   |   2 +
 Makefile     |  36 +++++++-----
 conf.c       | 154 +++++++++++++++++++++++++++++++++++++++++++++++++--
 conf.h       |   2 +
 epoll_type.h |   4 ++
 flow.c       |  80 +++++++++++++-------------
 flow_table.h |   2 +-
 fwd.c        |  14 ++---
 icmp.c       |  14 ++---
 inany.h      |   4 +-
 iov.c        |   2 +-
 isolation.c  |   2 +-
 lineread.c   |   4 +-
 netlink.c    |   2 +-
 packet.c     |   4 +-
 passt.1      |   9 ++-
 passt.c      |  10 +++-
 passt.h      |   6 ++
 pesto.1      |  47 ++++++++++++++++
 pesto.c      | 111 +++++++++++++++++++++++++++++++++++++
 pesto.h      |  34 ++++++++++++
 pesto_util.c |  62 +++++++++++++++++++++
 pesto_util.h |  19 +++++++
 pif.c        |   4 +-
 pif.h        |   2 -
 tap.c        |   6 +-
 tcp.c        |  24 ++++----
 tcp_splice.c |  10 ++--
 tcp_vu.c     |   8 +--
 udp.c        |  22 ++++----
 udp_flow.c   |   4 +-
 udp_vu.c     |   4 +-
 util.c       |  42 +-------------
 util.h       |  15 ++---
 vhost_user.c |   8 +--
 virtio.c     |   4 +-
 vu_common.c  |   4 +-
 37 files changed, 599 insertions(+), 182 deletions(-)
 create mode 100644 pesto.1
 create mode 100644 pesto.c
 create mode 100644 pesto.h
 create mode 100644 pesto_util.c
 create mode 100644 pesto_util.h

-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/5] Makefile: Use $^ to avoid duplication in static checker rules
  2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
@ 2026-03-16  5:46 ` David Gibson
  2026-03-16  5:46 ` [PATCH 2/5] doc: Fix formatting of (DEPRECATED) notes in man page David Gibson
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-03-16  5:46 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

Currently we duplicate the list of sources / headers to check in the
dependency list for the clang-tidy and cppcheck rules, then again in the
command itself.  Since we already require GNU make, we can avoid this by
using the special $^ symbol which expands to the full list of dependencies.

Since clang-tidy only needs the .c files, not the headers listed, we remove
the headers from the dependency list to make this work.  Since these are
phony targets that will always be rebuilt, that shouldn't have any side
effects.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 Makefile | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index 91e037b8..fe016f30 100644
--- a/Makefile
+++ b/Makefile
@@ -172,8 +172,8 @@ docs: README.md
 		done < README.md;					\
 	) > README.plain.md
 
-clang-tidy: $(PASST_SRCS) $(HEADERS)
-	clang-tidy $(PASST_SRCS) -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
+clang-tidy: $(PASST_SRCS)
+	clang-tidy $^ -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
 	           -DCLANG_TIDY_58992
 
 cppcheck: $(PASST_SRCS) $(HEADERS)
@@ -189,4 +189,4 @@ cppcheck: $(PASST_SRCS) $(HEADERS)
 	--suppress=missingIncludeSystem \
 	--suppress=unusedStructMember					\
 	$(filter -D%,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) -D CPPCHECK_6936  \
-	$(PASST_SRCS) $(HEADERS)
+	$^
-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 2/5] doc: Fix formatting of (DEPRECATED) notes in man page
  2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
  2026-03-16  5:46 ` [PATCH 1/5] Makefile: Use $^ to avoid duplication in static checker rules David Gibson
@ 2026-03-16  5:46 ` David Gibson
  2026-03-16  5:46 ` [PATCH 3/5] pif: Remove unused PIF_NAMELEN David Gibson
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-03-16  5:46 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

Some places where we note command line options as (DEPRECATED) are missing
a space between the option name itself and the tag.  Correct them.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 passt.1 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/passt.1 b/passt.1
index e52364f3..13e8df9d 100644
--- a/passt.1
+++ b/passt.1
@@ -454,7 +454,7 @@ chosen for the hypervisor UNIX domain socket. No socket is created if not in
 \-\-vhost-user mode.
 
 .TP
-.BR \-\-migrate-exit (DEPRECATED)
+.BR \-\-migrate-exit " " (DEPRECATED)
 Exit after a completed migration as source. By default, \fBpasst\fR keeps
 running and the migrated guest can continue using its connection, or a new guest
 can connect.
@@ -465,7 +465,7 @@ legacy behaviour. If you have any use for this, refer to \fBREPORTING BUGS\fR
 below.
 
 .TP
-.BR \-\-migrate-no-linger (DEPRECATED)
+.BR \-\-migrate-no-linger " " (DEPRECATED)
 Close TCP sockets on the source instance once migration completes.
 
 By default, sockets are kept open, and events on data sockets are ignored, so
-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 3/5] pif: Remove unused PIF_NAMELEN
  2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
  2026-03-16  5:46 ` [PATCH 1/5] Makefile: Use $^ to avoid duplication in static checker rules David Gibson
  2026-03-16  5:46 ` [PATCH 2/5] doc: Fix formatting of (DEPRECATED) notes in man page David Gibson
@ 2026-03-16  5:46 ` David Gibson
  2026-03-16  5:46 ` [PATCH 4/5] treewide: Spell ASSERT() as assert() David Gibson
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-03-16  5:46 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

PIF_NAMELEN was meant to represent the maximum length of a pif name.
However, so far all names are compile-time strings, so we haven't needed a
length.  Remove it, since it's slightly misleading.  We can re-introduce
something like it if/when we actually need it.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 pif.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/pif.h b/pif.h
index e471fca3..7bb58e5c 100644
--- a/pif.h
+++ b/pif.h
@@ -35,8 +35,6 @@ enum pif_type {
 	PIF_NUM_TYPES,
 };
 
-#define PIF_NAMELEN	8
-
 extern const char *pif_type_str[];
 
 static inline const char *pif_type(enum pif_type pt)
-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 4/5] treewide: Spell ASSERT() as assert()
  2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
                   ` (2 preceding siblings ...)
  2026-03-16  5:46 ` [PATCH 3/5] pif: Remove unused PIF_NAMELEN David Gibson
@ 2026-03-16  5:46 ` David Gibson
  2026-03-17  0:02   ` Stefano Brivio
  2026-03-16  5:46 ` [PATCH 5/5] pesto: Introduce stub configuration interface and tool David Gibson
  2026-03-17  0:02 ` [PATCH 0/5] RFC: Stub dynamic update implementation Stefano Brivio
  5 siblings, 1 reply; 13+ messages in thread
From: David Gibson @ 2026-03-16  5:46 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

The standard library assert(3), at least with glibc, hits our seccomp
filter and dies with SIGSYS before it's able to print a message, making it
near useless.  Therefore, since 7a8ed9459dfe ("Make assertions actually
useful") we've instead used our own implementation, named ASSERT().

This makes our code look slightly odd though - ASSERT() has the same
overall effect as assert(), it's just a different implementation.  More
importantly this makes it awkward to share code between passt/pasta proper
and things that compile in a more typical environment.  We're going to want
that for our upcoming dynamic configuration tool.

Address this by overriding the standard library's assert() implementation
with our own, instead of giving ours its own name.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 conf.c       |  6 ++--
 flow.c       | 80 ++++++++++++++++++++++++++--------------------------
 flow_table.h |  2 +-
 fwd.c        | 14 ++++-----
 icmp.c       | 14 ++++-----
 inany.h      |  4 +--
 iov.c        |  2 +-
 isolation.c  |  2 +-
 lineread.c   |  4 +--
 netlink.c    |  2 +-
 packet.c     |  4 +--
 passt.c      |  2 +-
 pif.c        |  4 +--
 tap.c        |  6 ++--
 tcp.c        | 24 ++++++++--------
 tcp_splice.c | 10 +++----
 tcp_vu.c     |  8 +++---
 udp.c        | 22 +++++++--------
 udp_flow.c   |  4 +--
 udp_vu.c     |  4 +--
 util.c       |  6 ++--
 util.h       | 10 +++++--
 vhost_user.c |  8 +++---
 virtio.c     |  4 +--
 vu_common.c  |  4 +--
 25 files changed, 127 insertions(+), 123 deletions(-)

diff --git a/conf.c b/conf.c
index dafac463..940fb9e9 100644
--- a/conf.c
+++ b/conf.c
@@ -161,7 +161,7 @@ static void conf_ports_range_except(const struct ctx *c, char optname,
 	else if (optname == 'u' || optname == 'U')
 		proto = IPPROTO_UDP;
 	else
-		ASSERT(0);
+		assert(0);
 
 	if (addr) {
 		if (!c->ifi4 && inany_v4(addr)) {
@@ -186,7 +186,7 @@ static void conf_ports_range_except(const struct ctx *c, char optname,
 			/* FIXME: Once the fwd bitmaps are removed, move this
 			 * workaround to the caller
 			 */
-			ASSERT(!addr && ifname && !strcmp(ifname, "lo"));
+			assert(!addr && ifname && !strcmp(ifname, "lo"));
 			warn(
 "SO_BINDTODEVICE unavailable, forwarding only 127.0.0.1 and ::1 for '-%c %s'",
 			     optname, optarg);
@@ -1743,7 +1743,7 @@ void conf(struct ctx *c, int argc, char **argv)
 			die("Invalid host nameserver address: %s", optarg);
 		case 25:
 			/* Already handled in conf_mode() */
-			ASSERT(c->mode == MODE_VU);
+			assert(c->mode == MODE_VU);
 			break;
 		case 26:
 			vu_print_capabilities();
diff --git a/flow.c b/flow.c
index 735d3c5f..4282da2e 100644
--- a/flow.c
+++ b/flow.c
@@ -216,7 +216,7 @@ int flowside_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
 	const char *ifname = NULL;
 	union sockaddr_inany sa;
 
-	ASSERT(pif_is_socket(pif));
+	assert(pif_is_socket(pif));
 
 	pif_sockaddr(c, &sa, pif, &tgt->oaddr, tgt->oport);
 
@@ -244,7 +244,7 @@ int flowside_sock_l4(const struct ctx *c, enum epoll_type type, uint8_t pif,
 		/* If we add new socket pifs, they'll need to be implemented
 		 * here
 		 */
-		ASSERT(0);
+		assert(0);
 	}
 }
 
@@ -341,8 +341,8 @@ static void flow_set_state(struct flow_common *f, enum flow_state state)
 {
 	uint8_t oldstate = f->state;
 
-	ASSERT(state < FLOW_NUM_STATES);
-	ASSERT(oldstate < FLOW_NUM_STATES);
+	assert(state < FLOW_NUM_STATES);
+	assert(oldstate < FLOW_NUM_STATES);
 
 	f->state = state;
 	flow_log_(f, true, LOG_DEBUG, "%s -> %s", flow_state_str[oldstate],
@@ -369,7 +369,7 @@ int flow_epollfd(const struct flow_common *f)
  */
 void flow_epollid_set(struct flow_common *f, int epollid)
 {
-	ASSERT(epollid < EPOLLFD_ID_SIZE);
+	assert(epollid < EPOLLFD_ID_SIZE);
 
 	f->epollid = epollid;
 }
@@ -407,7 +407,7 @@ int flow_epoll_set(const struct flow_common *f, int command, uint32_t events,
  */
 void flow_epollid_register(int epollid, int epollfd)
 {
-	ASSERT(epollid < EPOLLFD_ID_SIZE);
+	assert(epollid < EPOLLFD_ID_SIZE);
 
 	epoll_id_to_fd[epollid] = epollfd;
 }
@@ -421,10 +421,10 @@ static void flow_initiate_(union flow *flow, uint8_t pif)
 {
 	struct flow_common *f = &flow->f;
 
-	ASSERT(pif != PIF_NONE);
-	ASSERT(flow_new_entry == flow && f->state == FLOW_STATE_NEW);
-	ASSERT(f->type == FLOW_TYPE_NONE);
-	ASSERT(f->pif[INISIDE] == PIF_NONE && f->pif[TGTSIDE] == PIF_NONE);
+	assert(pif != PIF_NONE);
+	assert(flow_new_entry == flow && f->state == FLOW_STATE_NEW);
+	assert(f->type == FLOW_TYPE_NONE);
+	assert(f->pif[INISIDE] == PIF_NONE && f->pif[TGTSIDE] == PIF_NONE);
 
 	f->pif[INISIDE] = pif;
 	flow_set_state(f, FLOW_STATE_INI);
@@ -474,7 +474,7 @@ struct flowside *flow_initiate_sa(union flow *flow, uint8_t pif,
 	if (inany_from_sockaddr(&ini->eaddr, &ini->eport, ssa) < 0) {
 		char str[SOCKADDR_STRLEN];
 
-		ASSERT_WITH_MSG(0, "Bad socket address %s",
+		assert_with_msg(0, "Bad socket address %s",
 				sockaddr_ntop(ssa, str, sizeof(str)));
 	}
 	if (daddr)
@@ -508,10 +508,10 @@ struct flowside *flow_target(const struct ctx *c, union flow *flow,
 	const struct fwd_table *fwd;
 	uint8_t tgtpif = PIF_NONE;
 
-	ASSERT(flow_new_entry == flow && f->state == FLOW_STATE_INI);
-	ASSERT(f->type == FLOW_TYPE_NONE);
-	ASSERT(f->pif[INISIDE] != PIF_NONE && f->pif[TGTSIDE] == PIF_NONE);
-	ASSERT(flow->f.state == FLOW_STATE_INI);
+	assert(flow_new_entry == flow && f->state == FLOW_STATE_INI);
+	assert(f->type == FLOW_TYPE_NONE);
+	assert(f->pif[INISIDE] != PIF_NONE && f->pif[TGTSIDE] == PIF_NONE);
+	assert(flow->f.state == FLOW_STATE_INI);
 
 	switch (f->pif[INISIDE]) {
 	case PIF_TAP:
@@ -574,10 +574,10 @@ union flow *flow_set_type(union flow *flow, enum flow_type type)
 {
 	struct flow_common *f = &flow->f;
 
-	ASSERT(type != FLOW_TYPE_NONE);
-	ASSERT(flow_new_entry == flow && f->state == FLOW_STATE_TGT);
-	ASSERT(f->type == FLOW_TYPE_NONE);
-	ASSERT(f->pif[INISIDE] != PIF_NONE && f->pif[TGTSIDE] != PIF_NONE);
+	assert(type != FLOW_TYPE_NONE);
+	assert(flow_new_entry == flow && f->state == FLOW_STATE_TGT);
+	assert(f->type == FLOW_TYPE_NONE);
+	assert(f->pif[INISIDE] != PIF_NONE && f->pif[TGTSIDE] != PIF_NONE);
 
 	f->type = type;
 	flow_set_state(f, FLOW_STATE_TYPED);
@@ -590,8 +590,8 @@ union flow *flow_set_type(union flow *flow, enum flow_type type)
  */
 void flow_activate(struct flow_common *f)
 {
-	ASSERT(&flow_new_entry->f == f && f->state == FLOW_STATE_TYPED);
-	ASSERT(f->pif[INISIDE] != PIF_NONE && f->pif[TGTSIDE] != PIF_NONE);
+	assert(&flow_new_entry->f == f && f->state == FLOW_STATE_TYPED);
+	assert(f->pif[INISIDE] != PIF_NONE && f->pif[TGTSIDE] != PIF_NONE);
 
 	flow_set_state(f, FLOW_STATE_ACTIVE);
 	flow_new_entry = NULL;
@@ -606,26 +606,26 @@ union flow *flow_alloc(void)
 {
 	union flow *flow = &flowtab[flow_first_free];
 
-	ASSERT(!flow_new_entry);
+	assert(!flow_new_entry);
 
 	if (flow_first_free >= FLOW_MAX)
 		return NULL;
 
-	ASSERT(flow->f.state == FLOW_STATE_FREE);
-	ASSERT(flow->f.type == FLOW_TYPE_NONE);
-	ASSERT(flow->free.n >= 1);
-	ASSERT(flow_first_free + flow->free.n <= FLOW_MAX);
+	assert(flow->f.state == FLOW_STATE_FREE);
+	assert(flow->f.type == FLOW_TYPE_NONE);
+	assert(flow->free.n >= 1);
+	assert(flow_first_free + flow->free.n <= FLOW_MAX);
 
 	if (flow->free.n > 1) {
 		union flow *next;
 
 		/* Use one entry from the cluster */
-		ASSERT(flow_first_free <= FLOW_MAX - 2);
+		assert(flow_first_free <= FLOW_MAX - 2);
 		next = &flowtab[++flow_first_free];
 
-		ASSERT(FLOW_IDX(next) < FLOW_MAX);
-		ASSERT(next->f.type == FLOW_TYPE_NONE);
-		ASSERT(next->free.n == 0);
+		assert(FLOW_IDX(next) < FLOW_MAX);
+		assert(next->f.type == FLOW_TYPE_NONE);
+		assert(next->free.n == 0);
 
 		next->free.n = flow->free.n - 1;
 		next->free.next = flow->free.next;
@@ -649,12 +649,12 @@ union flow *flow_alloc(void)
  */
 void flow_alloc_cancel(union flow *flow)
 {
-	ASSERT(flow_new_entry == flow);
-	ASSERT(flow->f.state == FLOW_STATE_NEW ||
+	assert(flow_new_entry == flow);
+	assert(flow->f.state == FLOW_STATE_NEW ||
 	       flow->f.state == FLOW_STATE_INI ||
 	       flow->f.state == FLOW_STATE_TGT ||
 	       flow->f.state == FLOW_STATE_TYPED);
-	ASSERT(flow_first_free > FLOW_IDX(flow));
+	assert(flow_first_free > FLOW_IDX(flow));
 
 	flow_set_state(&flow->f, FLOW_STATE_FREE);
 	memset(flow, 0, sizeof(*flow));
@@ -704,7 +704,7 @@ static uint64_t flow_sidx_hash(const struct ctx *c, flow_sidx_t sidx)
 	const struct flowside *side = &f->side[sidx.sidei];
 	uint8_t pif = f->pif[sidx.sidei];
 
-	ASSERT(pif != PIF_NONE);
+	assert(pif != PIF_NONE);
 	return flow_hash(c, FLOW_PROTO(f), pif, side);
 }
 
@@ -897,7 +897,7 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 		flow_timer_run = *now;
 	}
 
-	ASSERT(!flow_new_entry); /* Incomplete flow at end of cycle */
+	assert(!flow_new_entry); /* Incomplete flow at end of cycle */
 
 	/* Check which flows we might need to close first, but don't free them
 	 * yet as it's not safe to do that in the middle of flow_foreach().
@@ -907,7 +907,7 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 
 		switch (flow->f.type) {
 		case FLOW_TYPE_NONE:
-			ASSERT(false);
+			assert(false);
 			break;
 		case FLOW_TCP:
 			closed = tcp_flow_defer(&flow->tcp);
@@ -942,7 +942,7 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 			unsigned skip = flow->free.n;
 
 			/* First entry of a free cluster must have n >= 1 */
-			ASSERT(skip);
+			assert(skip);
 
 			if (free_head) {
 				/* Merge into preceding free cluster */
@@ -965,7 +965,7 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 		case FLOW_STATE_TGT:
 		case FLOW_STATE_TYPED:
 			/* Incomplete flow at end of cycle */
-			ASSERT(false);
+			assert(false);
 			break;
 
 		case FLOW_STATE_ACTIVE:
@@ -975,7 +975,7 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 
 				if (free_head) {
 					/* Add slot to current free cluster */
-					ASSERT(FLOW_IDX(flow) ==
+					assert(FLOW_IDX(flow) ==
 					    FLOW_IDX(free_head) + free_head->n);
 					free_head->n++;
 					flow->free.n = flow->free.next = 0;
@@ -992,7 +992,7 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 			break;
 
 		default:
-			ASSERT(false);
+			assert(false);
 		}
 	}
 
diff --git a/flow_table.h b/flow_table.h
index 8fb7b5c3..7694e726 100644
--- a/flow_table.h
+++ b/flow_table.h
@@ -176,7 +176,7 @@ static inline flow_sidx_t flow_sidx(const struct flow_common *f,
 				    unsigned sidei)
 {
 	/* cppcheck-suppress [knownConditionTrueFalse, unmatchedSuppression] */
-	ASSERT(sidei == !!sidei);
+	assert(sidei == !!sidei);
 
 	return (flow_sidx_t){
 		.sidei = sidei,
diff --git a/fwd.c b/fwd.c
index bedbf98a..f3b4bf2a 100644
--- a/fwd.c
+++ b/fwd.c
@@ -352,7 +352,7 @@ void fwd_rule_add(struct fwd_table *fwd, uint8_t proto, uint8_t flags,
 	struct fwd_rule *new;
 	unsigned i, port;
 
-	ASSERT(!(flags & ~allowed_flags));
+	assert(!(flags & ~allowed_flags));
 
 	if (fwd->count >= ARRAY_SIZE(fwd->rules))
 		die("Too many port forwarding ranges");
@@ -402,7 +402,7 @@ void fwd_rule_add(struct fwd_table *fwd, uint8_t proto, uint8_t flags,
 			die("Invalid interface name: %s", ifname);
 	}
 
-	ASSERT(first <= last);
+	assert(first <= last);
 	new->first = first;
 	new->last = last;
 
@@ -450,7 +450,7 @@ const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd,
 		char ostr[INANY_ADDRSTRLEN], rstr[INANY_ADDRSTRLEN];
 		const struct fwd_rule *rule = &fwd->rules[hint];
 
-		ASSERT((unsigned)hint < fwd->count);
+		assert((unsigned)hint < fwd->count);
 		if (fwd_rule_match(rule, ini, proto))
 			return rule;
 
@@ -521,14 +521,14 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd,
 	bool bound_one = false;
 	unsigned port, idx;
 
-	ASSERT(pif_is_socket(pif));
+	assert(pif_is_socket(pif));
 
 	if (!*ifname)
 		ifname = NULL;
 
 	idx = rule - fwd->rules;
-	ASSERT(idx < MAX_FWD_RULES);
-	ASSERT(!(rule->flags & FWD_SCAN && !scanmap));
+	assert(idx < MAX_FWD_RULES);
+	assert(!(rule->flags & FWD_SCAN && !scanmap));
 	
 	for (port = rule->first; port <= rule->last; port++) {
 		int fd = rule->socks[port - rule->first];
@@ -554,7 +554,7 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd,
 		else if (rule->proto == IPPROTO_UDP)
 			fd = udp_listen(c, pif, idx, addr, ifname, port);
 		else
-			ASSERT(0);
+			assert(0);
 
 		if (fd < 0) {
 			char astr[INANY_ADDRSTRLEN];
diff --git a/icmp.c b/icmp.c
index 0b0f593c..18b6106a 100644
--- a/icmp.c
+++ b/icmp.c
@@ -58,7 +58,7 @@ static struct icmp_ping_flow *ping_at_sidx(flow_sidx_t sidx)
 	if (!flow)
 		return NULL;
 
-	ASSERT(flow->f.type == FLOW_PING4 || flow->f.type == FLOW_PING6);
+	assert(flow->f.type == FLOW_PING4 || flow->f.type == FLOW_PING6);
 	return &flow->ping;
 }
 
@@ -80,7 +80,7 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref)
 	if (c->no_icmp)
 		return;
 
-	ASSERT(pingf);
+	assert(pingf);
 
 	n = recvfrom(ref.fd, buf, sizeof(buf), 0, &sr.sa, &sl);
 	if (n < 0) {
@@ -109,7 +109,7 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref)
 		ih6->icmp6_identifier = htons(ini->eport);
 		seq = ntohs(ih6->icmp6_sequence);
 	} else {
-		ASSERT(0);
+		assert(0);
 	}
 
 	/* In PASTA mode, we'll get any reply we send, discard them. */
@@ -131,7 +131,7 @@ void icmp_sock_handler(const struct ctx *c, union epoll_ref ref)
 		const struct in_addr *saddr = inany_v4(&ini->oaddr);
 		const struct in_addr *daddr = inany_v4(&ini->eaddr);
 
-		ASSERT(saddr && daddr); /* Must have IPv4 addresses */
+		assert(saddr && daddr); /* Must have IPv4 addresses */
 		tap_icmp4_send(c, *saddr, *daddr, buf, pingf->f.tap_omac, n);
 	} else if (pingf->f.type == FLOW_PING6) {
 		const struct in6_addr *saddr = &ini->oaddr.a6;
@@ -256,7 +256,7 @@ int icmp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
 	int cnt;
 
 	(void)saddr;
-	ASSERT(pif == PIF_TAP);
+	assert(pif == PIF_TAP);
 
 	if (af == AF_INET) {
 		struct icmphdr ih_storage;
@@ -287,7 +287,7 @@ int icmp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
 		id = ntohs(ih->icmp6_identifier);
 		seq = ntohs(ih->icmp6_sequence);
 	} else {
-		ASSERT(0);
+		assert(0);
 	}
 
 	cnt = iov_tail_clone(&iov[0], MAX_IOV_ICMP, data);
@@ -304,7 +304,7 @@ int icmp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
 
 	tgt = &pingf->f.side[TGTSIDE];
 
-	ASSERT(flow_proto[pingf->f.type] == proto);
+	assert(flow_proto[pingf->f.type] == proto);
 	pingf->ts = now->tv_sec;
 
 	pif_sockaddr(c, &sa, PIF_HOST, &tgt->eaddr, 0);
diff --git a/inany.h b/inany.h
index 9891ed6b..30e24164 100644
--- a/inany.h
+++ b/inany.h
@@ -86,7 +86,7 @@ static inline socklen_t socklen_inany(const union sockaddr_inany *sa)
 	case AF_INET6:
 		return sizeof(sa->sa6);
 	default:
-		ASSERT(0);
+		assert(0);
 	}
 }
 
@@ -268,7 +268,7 @@ static inline void inany_from_af(union inany_addr *aa,
 		aa->v4mapped.a4 = *((struct in_addr *)addr);
 	} else {
 		/* Not valid to call with other address families */
-		ASSERT(0);
+		assert(0);
 	}
 }
 
diff --git a/iov.c b/iov.c
index 31a3f5bc..b710daff 100644
--- a/iov.c
+++ b/iov.c
@@ -118,7 +118,7 @@ size_t iov_to_buf(const struct iovec *iov, size_t iov_cnt,
 	for (copied = 0; copied < bytes && i < iov_cnt; i++) {
 		size_t len = MIN(iov[i].iov_len - offset, bytes - copied);
 
-		ASSERT(iov[i].iov_base);
+		assert(iov[i].iov_base);
 
 		memcpy((char *)buf + copied, (char *)iov[i].iov_base + offset,
 		       len);
diff --git a/isolation.c b/isolation.c
index b25f3498..7e6225df 100644
--- a/isolation.c
+++ b/isolation.c
@@ -396,7 +396,7 @@ void isolate_postfork(const struct ctx *c)
 		prog.filter = filter_vu;
 		break;
 	default:
-		ASSERT(0);
+		assert(0);
 	}
 
 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
diff --git a/lineread.c b/lineread.c
index 4225de61..b19f5ebf 100644
--- a/lineread.c
+++ b/lineread.c
@@ -44,8 +44,8 @@ static ssize_t peek_line(struct lineread *lr, bool eof)
 	char *nl;
 
 	/* Sanity checks (which also document invariants) */
-	ASSERT(lr->next_line + lr->count >= lr->next_line);
-	ASSERT(lr->next_line + lr->count <= LINEREAD_BUFFER_SIZE);
+	assert(lr->next_line + lr->count >= lr->next_line);
+	assert(lr->next_line + lr->count <= LINEREAD_BUFFER_SIZE);
 
 	nl = memchr(lr->buf + lr->next_line, '\n', lr->count);
 
diff --git a/netlink.c b/netlink.c
index e07b47f4..6b74e882 100644
--- a/netlink.c
+++ b/netlink.c
@@ -160,7 +160,7 @@ static uint32_t nl_send(int s, void *req, uint16_t type,
  */
 static int nl_status(const struct nlmsghdr *nh, ssize_t n, uint32_t seq)
 {
-	ASSERT(NLMSG_OK(nh, n));
+	assert(NLMSG_OK(nh, n));
 
 	if (nh->nlmsg_seq != seq)
 		die("netlink: Unexpected sequence number (%u != %u)",
diff --git a/packet.c b/packet.c
index 890561bb..1cb74b74 100644
--- a/packet.c
+++ b/packet.c
@@ -168,7 +168,7 @@ bool packet_get_do(const struct pool *p, size_t idx,
 {
 	size_t i;
 
-	ASSERT_WITH_MSG(p->count <= p->size,
+	assert_with_msg(p->count <= p->size,
 			"Corrupted pool count: %zu, size: %zu, %s:%i",
 			p->count, p->size, func, line);
 
@@ -188,7 +188,7 @@ bool packet_get_do(const struct pool *p, size_t idx,
 	data->off = 0;
 
 	for (i = 0; i < data->cnt; i++) {
-		ASSERT_WITH_MSG(!packet_check_range(p, data->iov[i].iov_base,
+		assert_with_msg(!packet_check_range(p, data->iov[i].iov_base,
 						    data->iov[i].iov_len,
 						    func, line),
 				"Corrupt packet pool, %s:%i", func, line);
diff --git a/passt.c b/passt.c
index fc3b89b8..f84419c7 100644
--- a/passt.c
+++ b/passt.c
@@ -305,7 +305,7 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events)
 			break;
 		default:
 			/* Can't happen */
-			ASSERT(0);
+			assert(0);
 		}
 		stats.events[ref.type]++;
 		print_stats(c, &stats, &now);
diff --git a/pif.c b/pif.c
index 82a3b5e4..1e807247 100644
--- a/pif.c
+++ b/pif.c
@@ -39,7 +39,7 @@ void pif_sockaddr(const struct ctx *c, union sockaddr_inany *sa,
 {
 	const struct in_addr *v4 = inany_v4(addr);
 
-	ASSERT(pif_is_socket(pif));
+	assert(pif_is_socket(pif));
 
 	if (v4) {
 		sa->sa_family = AF_INET;
@@ -83,7 +83,7 @@ int pif_listen(const struct ctx *c, enum epoll_type type, uint8_t pif,
 	union epoll_ref ref;
 	int ret;
 
-	ASSERT(pif_is_socket(pif));
+	assert(pif_is_socket(pif));
 
 	if (!addr) {
 		ref.fd = sock_l4_dualstack_any(c, type, port, ifname);
diff --git a/tap.c b/tap.c
index eaa6111a..1049e023 100644
--- a/tap.c
+++ b/tap.c
@@ -117,7 +117,7 @@ unsigned long tap_l2_max_len(const struct ctx *c)
 		return L2_MAX_LEN_VU;
 	}
 	/* NOLINTEND(bugprone-branch-clone) */
-	ASSERT(0);
+	assert(0);
 
 	return 0; /* Unreachable, for cppcheck's sake */
 }
@@ -543,7 +543,7 @@ size_t tap_send_frames(const struct ctx *c, const struct iovec *iov,
 	case MODE_VU:
 		/* fall through */
 	default:
-		ASSERT(0);
+		assert(0);
 	}
 
 	if (m < nframes)
@@ -1536,7 +1536,7 @@ void tap_backend_init(struct ctx *c)
 	}
 
 	if (c->fd_tap != -1) { /* Passed as --fd */
-		ASSERT(c->one_off);
+		assert(c->one_off);
 		tap_start_connection(c);
 		return;
 	}
diff --git a/tcp.c b/tcp.c
index 9d91c3c8..b1458624 100644
--- a/tcp.c
+++ b/tcp.c
@@ -461,7 +461,7 @@ static struct tcp_tap_conn *conn_at_sidx(flow_sidx_t sidx)
 	if (!flow)
 		return NULL;
 
-	ASSERT(flow->f.type == FLOW_TCP);
+	assert(flow->f.type == FLOW_TCP);
 	return &flow->tcp;
 }
 
@@ -966,7 +966,7 @@ size_t tcp_fill_headers(const struct ctx *c, struct tcp_tap_conn *conn,
 		const struct in_addr *src4 = inany_v4(&tapside->oaddr);
 		const struct in_addr *dst4 = inany_v4(&tapside->eaddr);
 
-		ASSERT(src4 && dst4);
+		assert(src4 && dst4);
 
 		l3len += + sizeof(*ip4h);
 
@@ -1879,7 +1879,7 @@ static int tcp_data_from_tap(const struct ctx *c, struct tcp_tap_conn *conn,
 	if (conn->events == CLOSED)
 		return p->count - idx;
 
-	ASSERT(conn->events & ESTABLISHED);
+	assert(conn->events & ESTABLISHED);
 
 	for (i = idx, iov_i = 0; i < (int)p->count; i++) {
 		uint32_t seq, seq_offset, ack_seq;
@@ -2260,8 +2260,8 @@ int tcp_tap_handler(const struct ctx *c, uint8_t pif, sa_family_t af,
 		return 1;
 	}
 
-	ASSERT(flow->f.type == FLOW_TCP);
-	ASSERT(pif_at_sidx(sidx) == PIF_TAP);
+	assert(flow->f.type == FLOW_TCP);
+	assert(pif_at_sidx(sidx) == PIF_TAP);
 	conn = &flow->tcp;
 
 	flow_trace(conn, "packet length %zu from tap", l4len);
@@ -2500,7 +2500,7 @@ void tcp_listen_handler(const struct ctx *c, union epoll_ref ref,
 	union flow *flow;
 	int s;
 
-	ASSERT(!c->no_tcp);
+	assert(!c->no_tcp);
 
 	if (!(flow = flow_alloc()))
 		return;
@@ -2570,8 +2570,8 @@ void tcp_timer_handler(const struct ctx *c, union epoll_ref ref)
 	struct itimerspec check_armed = { { 0 }, { 0 } };
 	struct tcp_tap_conn *conn = &FLOW(ref.flow)->tcp;
 
-	ASSERT(!c->no_tcp);
-	ASSERT(conn->f.type == FLOW_TCP);
+	assert(!c->no_tcp);
+	assert(conn->f.type == FLOW_TCP);
 
 	/* We don't reset timers on ~ACK_FROM_TAP_DUE, ~ACK_TO_TAP_DUE. If the
 	 * timer is currently armed, this event came from a previous setting,
@@ -2632,8 +2632,8 @@ void tcp_sock_handler(const struct ctx *c, union epoll_ref ref,
 {
 	struct tcp_tap_conn *conn = conn_at_sidx(ref.flowside);
 
-	ASSERT(!c->no_tcp);
-	ASSERT(pif_at_sidx(ref.flowside) != PIF_TAP);
+	assert(!c->no_tcp);
+	assert(pif_at_sidx(ref.flowside) != PIF_TAP);
 
 	if (conn->events == CLOSED)
 		return;
@@ -2701,7 +2701,7 @@ int tcp_listen(const struct ctx *c, uint8_t pif, unsigned rule,
 {
 	int s;
 
-	ASSERT(!c->no_tcp);
+	assert(!c->no_tcp);
 
 	if (!c->ifi4) {
 		if (!addr)
@@ -2853,7 +2853,7 @@ static void tcp_get_rto_params(struct ctx *c)
  */
 int tcp_init(struct ctx *c)
 {
-	ASSERT(!c->no_tcp);
+	assert(!c->no_tcp);
 
 	tcp_get_rto_params(c);
 
diff --git a/tcp_splice.c b/tcp_splice.c
index d60981ca..42ee8abc 100644
--- a/tcp_splice.c
+++ b/tcp_splice.c
@@ -105,7 +105,7 @@ static struct tcp_splice_conn *conn_at_sidx(flow_sidx_t sidx)
 	if (!flow)
 		return NULL;
 
-	ASSERT(flow->f.type == FLOW_TCP_SPLICE);
+	assert(flow->f.type == FLOW_TCP_SPLICE);
 	return &flow->tcp_splice;
 }
 
@@ -369,7 +369,7 @@ static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn)
 	else if (tgtpif == PIF_SPLICE)
 		conn->s[1] = tcp_conn_sock_ns(c, af);
 	else
-		ASSERT(0);
+		assert(0);
 
 	if (conn->s[1] < 0)
 		return -1;
@@ -457,7 +457,7 @@ void tcp_splice_conn_from_sock(const struct ctx *c, union flow *flow, int s0)
 	struct tcp_splice_conn *conn = FLOW_SET_TYPE(flow, FLOW_TCP_SPLICE,
 						     tcp_splice);
 
-	ASSERT(c->mode == MODE_PASTA);
+	assert(c->mode == MODE_PASTA);
 
 	conn->s[0] = s0;
 	conn->s[1] = -1;
@@ -489,7 +489,7 @@ void tcp_splice_sock_handler(struct ctx *c, union epoll_ref ref,
 	uint8_t lowat_set_flag, lowat_act_flag;
 	int eof, never_read;
 
-	ASSERT(conn->f.type == FLOW_TCP_SPLICE);
+	assert(conn->f.type == FLOW_TCP_SPLICE);
 
 	if (conn->events == SPLICE_CLOSED)
 		return;
@@ -779,7 +779,7 @@ void tcp_splice_timer(struct tcp_splice_conn *conn)
 {
 	unsigned sidei;
 
-	ASSERT(!(conn->flags & CLOSING));
+	assert(!(conn->flags & CLOSING));
 
 	flow_foreach_sidei(sidei) {
 		if ((conn->flags & RCVLOWAT_SET(sidei)) &&
diff --git a/tcp_vu.c b/tcp_vu.c
index fd734e85..826a38d1 100644
--- a/tcp_vu.c
+++ b/tcp_vu.c
@@ -94,7 +94,7 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
 	if (elem_cnt != 1)
 		return -1;
 
-	ASSERT(flags_elem[0].in_sg[0].iov_len >=
+	assert(flags_elem[0].in_sg[0].iov_len >=
 	       MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN));
 
 	vu_set_vnethdr(flags_elem[0].in_sg[0].iov_base, 1);
@@ -221,7 +221,7 @@ static ssize_t tcp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq,
 
 		/* reserve space for headers in iov */
 		iov = &elem[elem_cnt].in_sg[0];
-		ASSERT(iov->iov_len >= hdrlen);
+		assert(iov->iov_len >= hdrlen);
 		iov->iov_base = (char *)iov->iov_base + hdrlen;
 		iov->iov_len -= hdrlen;
 		head[(*head_cnt)++] = elem_cnt;
@@ -298,7 +298,7 @@ static void tcp_vu_prepare(const struct ctx *c, struct tcp_tap_conn *conn,
 	/* we guess the first iovec provided by the guest can embed
 	 * all the headers needed by L2 frame, including any padding
 	 */
-	ASSERT(iov[0].iov_len >= hdrlen);
+	assert(iov[0].iov_len >= hdrlen);
 
 	eh = vu_eth(base);
 
@@ -445,7 +445,7 @@ int tcp_vu_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn)
 		ssize_t dlen;
 		size_t l2len;
 
-		ASSERT(frame_size >= hdrlen);
+		assert(frame_size >= hdrlen);
 
 		dlen = frame_size - hdrlen;
 		vu_set_vnethdr(iov->iov_base, buf_cnt);
diff --git a/udp.c b/udp.c
index 2275c16b..1fc5a42c 100644
--- a/udp.c
+++ b/udp.c
@@ -271,7 +271,7 @@ size_t udp_update_hdr4(struct iphdr *ip4h, struct udp_payload_t *bp,
 	size_t l4len = dlen + sizeof(bp->uh);
 	size_t l3len = l4len + sizeof(*ip4h);
 
-	ASSERT(src && dst);
+	assert(src && dst);
 
 	ip4h->tot_len = htons(l3len);
 	ip4h->daddr = dst->s_addr;
@@ -431,7 +431,7 @@ static void udp_send_tap_icmp4(const struct ctx *c,
 	size_t msglen = sizeof(msg) - sizeof(msg.data) + dlen;
 	size_t l4len = dlen + sizeof(struct udphdr);
 
-	ASSERT(dlen <= ICMP4_MAX_DLEN);
+	assert(dlen <= ICMP4_MAX_DLEN);
 	memset(&msg, 0, sizeof(msg));
 	msg.icmp4h.type = ee->ee_type;
 	msg.icmp4h.code = ee->ee_code;
@@ -480,7 +480,7 @@ static void udp_send_tap_icmp6(const struct ctx *c,
 	size_t msglen = sizeof(msg) - sizeof(msg.data) + dlen;
 	size_t l4len = dlen + sizeof(struct udphdr);
 
-	ASSERT(dlen <= ICMP6_MAX_DLEN);
+	assert(dlen <= ICMP6_MAX_DLEN);
 	memset(&msg, 0, sizeof(msg));
 	msg.icmp6h.icmp6_type = ee->ee_type;
 	msg.icmp6h.icmp6_code = ee->ee_code;
@@ -628,7 +628,7 @@ static int udp_sock_recverr(const struct ctx *c, int s, flow_sidx_t sidx,
 	}
 
 	uflow = udp_at_sidx(sidx);
-	ASSERT(uflow);
+	assert(uflow);
 	fromside = &uflow->f.side[sidx.sidei];
 	toside = &uflow->f.side[!sidx.sidei];
 	topif = uflow->f.pif[!sidx.sidei];
@@ -692,7 +692,7 @@ static int udp_sock_errs(const struct ctx *c, int s, flow_sidx_t sidx,
 	socklen_t errlen;
 	int rc, err;
 
-	ASSERT(!c->no_udp);
+	assert(!c->no_udp);
 
 	/* Empty the error queue */
 	while ((rc = udp_sock_recverr(c, s, sidx, pif, port)) > 0)
@@ -772,7 +772,7 @@ static int udp_peek_addr(int s, union sockaddr_inany *src,
  */
 static int udp_sock_recv(const struct ctx *c, int s, struct mmsghdr *mmh, int n)
 {
-	ASSERT(!c->no_udp);
+	assert(!c->no_udp);
 
 	n = recvmmsg(s, mmh, n, 0, NULL);
 	if (n < 0) {
@@ -940,7 +940,7 @@ void udp_sock_handler(const struct ctx *c, union epoll_ref ref,
 {
 	struct udp_flow *uflow = udp_at_sidx(ref.flowside);
 
-	ASSERT(!c->no_udp && uflow);
+	assert(!c->no_udp && uflow);
 
 	if (events & EPOLLERR) {
 		if (udp_sock_errs(c, ref.fd, ref.flowside, PIF_NONE, 0) < 0) {
@@ -1023,7 +1023,7 @@ int udp_tap_handler(const struct ctx *c, uint8_t pif,
 	in_port_t src, dst;
 	uint8_t topif;
 
-	ASSERT(!c->no_udp);
+	assert(!c->no_udp);
 
 	if (!packet_get(p, idx, &data))
 		return 1;
@@ -1061,7 +1061,7 @@ int udp_tap_handler(const struct ctx *c, uint8_t pif,
 	toside = flowside_at_sidx(tosidx);
 
 	s = uflow->s[tosidx.sidei];
-	ASSERT(s >= 0);
+	assert(s >= 0);
 
 	pif_sockaddr(c, &to_sa, topif, &toside->eaddr, toside->eport);
 
@@ -1141,7 +1141,7 @@ int udp_listen(const struct ctx *c, uint8_t pif, unsigned rule,
 {
 	int s;
 
-	ASSERT(!c->no_udp);
+	assert(!c->no_udp);
 
 	if (!c->ifi4) {
 		if (!addr)
@@ -1209,7 +1209,7 @@ static void udp_get_timeout_params(struct ctx *c)
  */
 int udp_init(struct ctx *c)
 {
-	ASSERT(!c->no_udp);
+	assert(!c->no_udp);
 
 	udp_get_timeout_params(c);
 
diff --git a/udp_flow.c b/udp_flow.c
index 00238372..7e2453e7 100644
--- a/udp_flow.c
+++ b/udp_flow.c
@@ -31,7 +31,7 @@ struct udp_flow *udp_at_sidx(flow_sidx_t sidx)
 	if (!flow)
 		return NULL;
 
-	ASSERT(flow->f.type == FLOW_UDP);
+	assert(flow->f.type == FLOW_UDP);
 	return &flow->udp;
 }
 
@@ -280,7 +280,7 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c,
 	union flow *flow;
 	flow_sidx_t sidx;
 
-	ASSERT(pif == PIF_TAP);
+	assert(pif == PIF_TAP);
 
 	sidx = flow_lookup_af(c, IPPROTO_UDP, pif, af, saddr, daddr,
 			      srcport, dstport);
diff --git a/udp_vu.c b/udp_vu.c
index 5effca77..7939290f 100644
--- a/udp_vu.c
+++ b/udp_vu.c
@@ -75,7 +75,7 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s,
 	int iov_cnt, iov_used;
 	size_t hdrlen, l2len;
 
-	ASSERT(!c->no_udp);
+	assert(!c->no_udp);
 
 	if (!vu_queue_enabled(vq) || !vu_queue_started(vq)) {
 		debug("Got UDP packet, but RX virtqueue not usable yet");
@@ -97,7 +97,7 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s,
 		return -1;
 
 	/* reserve space for the headers */
-	ASSERT(iov_vu[0].iov_len >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN));
+	assert(iov_vu[0].iov_len >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN));
 	iov_vu[0].iov_base = (char *)iov_vu[0].iov_base + hdrlen;
 	iov_vu[0].iov_len -= hdrlen;
 
diff --git a/util.c b/util.c
index a4f2be42..22318c00 100644
--- a/util.c
+++ b/util.c
@@ -84,7 +84,7 @@ static int sock_l4_(const struct ctx *c, enum epoll_type type,
 		socktype = SOCK_DGRAM | SOCK_NONBLOCK;
 		break;
 	default:
-		ASSERT(0);
+		assert(0);
 	}
 
 	fd = socket(af, socktype, proto);
@@ -884,7 +884,7 @@ int read_all_buf(int fd, void *buf, size_t len)
 	while (left) {
 		ssize_t rc;
 
-		ASSERT(left <= len);
+		assert(left <= len);
 
 		do
 			rc = read(fd, p, left);
@@ -924,7 +924,7 @@ int read_remainder(int fd, const struct iovec *iov, size_t cnt, size_t skip)
 		ssize_t rc;
 
 		if (offset) {
-			ASSERT(offset < iov[i].iov_len);
+			assert(offset < iov[i].iov_len);
 			/* Read the remainder of the partially read buffer */
 			if (read_all_buf(fd, (char *)iov[i].iov_base + offset,
 					 iov[i].iov_len - offset) < 0)
diff --git a/util.h b/util.h
index 4cbb5da3..dcb79afe 100644
--- a/util.h
+++ b/util.h
@@ -73,10 +73,14 @@ void abort_with_msg(const char *fmt, ...)
  * Therefore, avoid using the usual do while wrapper we use to force the macro
  * to act like a single statement requiring a ';'.
  */
-#define ASSERT_WITH_MSG(expr, ...)					\
+#define assert_with_msg(expr, ...)					\
 	((expr) ? (void)0 : abort_with_msg(__VA_ARGS__))
-#define ASSERT(expr)							\
-	ASSERT_WITH_MSG((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
+/* The standard library assert() hits our seccomp filter and dies before it can
+ * actually print a message.  So, replace it with our own version.
+ */
+#undef assert
+#define assert(expr)							\
+	assert_with_msg((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
 			__func__, __FILE__, __LINE__, STRINGIFY(expr))
 
 #ifdef P_tmpdir
diff --git a/vhost_user.c b/vhost_user.c
index 9fe12411..75665ec6 100644
--- a/vhost_user.c
+++ b/vhost_user.c
@@ -216,9 +216,9 @@ static int vu_message_read_default(int conn_fd, struct vhost_user_msg *vmsg)
 		    cmsg->cmsg_type == SCM_RIGHTS) {
 			size_t fd_size;
 
-			ASSERT(cmsg->cmsg_len >= CMSG_LEN(0));
+			assert(cmsg->cmsg_len >= CMSG_LEN(0));
 			fd_size = cmsg->cmsg_len - CMSG_LEN(0);
-			ASSERT(fd_size <= sizeof(vmsg->fds));
+			assert(fd_size <= sizeof(vmsg->fds));
 			vmsg->fd_num = fd_size / sizeof(int);
 			memcpy(vmsg->fds, CMSG_DATA(cmsg), fd_size);
 			break;
@@ -272,7 +272,7 @@ static void vu_message_write(int conn_fd, struct vhost_user_msg *vmsg)
 	};
 	int rc;
 
-	ASSERT(vmsg->fd_num <= VHOST_MEMORY_BASELINE_NREGIONS);
+	assert(vmsg->fd_num <= VHOST_MEMORY_BASELINE_NREGIONS);
 	if (vmsg->fd_num > 0) {
 		size_t fdsize = vmsg->fd_num * sizeof(int);
 		struct cmsghdr *cmsg;
@@ -483,7 +483,7 @@ static bool vu_set_mem_table_exec(struct vu_dev *vdev,
 		}
 	}
 
-	ASSERT(vdev->memory.nregions < VHOST_USER_MAX_RAM_SLOTS);
+	assert(vdev->memory.nregions < VHOST_USER_MAX_RAM_SLOTS);
 
 	return false;
 }
diff --git a/virtio.c b/virtio.c
index 447137ee..47b6cc2a 100644
--- a/virtio.c
+++ b/virtio.c
@@ -402,8 +402,8 @@ static bool virtqueue_map_desc(const struct vu_dev *dev,
 {
 	unsigned int num_sg = *p_num_sg;
 
-	ASSERT(num_sg < max_num_sg);
-	ASSERT(sz);
+	assert(num_sg < max_num_sg);
+	assert(sz);
 
 	while (sz) {
 		uint64_t len = sz;
diff --git a/vu_common.c b/vu_common.c
index 5f2ce18e..75ad43d3 100644
--- a/vu_common.c
+++ b/vu_common.c
@@ -43,7 +43,7 @@ int vu_packet_check_range(struct vdev_memory *memory,
 		/* NOLINTNEXTLINE(performance-no-int-to-ptr) */
 		const char *base = (const char *)base_addr;
 
-		ASSERT(base_addr >= dev_region[i].mmap_addr);
+		assert(base_addr >= dev_region[i].mmap_addr);
 
 		if (len <= dev_region[i].size && base <= ptr &&
 		    (size_t)(ptr - base) <= dev_region[i].size - len)
@@ -167,7 +167,7 @@ static void vu_handle_tx(struct vu_dev *vdev, int index,
 	int out_sg_count;
 	int count;
 
-	ASSERT(VHOST_USER_IS_QUEUE_TX(index));
+	assert(VHOST_USER_IS_QUEUE_TX(index));
 
 	tap_flush_pools();
 
-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 5/5] pesto: Introduce stub configuration interface and tool
  2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
                   ` (3 preceding siblings ...)
  2026-03-16  5:46 ` [PATCH 4/5] treewide: Spell ASSERT() as assert() David Gibson
@ 2026-03-16  5:46 ` David Gibson
  2026-03-17  0:02   ` Stefano Brivio
  2026-03-17  0:02 ` [PATCH 0/5] RFC: Stub dynamic update implementation Stefano Brivio
  5 siblings, 1 reply; 13+ messages in thread
From: David Gibson @ 2026-03-16  5:46 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

Add a control socket to passt/pasta for updating configuration at runtime.
Add a tool (pesto) for communicating with the control socket.

For now this is a stub implementation that forms the connection and sends
some version information, but nothing more.
Example:
  ./pasta --debug --config-net -c /tmp/pasta.conf -t none
  ./pesto /tmp/pasta.conf

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
[dwg: Based on an early draft from Stefano]
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 .gitignore   |   2 +
 Makefile     |  32 +++++++----
 conf.c       | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 conf.h       |   2 +
 epoll_type.h |   4 ++
 passt.1      |   5 ++
 passt.c      |   8 +++
 passt.h      |   6 +++
 pesto.1      |  47 ++++++++++++++++
 pesto.c      | 111 ++++++++++++++++++++++++++++++++++++++
 pesto.h      |  34 ++++++++++++
 pesto_util.c |  62 +++++++++++++++++++++
 pesto_util.h |  19 +++++++
 util.c       |  38 -------------
 util.h       |   5 +-
 15 files changed, 469 insertions(+), 54 deletions(-)
 create mode 100644 pesto.1
 create mode 100644 pesto.c
 create mode 100644 pesto.h
 create mode 100644 pesto_util.c
 create mode 100644 pesto_util.h

diff --git a/.gitignore b/.gitignore
index 3c16adc7..3e40d9f7 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 fe016f30..a528b34a 100644
--- a/Makefile
+++ b/Makefile
@@ -39,21 +39,25 @@ FLAGS += -DDUAL_STACK_SOCKETS=$(DUAL_STACK_SOCKETS)
 
 PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c epoll_ctl.c \
 	flow.c fwd.c icmp.c igmp.c inany.c iov.c ip.c isolation.c lineread.c \
-	log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c pasta.c pcap.c \
-	pif.c repair.c tap.c tcp.c tcp_buf.c tcp_splice.c tcp_vu.c udp.c \
-	udp_flow.c udp_vu.c util.c vhost_user.c virtio.c vu_common.c
+	log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c pasta.c \
+	pesto_util.c pcap.c pif.c repair.c tap.c tcp.c tcp_buf.c tcp_splice.c \
+	tcp_vu.c udp.c udp_flow.c udp_vu.c util.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 pesto_util.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
 
+PESTO_HEADERS = pesto.h pesto_util.h
 PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h epoll_ctl.h \
 	flow.h fwd.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 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
+	udp_internal.h udp_vu.h util.h vhost_user.h virtio.h vu_common.h \
+	$(PESTO_HEADERS)
 HEADERS = $(PASST_HEADERS) seccomp.h
 
 C := \#include <sys/random.h>\nint main(){int a=getrandom(0, 0, 0);}
@@ -74,9 +78,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
@@ -90,6 +94,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)
 
@@ -109,6 +116,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 \
@@ -118,7 +128,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
 
@@ -172,11 +182,11 @@ docs: README.md
 		done < README.md;					\
 	) > README.plain.md
 
-clang-tidy: $(PASST_SRCS)
+clang-tidy: $(PASST_SRCS) $(PESTO_SRCS)
 	clang-tidy $^ -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
 	           -DCLANG_TIDY_58992
 
-cppcheck: $(PASST_SRCS) $(HEADERS)
+cppcheck: $(PASST_SRCS) $(PESTO_SRCS) $(HEADERS)
 	if cppcheck --check-level=exhaustive /dev/null > /dev/null 2>&1; then \
 		CPPCHECK_EXHAUSTIVE="--check-level=exhaustive";		\
 	else								\
diff --git a/conf.c b/conf.c
index 940fb9e9..04f65b30 100644
--- a/conf.c
+++ b/conf.c
@@ -47,6 +47,9 @@
 #include "isolation.h"
 #include "log.h"
 #include "vhost_user.h"
+#include "epoll_ctl.h"
+#include "conf.h"
+#include "pesto.h"
 
 #define NETNS_RUN_DIR	"/run/netns"
 
@@ -894,6 +897,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");
 
@@ -1425,6 +1429,17 @@ 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_conf = -1;
+	if (*c->conf_path) {
+		c->fd_conf_listen = sock_unix(c->conf_path);
+		if (c->fd_conf_listen < 0) {
+			die_perror("Couldn't open control socket %s",
+				   c->conf_path);
+		}
+	} else {
+		c->fd_conf_listen = -1;
+	}
 }
 
 /**
@@ -1460,6 +1475,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_conf_listen < 0)
+		return;
+
+	if (listen(c->fd_conf_listen, 0))
+		die_perror("Couldn't listen on configuration socket");
+
+	ref.fd = c->fd_conf_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
@@ -1542,9 +1576,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";
 	char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 };
 	bool copy_addrs_opt = false, copy_routes_opt = false;
@@ -1808,6 +1843,13 @@ void conf(struct ctx *c, int argc, char **argv)
 
 			c->fd_tap = -1;
 			break;
+		case 'c':
+			ret = snprintf(c->conf_path, sizeof(c->conf_path), "%s",
+				       optarg);
+			if (ret <= 0 || ret >= (int)sizeof(c->sock_path))
+				die("Invalid configuration path: %s", optarg);
+			c->fd_conf_listen = c->fd_conf = -1;
+			break;
 		case 'F':
 			errno = 0;
 			fd_tap_opt = strtol(optarg, NULL, 0);
@@ -2242,4 +2284,108 @@ void conf(struct ctx *c, int argc, char **argv)
 
 	if (!c->quiet)
 		conf_print(c);
+
+	conf_sock_listen(c);
+}
+
+/**
+ * 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)
+{
+	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;
+
+	if (events != EPOLLIN) {
+		err("Unexpected event 0x%04x on configuration socket", events);
+		return;
+	}
+
+	fd = accept4(c->fd_conf_listen, NULL, NULL, SOCK_NONBLOCK);
+	if (fd < 0) {
+		warn_perror("accept4() on configuration listening socket");
+		return;
+	}
+
+	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0)
+		warn_perror("Can't get configuration client credentials");
+
+	/* Another client is already connected: accept and close right away. */
+	if (c->fd_conf != -1) {
+		info("Discarding configuration client, PID %i", uc.pid);
+		goto fail;
+	}
+
+	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;
+	}
+
+	c->fd_conf = fd;
+	info("Accepted configuration client, PID %i", uc.pid);
+	if (!PESTO_PROTOCOL_VERSION) {
+		warn(
+"Warning: Using experimental unsupported configuration protocol");
+	}
+
+	return;
+
+fail:
+	close(fd);
+}
+
+/**
+ * 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_conf, 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:
+	debug("Closing configuration socket");
+	epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_conf, NULL);
+	close(c->fd_conf);
+	c->fd_conf = -1;
 }
diff --git a/conf.h b/conf.h
index b45ad746..16f97189 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 a90ffb67..061325aa 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 13e8df9d..32d70c8d 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 f84419c7..bc42ea33 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 b614bdf0..a49fbe1d 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
+ * @conf_path:		Path for 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_conf_listen:	Listening configuration socket, if any
+ * @fd_conf:		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
@@ -224,6 +227,7 @@ struct ctx {
 	int foreground;
 	int nofile;
 	char sock_path[UNIX_PATH_MAX];
+	char conf_path[UNIX_PATH_MAX];
 	char repair_path[UNIX_PATH_MAX];
 	char pcap[PATH_MAX];
 
@@ -241,6 +245,8 @@ struct ctx {
 	int epollfd;
 	int fd_tap_listen;
 	int fd_tap;
+	int fd_conf_listen;
+	int fd_conf;
 	int fd_repair_listen;
 	int fd_repair;
 	unsigned char our_tap_mac[ETH_ALEN];
diff --git a/pesto.1 b/pesto.1
new file mode 100644
index 00000000..fe2072f5
--- /dev/null
+++ b/pesto.1
@@ -0,0 +1,47 @@
+.\" SPDX-License-Identifier: GPL-2.0-or-later
+.\" Copyright Red Hat
+.\" Author: David Gibson <david@gibson.dropbear.id.au>
+.TH pesto 1
+
+.SH NAME
+.B pesto
+\- View or alter configuration of a running \fBpasst\fR(1) or
+\fBpasta\fR(1).
+
+.SH SYNOPSIS
+.B pesto
+\fIPATH\fR
+
+.SH DESCRIPTION
+
+.B pesto
+is an experimental 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 AUTHOR
+
+Stefano Brivio <sbrivio@redhat.com>,
+David Gibson <david@gibson.dropbear.id.au>.
+
+.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 them and/or modify
+them 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 00000000..a158eadf
--- /dev/null
+++ b/pesto.c
@@ -0,0 +1,111 @@
+// 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 <sbrivio@redhat.com>
+ */
+
+#include <arpa/inet.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <linux/audit.h>
+#include <linux/capability.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+
+#include "seccomp_pesto.h"
+#include "pesto_util.h"
+#include "pesto.h"
+
+#define die(...)							\
+	do {								\
+		FPRINTF(stderr, __VA_ARGS__);				\
+		FPRINTF(stderr, "\n");					\
+		exit(EXIT_FAILURE);					\
+	} while (0)
+
+/**
+ * main() - Entry point and whole program with loop
+ * @argc:	Argument count
+ * @argv:	Arguments: socket path, operation, port specifiers
+ *
+ * Return: 0 on success, won't return on failure
+ *
+ * #syscalls:pesto connect write close exit_group fstat brk
+ * #syscalls:pesto socket s390x:socketcall i686:socketcall
+ * #syscalls:pesto recvfrom recvmsg arm:recv ppc64le:recv
+ * #syscalls:pesto sendto sendmsg arm:send ppc64le:send
+ */
+int main(int argc, char **argv)
+{
+	struct sockaddr_un a = { AF_UNIX, "" };
+	struct pesto_hello hello;
+	struct sock_fprog prog;
+	uint32_t s_version;
+	int ret, s;
+
+	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");
+
+	if (argc < 2)
+		die("Usage: %s CONTROLPATH", argv[0]);
+
+	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+		die("Failed to create AF_UNIX socket: %s", strerror(errno));
+
+	ret = snprintf(a.sun_path, sizeof(a.sun_path), "%s", argv[1]);
+	if (ret <= 0 || ret >= (int)sizeof(a.sun_path))
+		die("Invalid socket path \"%s\"", argv[1]);
+
+	ret = connect(s, (struct sockaddr *)&a, sizeof(a));
+	if (ret < 0) {
+		die("Failed to connect to %s: %s",
+		    a.sun_path, strerror(errno));
+	}
+
+	ret = read_all_buf(s, &hello, sizeof(hello));
+	if (ret < 0)
+		die("Couldn't read server greeting: %s", strerror(errno));
+
+	if (memcmp(hello.magic, PESTO_SERVER_MAGIC, sizeof(hello.magic)))
+		die("Bad magic number from server");
+
+	s_version = ntohl(hello.version);
+
+	if (s_version > PESTO_PROTOCOL_VERSION) {
+		die("Unknown server protocol version %"PRIu32" > %"PRIu32"\n",
+		    s_version, PESTO_PROTOCOL_VERSION);
+	}
+
+	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");
+	}
+
+	return 0;
+}
diff --git a/pesto.h b/pesto.h
new file mode 100644
index 00000000..92d4df3a
--- /dev/null
+++ b/pesto.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ *
+ * Definitions and functions used by both client and server of the configuration
+ * update protocol (pesto).
+ */
+
+#ifndef PESTO_H
+#define PESTO_H
+
+#include <assert.h>
+#include <stdint.h>
+
+#define PESTO_SERVER_MAGIC	"pesto:s"
+
+/* Version 0 is reserved for unreleased / unsupported experimental versions */
+#define PESTO_PROTOCOL_VERSION	0
+
+/**
+ * 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/pesto_util.c b/pesto_util.c
new file mode 100644
index 00000000..8e6b43d4
--- /dev/null
+++ b/pesto_util.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* ASST - Plug A Simple Socket Transport
+ *  for qemu/UNIX domain socket mode
+ *
+ * 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
+ *
+ * pesto_util.c - Helpers used by both passt/pasta and pesto
+ *
+ * Copyright Red Hat
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "pesto_util.h"
+
+/**
+ * read_all_buf() - Fill a whole buffer from a file descriptor
+ * @fd:		File descriptor
+ * @buf:	Pointer to base of buffer
+ * @len:	Length of buffer
+ *
+ * Return: 0 on success, -1 on error (with errno set)
+ *
+ * #syscalls read
+ */
+int read_all_buf(int fd, void *buf, size_t len)
+{
+	size_t left = len;
+	char *p = buf;
+
+	while (left) {
+		ssize_t rc;
+
+		assert(left <= len);
+
+		do
+			rc = read(fd, p, left);
+		while ((rc < 0) && errno == EINTR);
+
+		if (rc < 0)
+			return -1;
+
+		if (rc == 0) {
+			errno = ENODATA;
+			return -1;
+		}
+
+		p += rc;
+		left -= rc;
+	}
+	return 0;
+}
diff --git a/pesto_util.h b/pesto_util.h
new file mode 100644
index 00000000..1fc5d9fd
--- /dev/null
+++ b/pesto_util.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ *
+ * Helper functions used by both passt/pasta and pesto (the configuration update
+ * client).
+ */
+
+#ifndef PESTO_UTIL_H
+#define PESTO_UTIL_H
+
+#include <stddef.h>
+
+/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */
+#define FPRINTF(f, ...)	(void)fprintf(f, __VA_ARGS__)
+
+int read_all_buf(int fd, void *buf, size_t len);
+
+#endif /* PESTO_UTIL_H */
diff --git a/util.c b/util.c
index 22318c00..1571850b 100644
--- a/util.c
+++ b/util.c
@@ -866,44 +866,6 @@ int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip)
 	return 0;
 }
 
-/**
- * read_all_buf() - Fill a whole buffer from a file descriptor
- * @fd:		File descriptor
- * @buf:	Pointer to base of buffer
- * @len:	Length of buffer
- *
- * Return: 0 on success, -1 on error (with errno set)
- *
- * #syscalls read
- */
-int read_all_buf(int fd, void *buf, size_t len)
-{
-	size_t left = len;
-	char *p = buf;
-
-	while (left) {
-		ssize_t rc;
-
-		assert(left <= len);
-
-		do
-			rc = read(fd, p, left);
-		while ((rc < 0) && errno == EINTR);
-
-		if (rc < 0)
-			return -1;
-
-		if (rc == 0) {
-			errno = ENODATA;
-			return -1;
-		}
-
-		p += rc;
-		left -= rc;
-	}
-	return 0;
-}
-
 /**
  * read_remainder() - Read the tail of an IO vector from a file descriptor
  * @fd:		File descriptor
diff --git a/util.h b/util.h
index dcb79afe..74227a68 100644
--- a/util.h
+++ b/util.h
@@ -20,6 +20,7 @@
 #include <net/ethernet.h>
 
 #include "log.h"
+#include "pesto_util.h"
 
 #define VERSION_BLOB							       \
 	VERSION "\n"							       \
@@ -242,7 +243,6 @@ int write_file(const char *path, const char *buf);
 intmax_t read_file_integer(const char *path, intmax_t fallback);
 int write_all_buf(int fd, const void *buf, size_t len);
 int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip);
-int read_all_buf(int fd, void *buf, size_t len);
 int read_remainder(int fd, const struct iovec *iov, size_t cnt, size_t skip);
 void close_open_files(int argc, char **argv);
 bool snprintf_check(char *str, size_t size, const char *format, ...);
@@ -314,9 +314,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);
 
 /*
-- 
2.53.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/5] treewide: Spell ASSERT() as assert()
  2026-03-16  5:46 ` [PATCH 4/5] treewide: Spell ASSERT() as assert() David Gibson
@ 2026-03-17  0:02   ` Stefano Brivio
  2026-03-17  0:39     ` David Gibson
  0 siblings, 1 reply; 13+ messages in thread
From: Stefano Brivio @ 2026-03-17  0:02 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev

On Mon, 16 Mar 2026 16:46:28 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> +++ b/util.h
> @@ -73,10 +73,14 @@ void abort_with_msg(const char *fmt, ...)
>   * Therefore, avoid using the usual do while wrapper we use to force the macro
>   * to act like a single statement requiring a ';'.
>   */
> -#define ASSERT_WITH_MSG(expr, ...)					\
> +#define assert_with_msg(expr, ...)					\
>  	((expr) ? (void)0 : abort_with_msg(__VA_ARGS__))
> -#define ASSERT(expr)							\
> -	ASSERT_WITH_MSG((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
> +/* The standard library assert() hits our seccomp filter and dies before it can
> + * actually print a message.  So, replace it with our own version.
> + */
> +#undef assert
> +#define assert(expr)							\
> +	assert_with_msg((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
>  			__func__, __FILE__, __LINE__, STRINGIFY(expr))

While looking this up to make sure it's specified as a macro (it is,
and this builds against musl as well), I realised that POSIX.1-2024
says:

  https://pubs.opengroup.org/onlinepubs/9799919799/functions/assert.html

  Forcing a definition of the name NDEBUG, either from the compiler
  command line or with the preprocessor control statement #define NDEBUG
  ahead of the #include <assert.h> statement, shall stop assertions from
  being compiled into the program.

...so, I wonder, now that it's called assert(), should we define it as
"do { } while(0)" #ifdef NDEBUG, for correctness (and maybe somebody
has obscure usages for NDEBUG which we shouldn't sabotage)?

This will conflict with "[PATCH v2 3/3] vu_common: Move iovec management
into vu_collect()" by the way, but I'll take care of it, if it still
conflicts by the time I merge it.

-- 
Stefano


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/5] RFC: Stub dynamic update implementation
  2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
                   ` (4 preceding siblings ...)
  2026-03-16  5:46 ` [PATCH 5/5] pesto: Introduce stub configuration interface and tool David Gibson
@ 2026-03-17  0:02 ` Stefano Brivio
  5 siblings, 0 replies; 13+ messages in thread
From: Stefano Brivio @ 2026-03-17  0:02 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev

On Mon, 16 Mar 2026 16:46:24 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> I've taken Stefano's draft implementation of dynamic updates, and
> polished it up to have a stub implementation of the dynamic update
> protocol.  So far it doesn't actually do anything, beyond establishing
> the connection and checking versions.  I'm continuing to work on the
> actual guts of it.
> 
> Patches 1..3/5 are trivial cleanups I happened across while working on
> this.  Feel free to apply if you like.  4/5 clears up a more specific
> problem that caused problems sharing code between client and server.
> 5/5 is the implementation proper.

Applied up to 3/5. Any other reason not to apply 4/5 other than
possibly my pending comment? I'd be happy to keep this series as small
as possible.

-- 
Stefano


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 5/5] pesto: Introduce stub configuration interface and tool
  2026-03-16  5:46 ` [PATCH 5/5] pesto: Introduce stub configuration interface and tool David Gibson
@ 2026-03-17  0:02   ` Stefano Brivio
  2026-03-17  0:48     ` David Gibson
  0 siblings, 1 reply; 13+ messages in thread
From: Stefano Brivio @ 2026-03-17  0:02 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev

On Mon, 16 Mar 2026 16:46:29 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> Add a control socket to passt/pasta for updating configuration at runtime.
> Add a tool (pesto) for communicating with the control socket.
> 
> For now this is a stub implementation that forms the connection and sends
> some version information, but nothing more.
> Example:
>   ./pasta --debug --config-net -c /tmp/pasta.conf -t none
>   ./pesto /tmp/pasta.conf
> 
> Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
> [dwg: Based on an early draft from Stefano]
> Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> ---
>  .gitignore   |   2 +
>  Makefile     |  32 +++++++----
>  conf.c       | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++-
>  conf.h       |   2 +
>  epoll_type.h |   4 ++
>  passt.1      |   5 ++
>  passt.c      |   8 +++
>  passt.h      |   6 +++
>  pesto.1      |  47 ++++++++++++++++
>  pesto.c      | 111 ++++++++++++++++++++++++++++++++++++++
>  pesto.h      |  34 ++++++++++++
>  pesto_util.c |  62 +++++++++++++++++++++
>  pesto_util.h |  19 +++++++
>  util.c       |  38 -------------
>  util.h       |   5 +-
>  15 files changed, 469 insertions(+), 54 deletions(-)
>  create mode 100644 pesto.1
>  create mode 100644 pesto.c
>  create mode 100644 pesto.h
>  create mode 100644 pesto_util.c
>  create mode 100644 pesto_util.h
> 
> diff --git a/.gitignore b/.gitignore
> index 3c16adc7..3e40d9f7 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 fe016f30..a528b34a 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -39,21 +39,25 @@ FLAGS += -DDUAL_STACK_SOCKETS=$(DUAL_STACK_SOCKETS)
>  
>  PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c epoll_ctl.c \
>  	flow.c fwd.c icmp.c igmp.c inany.c iov.c ip.c isolation.c lineread.c \
> -	log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c pasta.c pcap.c \
> -	pif.c repair.c tap.c tcp.c tcp_buf.c tcp_splice.c tcp_vu.c udp.c \
> -	udp_flow.c udp_vu.c util.c vhost_user.c virtio.c vu_common.c
> +	log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c pasta.c \
> +	pesto_util.c pcap.c pif.c repair.c tap.c tcp.c tcp_buf.c tcp_splice.c \
> +	tcp_vu.c udp.c udp_flow.c udp_vu.c util.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 pesto_util.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
>  
> +PESTO_HEADERS = pesto.h pesto_util.h
>  PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h epoll_ctl.h \
>  	flow.h fwd.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 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
> +	udp_internal.h udp_vu.h util.h vhost_user.h virtio.h vu_common.h \
> +	$(PESTO_HEADERS)
>  HEADERS = $(PASST_HEADERS) seccomp.h
>  
>  C := \#include <sys/random.h>\nint main(){int a=getrandom(0, 0, 0);}
> @@ -74,9 +78,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
> @@ -90,6 +94,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)
>  
> @@ -109,6 +116,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 \
> @@ -118,7 +128,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
>  
> @@ -172,11 +182,11 @@ docs: README.md
>  		done < README.md;					\
>  	) > README.plain.md
>  
> -clang-tidy: $(PASST_SRCS)
> +clang-tidy: $(PASST_SRCS) $(PESTO_SRCS)
>  	clang-tidy $^ -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
>  	           -DCLANG_TIDY_58992
>  
> -cppcheck: $(PASST_SRCS) $(HEADERS)
> +cppcheck: $(PASST_SRCS) $(PESTO_SRCS) $(HEADERS)
>  	if cppcheck --check-level=exhaustive /dev/null > /dev/null 2>&1; then \
>  		CPPCHECK_EXHAUSTIVE="--check-level=exhaustive";		\
>  	else								\
> diff --git a/conf.c b/conf.c
> index 940fb9e9..04f65b30 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -47,6 +47,9 @@
>  #include "isolation.h"
>  #include "log.h"
>  #include "vhost_user.h"
> +#include "epoll_ctl.h"
> +#include "conf.h"
> +#include "pesto.h"
>  
>  #define NETNS_RUN_DIR	"/run/netns"
>  
> @@ -894,6 +897,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"

--conf-path

>  		"  -h, --help		Display this help message and exit\n"
>  		"  --version		Show version and exit\n");
>  
> @@ -1425,6 +1429,17 @@ 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_conf = -1;

Note: we should remember to tackle your own comments from
<aYqw6M2G9n-tv9Qm@zatzit>, eventually (not necessarily now, but fd_conf
just reminded me).

> +	if (*c->conf_path) {
> +		c->fd_conf_listen = sock_unix(c->conf_path);
> +		if (c->fd_conf_listen < 0) {
> +			die_perror("Couldn't open control socket %s",
> +				   c->conf_path);
> +		}
> +	} else {
> +		c->fd_conf_listen = -1;
> +	}
>  }
>  
>  /**
> @@ -1460,6 +1475,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_conf_listen < 0)
> +		return;
> +
> +	if (listen(c->fd_conf_listen, 0))
> +		die_perror("Couldn't listen on configuration socket");
> +
> +	ref.fd = c->fd_conf_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
> @@ -1542,9 +1576,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";
>  	char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 };
>  	bool copy_addrs_opt = false, copy_routes_opt = false;
> @@ -1808,6 +1843,13 @@ void conf(struct ctx *c, int argc, char **argv)
>  
>  			c->fd_tap = -1;
>  			break;
> +		case 'c':
> +			ret = snprintf(c->conf_path, sizeof(c->conf_path), "%s",
> +				       optarg);
> +			if (ret <= 0 || ret >= (int)sizeof(c->sock_path))
> +				die("Invalid configuration path: %s", optarg);
> +			c->fd_conf_listen = c->fd_conf = -1;
> +			break;
>  		case 'F':
>  			errno = 0;
>  			fd_tap_opt = strtol(optarg, NULL, 0);
> @@ -2242,4 +2284,108 @@ void conf(struct ctx *c, int argc, char **argv)
>  
>  	if (!c->quiet)
>  		conf_print(c);
> +
> +	conf_sock_listen(c);
> +}
> +
> +/**
> + * 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)
> +{
> +	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;
> +
> +	if (events != EPOLLIN) {
> +		err("Unexpected event 0x%04x on configuration socket", events);
> +		return;
> +	}
> +
> +	fd = accept4(c->fd_conf_listen, NULL, NULL, SOCK_NONBLOCK);
> +	if (fd < 0) {
> +		warn_perror("accept4() on configuration listening socket");
> +		return;
> +	}
> +
> +	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0)
> +		warn_perror("Can't get configuration client credentials");
> +
> +	/* Another client is already connected: accept and close right away. */
> +	if (c->fd_conf != -1) {
> +		info("Discarding configuration client, PID %i", uc.pid);
> +		goto fail;
> +	}
> +
> +	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;
> +	}
> +
> +	c->fd_conf = fd;
> +	info("Accepted configuration client, PID %i", uc.pid);
> +	if (!PESTO_PROTOCOL_VERSION) {
> +		warn(
> +"Warning: Using experimental unsupported configuration protocol");
> +	}
> +
> +	return;
> +
> +fail:
> +	close(fd);
> +}
> +
> +/**
> + * 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_conf, 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:
> +	debug("Closing configuration socket");
> +	epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_conf, NULL);
> +	close(c->fd_conf);
> +	c->fd_conf = -1;
>  }
> diff --git a/conf.h b/conf.h
> index b45ad746..16f97189 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 a90ffb67..061325aa 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 13e8df9d..32d70c8d 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)

I wouldn't even mention (EXPERIMENTAL). Pretty much all the options we
added so far were.

> +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 f84419c7..bc42ea33 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 b614bdf0..a49fbe1d 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
> + * @conf_path:		Path for 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_conf_listen:	Listening configuration socket, if any
> + * @fd_conf:		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
> @@ -224,6 +227,7 @@ struct ctx {
>  	int foreground;
>  	int nofile;
>  	char sock_path[UNIX_PATH_MAX];
> +	char conf_path[UNIX_PATH_MAX];
>  	char repair_path[UNIX_PATH_MAX];
>  	char pcap[PATH_MAX];
>  
> @@ -241,6 +245,8 @@ struct ctx {
>  	int epollfd;
>  	int fd_tap_listen;
>  	int fd_tap;
> +	int fd_conf_listen;
> +	int fd_conf;
>  	int fd_repair_listen;
>  	int fd_repair;
>  	unsigned char our_tap_mac[ETH_ALEN];
> diff --git a/pesto.1 b/pesto.1
> new file mode 100644
> index 00000000..fe2072f5
> --- /dev/null
> +++ b/pesto.1
> @@ -0,0 +1,47 @@
> +.\" SPDX-License-Identifier: GPL-2.0-or-later
> +.\" Copyright Red Hat
> +.\" Author: David Gibson <david@gibson.dropbear.id.au>
> +.TH pesto 1
> +
> +.SH NAME
> +.B pesto
> +\- View or alter configuration of a running \fBpasst\fR(1) or
> +\fBpasta\fR(1).

...process/instance?

> +
> +.SH SYNOPSIS
> +.B pesto
> +\fIPATH\fR
> +
> +.SH DESCRIPTION
> +
> +.B pesto
> +is an experimental client to view and update the port forwarding

I'd drop "experimental" from here as well, otherwise we'll never drop
it until somebody asks if there's a "stable" solution in two years.

> +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 AUTHOR

Should be AUTHORS (just like in passt(1) -- I checked, back then, both
section names seem to be common and they aren't really standardised
anywhere).

> +
> +Stefano Brivio <sbrivio@redhat.com>,
> +David Gibson <david@gibson.dropbear.id.au>.
> +
> +.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 them and/or modify
> +them 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 00000000..a158eadf
> --- /dev/null
> +++ b/pesto.c
> @@ -0,0 +1,111 @@
> +// 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 <sbrivio@redhat.com>
> + */
> +
> +#include <arpa/inet.h>
> +#include <sys/prctl.h>
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <limits.h>
> +#include <unistd.h>
> +
> +#include <linux/audit.h>
> +#include <linux/capability.h>
> +#include <linux/filter.h>
> +#include <linux/seccomp.h>
> +
> +#include "seccomp_pesto.h"
> +#include "pesto_util.h"
> +#include "pesto.h"
> +
> +#define die(...)							\
> +	do {								\
> +		FPRINTF(stderr, __VA_ARGS__);				\
> +		FPRINTF(stderr, "\n");					\
> +		exit(EXIT_FAILURE);					\
> +	} while (0)
> +
> +/**
> + * main() - Entry point and whole program with loop
> + * @argc:	Argument count
> + * @argv:	Arguments: socket path, operation, port specifiers
> + *
> + * Return: 0 on success, won't return on failure
> + *
> + * #syscalls:pesto connect write close exit_group fstat brk
> + * #syscalls:pesto socket s390x:socketcall i686:socketcall
> + * #syscalls:pesto recvfrom recvmsg arm:recv ppc64le:recv
> + * #syscalls:pesto sendto sendmsg arm:send ppc64le:send
> + */
> +int main(int argc, char **argv)
> +{
> +	struct sockaddr_un a = { AF_UNIX, "" };
> +	struct pesto_hello hello;
> +	struct sock_fprog prog;
> +	uint32_t s_version;
> +	int ret, s;
> +
> +	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");
> +
> +	if (argc < 2)
> +		die("Usage: %s CONTROLPATH", argv[0]);

While CONTROLPATH is a bit more descriptive, I think it's also harder
to read compared to PATH, and after all it's not adding much.

> +
> +	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
> +		die("Failed to create AF_UNIX socket: %s", strerror(errno));
> +
> +	ret = snprintf(a.sun_path, sizeof(a.sun_path), "%s", argv[1]);
> +	if (ret <= 0 || ret >= (int)sizeof(a.sun_path))
> +		die("Invalid socket path \"%s\"", argv[1]);
> +
> +	ret = connect(s, (struct sockaddr *)&a, sizeof(a));
> +	if (ret < 0) {
> +		die("Failed to connect to %s: %s",
> +		    a.sun_path, strerror(errno));
> +	}
> +
> +	ret = read_all_buf(s, &hello, sizeof(hello));
> +	if (ret < 0)
> +		die("Couldn't read server greeting: %s", strerror(errno));
> +
> +	if (memcmp(hello.magic, PESTO_SERVER_MAGIC, sizeof(hello.magic)))
> +		die("Bad magic number from server");
> +
> +	s_version = ntohl(hello.version);
> +
> +	if (s_version > PESTO_PROTOCOL_VERSION) {
> +		die("Unknown server protocol version %"PRIu32" > %"PRIu32"\n",
> +		    s_version, PESTO_PROTOCOL_VERSION);
> +	}
> +
> +	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");
> +	}
> +
> +	return 0;
> +}
> diff --git a/pesto.h b/pesto.h
> new file mode 100644
> index 00000000..92d4df3a
> --- /dev/null
> +++ b/pesto.h
> @@ -0,0 +1,34 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + * Copyright Red Hat
> + * Author: David Gibson <david@gibson.dropbear.id.au>
> + *
> + * Definitions and functions used by both client and server of the configuration
> + * update protocol (pesto).

The answer is probably not obvious now, but eventually we should figure
out what we want to call "pesto": for me it was just the client, from
this description it sounds like the protocol as well (or Bad Acronym
Sounds Indeed Logical?). Maybe we'll never need to name the protocol
though.

> + */
> +
> +#ifndef PESTO_H
> +#define PESTO_H
> +
> +#include <assert.h>
> +#include <stdint.h>
> +
> +#define PESTO_SERVER_MAGIC	"pesto:s"

Regardless of the consideration above, shouldn't this be "basil:s"?

> +
> +/* Version 0 is reserved for unreleased / unsupported experimental versions */
> +#define PESTO_PROTOCOL_VERSION	0
> +
> +/**
> + * 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/pesto_util.c b/pesto_util.c
> new file mode 100644
> index 00000000..8e6b43d4
> --- /dev/null
> +++ b/pesto_util.c
> @@ -0,0 +1,62 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +/* ASST - Plug A Simple Socket Transport

:)

> + *  for qemu/UNIX domain socket mode
> + *
> + * 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
> + *
> + * pesto_util.c - Helpers used by both passt/pasta and pesto

Maybe we should call this common.c? It's a bit counter-intuitive to
look for read_all_buf() as used by passt into pesto_util.c. I'm not
fond of common.c either, it's less descriptive, but also potentially
less misleading.

> + * Copyright Red Hat
> + * Author: Stefano Brivio <sbrivio@redhat.com>
> + * Author: David Gibson <david@gibson.dropbear.id.au>
> + */
> +
> +#include <assert.h>
> +#include <errno.h>
> +#include <unistd.h>
> +#include <sys/types.h>
> +
> +#include "pesto_util.h"
> +
> +/**
> + * read_all_buf() - Fill a whole buffer from a file descriptor
> + * @fd:		File descriptor
> + * @buf:	Pointer to base of buffer
> + * @len:	Length of buffer
> + *
> + * Return: 0 on success, -1 on error (with errno set)
> + *
> + * #syscalls read
> + */
> +int read_all_buf(int fd, void *buf, size_t len)
> +{
> +	size_t left = len;
> +	char *p = buf;
> +
> +	while (left) {
> +		ssize_t rc;
> +
> +		assert(left <= len);
> +
> +		do
> +			rc = read(fd, p, left);
> +		while ((rc < 0) && errno == EINTR);
> +
> +		if (rc < 0)
> +			return -1;
> +
> +		if (rc == 0) {
> +			errno = ENODATA;
> +			return -1;
> +		}
> +
> +		p += rc;
> +		left -= rc;
> +	}
> +	return 0;
> +}
> diff --git a/pesto_util.h b/pesto_util.h
> new file mode 100644
> index 00000000..1fc5d9fd
> --- /dev/null
> +++ b/pesto_util.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + * Copyright Red Hat
> + * Author: David Gibson <david@gibson.dropbear.id.au>
> + *
> + * Helper functions used by both passt/pasta and pesto (the configuration update
> + * client).
> + */
> +
> +#ifndef PESTO_UTIL_H
> +#define PESTO_UTIL_H
> +
> +#include <stddef.h>
> +
> +/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */
> +#define FPRINTF(f, ...)	(void)fprintf(f, __VA_ARGS__)
> +
> +int read_all_buf(int fd, void *buf, size_t len);
> +
> +#endif /* PESTO_UTIL_H */
> diff --git a/util.c b/util.c
> index 22318c00..1571850b 100644
> --- a/util.c
> +++ b/util.c
> @@ -866,44 +866,6 @@ int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip)
>  	return 0;
>  }
>  
> -/**
> - * read_all_buf() - Fill a whole buffer from a file descriptor
> - * @fd:		File descriptor
> - * @buf:	Pointer to base of buffer
> - * @len:	Length of buffer
> - *
> - * Return: 0 on success, -1 on error (with errno set)
> - *
> - * #syscalls read
> - */
> -int read_all_buf(int fd, void *buf, size_t len)
> -{
> -	size_t left = len;
> -	char *p = buf;
> -
> -	while (left) {
> -		ssize_t rc;
> -
> -		assert(left <= len);
> -
> -		do
> -			rc = read(fd, p, left);
> -		while ((rc < 0) && errno == EINTR);
> -
> -		if (rc < 0)
> -			return -1;
> -
> -		if (rc == 0) {
> -			errno = ENODATA;
> -			return -1;
> -		}
> -
> -		p += rc;
> -		left -= rc;
> -	}
> -	return 0;
> -}
> -
>  /**
>   * read_remainder() - Read the tail of an IO vector from a file descriptor
>   * @fd:		File descriptor
> diff --git a/util.h b/util.h
> index dcb79afe..74227a68 100644
> --- a/util.h
> +++ b/util.h
> @@ -20,6 +20,7 @@
>  #include <net/ethernet.h>
>  
>  #include "log.h"
> +#include "pesto_util.h"
>  
>  #define VERSION_BLOB							       \
>  	VERSION "\n"							       \
> @@ -242,7 +243,6 @@ int write_file(const char *path, const char *buf);
>  intmax_t read_file_integer(const char *path, intmax_t fallback);
>  int write_all_buf(int fd, const void *buf, size_t len);
>  int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip);
> -int read_all_buf(int fd, void *buf, size_t len);
>  int read_remainder(int fd, const struct iovec *iov, size_t cnt, size_t skip);
>  void close_open_files(int argc, char **argv);
>  bool snprintf_check(char *str, size_t size, const char *format, ...);
> @@ -314,9 +314,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);
>  
>  /*

The rest looks good to me, maybe we could just keep it like it is while
building stuff on top of this patch and address the comments later, no
particular preference (not a big effort either way).

-- 
Stefano


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/5] treewide: Spell ASSERT() as assert()
  2026-03-17  0:02   ` Stefano Brivio
@ 2026-03-17  0:39     ` David Gibson
  2026-03-17  9:36       ` Stefano Brivio
  0 siblings, 1 reply; 13+ messages in thread
From: David Gibson @ 2026-03-17  0:39 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: passt-dev

[-- Attachment #1: Type: text/plain, Size: 2509 bytes --]

On Tue, Mar 17, 2026 at 01:02:34AM +0100, Stefano Brivio wrote:
> On Mon, 16 Mar 2026 16:46:28 +1100
> David Gibson <david@gibson.dropbear.id.au> wrote:
> 
> > +++ b/util.h
> > @@ -73,10 +73,14 @@ void abort_with_msg(const char *fmt, ...)
> >   * Therefore, avoid using the usual do while wrapper we use to force the macro
> >   * to act like a single statement requiring a ';'.
> >   */
> > -#define ASSERT_WITH_MSG(expr, ...)					\
> > +#define assert_with_msg(expr, ...)					\
> >  	((expr) ? (void)0 : abort_with_msg(__VA_ARGS__))
> > -#define ASSERT(expr)							\
> > -	ASSERT_WITH_MSG((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
> > +/* The standard library assert() hits our seccomp filter and dies before it can
> > + * actually print a message.  So, replace it with our own version.
> > + */
> > +#undef assert
> > +#define assert(expr)							\
> > +	assert_with_msg((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
> >  			__func__, __FILE__, __LINE__, STRINGIFY(expr))
> 
> While looking this up to make sure it's specified as a macro (it is,
> and this builds against musl as well), I realised that POSIX.1-2024
> says:
> 
>   https://pubs.opengroup.org/onlinepubs/9799919799/functions/assert.html
> 
>   Forcing a definition of the name NDEBUG, either from the compiler
>   command line or with the preprocessor control statement #define NDEBUG
>   ahead of the #include <assert.h> statement, shall stop assertions from
>   being compiled into the program.
> 
> ...so, I wonder, now that it's called assert(), should we define it as
> "do { } while(0)" #ifdef NDEBUG, for correctness (and maybe somebody
> has obscure usages for NDEBUG which we shouldn't sabotage)?

I like the idea in principle.  Actually implementing it turns out to
be kind of a pain in the arse, because if we actually try to compile
with -DNDEBUG then we get a much of warnings due to reaching the end
of functions (assert(0) stopped us otherwise) or unused variables
(they're only used in the assert expression or message).

A project for some other time, I think.

> This will conflict with "[PATCH v2 3/3] vu_common: Move iovec management
> into vu_collect()" by the way, but I'll take care of it, if it still
> conflicts by the time I merge it.
> 
> -- 
> Stefano
> 

-- 
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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 5/5] pesto: Introduce stub configuration interface and tool
  2026-03-17  0:02   ` Stefano Brivio
@ 2026-03-17  0:48     ` David Gibson
  2026-03-17  9:36       ` Stefano Brivio
  0 siblings, 1 reply; 13+ messages in thread
From: David Gibson @ 2026-03-17  0:48 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: passt-dev

[-- Attachment #1: Type: text/plain, Size: 31155 bytes --]

On Tue, Mar 17, 2026 at 01:02:46AM +0100, Stefano Brivio wrote:
> On Mon, 16 Mar 2026 16:46:29 +1100
> David Gibson <david@gibson.dropbear.id.au> wrote:
> 
> > Add a control socket to passt/pasta for updating configuration at runtime.
> > Add a tool (pesto) for communicating with the control socket.
> > 
> > For now this is a stub implementation that forms the connection and sends
> > some version information, but nothing more.
> > Example:
> >   ./pasta --debug --config-net -c /tmp/pasta.conf -t none
> >   ./pesto /tmp/pasta.conf
> > 
> > Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
> > [dwg: Based on an early draft from Stefano]
> > Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> > ---
> >  .gitignore   |   2 +
> >  Makefile     |  32 +++++++----
> >  conf.c       | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++-
> >  conf.h       |   2 +
> >  epoll_type.h |   4 ++
> >  passt.1      |   5 ++
> >  passt.c      |   8 +++
> >  passt.h      |   6 +++
> >  pesto.1      |  47 ++++++++++++++++
> >  pesto.c      | 111 ++++++++++++++++++++++++++++++++++++++
> >  pesto.h      |  34 ++++++++++++
> >  pesto_util.c |  62 +++++++++++++++++++++
> >  pesto_util.h |  19 +++++++
> >  util.c       |  38 -------------
> >  util.h       |   5 +-
> >  15 files changed, 469 insertions(+), 54 deletions(-)
> >  create mode 100644 pesto.1
> >  create mode 100644 pesto.c
> >  create mode 100644 pesto.h
> >  create mode 100644 pesto_util.c
> >  create mode 100644 pesto_util.h
> > 
> > diff --git a/.gitignore b/.gitignore
> > index 3c16adc7..3e40d9f7 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 fe016f30..a528b34a 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -39,21 +39,25 @@ FLAGS += -DDUAL_STACK_SOCKETS=$(DUAL_STACK_SOCKETS)
> >  
> >  PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c epoll_ctl.c \
> >  	flow.c fwd.c icmp.c igmp.c inany.c iov.c ip.c isolation.c lineread.c \
> > -	log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c pasta.c pcap.c \
> > -	pif.c repair.c tap.c tcp.c tcp_buf.c tcp_splice.c tcp_vu.c udp.c \
> > -	udp_flow.c udp_vu.c util.c vhost_user.c virtio.c vu_common.c
> > +	log.c mld.c ndp.c netlink.c migrate.c packet.c passt.c pasta.c \
> > +	pesto_util.c pcap.c pif.c repair.c tap.c tcp.c tcp_buf.c tcp_splice.c \
> > +	tcp_vu.c udp.c udp_flow.c udp_vu.c util.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 pesto_util.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
> >  
> > +PESTO_HEADERS = pesto.h pesto_util.h
> >  PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h epoll_ctl.h \
> >  	flow.h fwd.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 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
> > +	udp_internal.h udp_vu.h util.h vhost_user.h virtio.h vu_common.h \
> > +	$(PESTO_HEADERS)
> >  HEADERS = $(PASST_HEADERS) seccomp.h
> >  
> >  C := \#include <sys/random.h>\nint main(){int a=getrandom(0, 0, 0);}
> > @@ -74,9 +78,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
> > @@ -90,6 +94,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)
> >  
> > @@ -109,6 +116,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 \
> > @@ -118,7 +128,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
> >  
> > @@ -172,11 +182,11 @@ docs: README.md
> >  		done < README.md;					\
> >  	) > README.plain.md
> >  
> > -clang-tidy: $(PASST_SRCS)
> > +clang-tidy: $(PASST_SRCS) $(PESTO_SRCS)
> >  	clang-tidy $^ -- $(filter-out -pie,$(FLAGS) $(CFLAGS) $(CPPFLAGS)) \
> >  	           -DCLANG_TIDY_58992
> >  
> > -cppcheck: $(PASST_SRCS) $(HEADERS)
> > +cppcheck: $(PASST_SRCS) $(PESTO_SRCS) $(HEADERS)
> >  	if cppcheck --check-level=exhaustive /dev/null > /dev/null 2>&1; then \
> >  		CPPCHECK_EXHAUSTIVE="--check-level=exhaustive";		\
> >  	else								\
> > diff --git a/conf.c b/conf.c
> > index 940fb9e9..04f65b30 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -47,6 +47,9 @@
> >  #include "isolation.h"
> >  #include "log.h"
> >  #include "vhost_user.h"
> > +#include "epoll_ctl.h"
> > +#include "conf.h"
> > +#include "pesto.h"
> >  
> >  #define NETNS_RUN_DIR	"/run/netns"
> >  
> > @@ -894,6 +897,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"
> 
> --conf-path

Oops, fixed.
> 
> >  		"  -h, --help		Display this help message and exit\n"
> >  		"  --version		Show version and exit\n");
> >  
> > @@ -1425,6 +1429,17 @@ 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_conf = -1;
> 
> Note: we should remember to tackle your own comments from
> <aYqw6M2G9n-tv9Qm@zatzit>, eventually (not necessarily now, but fd_conf
> just reminded me).

Uh, don't remember what that mail was and I'm not immediately sure how
to search for it based on message-id.

> > +	if (*c->conf_path) {
> > +		c->fd_conf_listen = sock_unix(c->conf_path);
> > +		if (c->fd_conf_listen < 0) {
> > +			die_perror("Couldn't open control socket %s",
> > +				   c->conf_path);
> > +		}
> > +	} else {
> > +		c->fd_conf_listen = -1;
> > +	}
> >  }
> >  
> >  /**
> > @@ -1460,6 +1475,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_conf_listen < 0)
> > +		return;
> > +
> > +	if (listen(c->fd_conf_listen, 0))
> > +		die_perror("Couldn't listen on configuration socket");
> > +
> > +	ref.fd = c->fd_conf_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
> > @@ -1542,9 +1576,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";
> >  	char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 };
> >  	bool copy_addrs_opt = false, copy_routes_opt = false;
> > @@ -1808,6 +1843,13 @@ void conf(struct ctx *c, int argc, char **argv)
> >  
> >  			c->fd_tap = -1;
> >  			break;
> > +		case 'c':
> > +			ret = snprintf(c->conf_path, sizeof(c->conf_path), "%s",
> > +				       optarg);
> > +			if (ret <= 0 || ret >= (int)sizeof(c->sock_path))
> > +				die("Invalid configuration path: %s", optarg);
> > +			c->fd_conf_listen = c->fd_conf = -1;
> > +			break;
> >  		case 'F':
> >  			errno = 0;
> >  			fd_tap_opt = strtol(optarg, NULL, 0);
> > @@ -2242,4 +2284,108 @@ void conf(struct ctx *c, int argc, char **argv)
> >  
> >  	if (!c->quiet)
> >  		conf_print(c);
> > +
> > +	conf_sock_listen(c);
> > +}
> > +
> > +/**
> > + * 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)
> > +{
> > +	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;
> > +
> > +	if (events != EPOLLIN) {
> > +		err("Unexpected event 0x%04x on configuration socket", events);
> > +		return;
> > +	}
> > +
> > +	fd = accept4(c->fd_conf_listen, NULL, NULL, SOCK_NONBLOCK);
> > +	if (fd < 0) {
> > +		warn_perror("accept4() on configuration listening socket");
> > +		return;
> > +	}
> > +
> > +	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0)
> > +		warn_perror("Can't get configuration client credentials");
> > +
> > +	/* Another client is already connected: accept and close right away. */
> > +	if (c->fd_conf != -1) {
> > +		info("Discarding configuration client, PID %i", uc.pid);
> > +		goto fail;
> > +	}
> > +
> > +	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;
> > +	}
> > +
> > +	c->fd_conf = fd;
> > +	info("Accepted configuration client, PID %i", uc.pid);
> > +	if (!PESTO_PROTOCOL_VERSION) {
> > +		warn(
> > +"Warning: Using experimental unsupported configuration protocol");
> > +	}
> > +
> > +	return;
> > +
> > +fail:
> > +	close(fd);
> > +}
> > +
> > +/**
> > + * 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_conf, 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:
> > +	debug("Closing configuration socket");
> > +	epoll_ctl(c->epollfd, EPOLL_CTL_DEL, c->fd_conf, NULL);
> > +	close(c->fd_conf);
> > +	c->fd_conf = -1;
> >  }
> > diff --git a/conf.h b/conf.h
> > index b45ad746..16f97189 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 a90ffb67..061325aa 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 13e8df9d..32d70c8d 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)
> 
> I wouldn't even mention (EXPERIMENTAL). Pretty much all the options we
> added so far were.

Well, my plan was to remove that once we had a v1 of the update protocol.

> > +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 f84419c7..bc42ea33 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 b614bdf0..a49fbe1d 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
> > + * @conf_path:		Path for 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_conf_listen:	Listening configuration socket, if any
> > + * @fd_conf:		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
> > @@ -224,6 +227,7 @@ struct ctx {
> >  	int foreground;
> >  	int nofile;
> >  	char sock_path[UNIX_PATH_MAX];
> > +	char conf_path[UNIX_PATH_MAX];
> >  	char repair_path[UNIX_PATH_MAX];
> >  	char pcap[PATH_MAX];
> >  
> > @@ -241,6 +245,8 @@ struct ctx {
> >  	int epollfd;
> >  	int fd_tap_listen;
> >  	int fd_tap;
> > +	int fd_conf_listen;
> > +	int fd_conf;
> >  	int fd_repair_listen;
> >  	int fd_repair;
> >  	unsigned char our_tap_mac[ETH_ALEN];
> > diff --git a/pesto.1 b/pesto.1
> > new file mode 100644
> > index 00000000..fe2072f5
> > --- /dev/null
> > +++ b/pesto.1
> > @@ -0,0 +1,47 @@
> > +.\" SPDX-License-Identifier: GPL-2.0-or-later
> > +.\" Copyright Red Hat
> > +.\" Author: David Gibson <david@gibson.dropbear.id.au>
> > +.TH pesto 1
> > +
> > +.SH NAME
> > +.B pesto
> > +\- View or alter configuration of a running \fBpasst\fR(1) or
> > +\fBpasta\fR(1).
> 
> ...process/instance?

I had that initially, but I was trying to keep it to one line.

> > +
> > +.SH SYNOPSIS
> > +.B pesto
> > +\fIPATH\fR
> > +
> > +.SH DESCRIPTION
> > +
> > +.B pesto
> > +is an experimental client to view and update the port forwarding
> 
> I'd drop "experimental" from here as well, otherwise we'll never drop
> it until somebody asks if there's a "stable" solution in two years.

Again, my plan was to remove that once we have a series that
implements a suitable-for-release version of the protocol.  This man
page to require a bunch of updates as we add options, so I'm hoping
that will be enough not to forget.

> > +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 AUTHOR
> 
> Should be AUTHORS (just like in passt(1) -- I checked, back then, both
> section names seem to be common and they aren't really standardised
> anywhere).

Fixed.

> 
> > +
> > +Stefano Brivio <sbrivio@redhat.com>,
> > +David Gibson <david@gibson.dropbear.id.au>.
> > +
> > +.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 them and/or modify
> > +them 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 00000000..a158eadf
> > --- /dev/null
> > +++ b/pesto.c
> > @@ -0,0 +1,111 @@
> > +// 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 <sbrivio@redhat.com>
> > + */
> > +
> > +#include <arpa/inet.h>
> > +#include <sys/prctl.h>
> > +#include <sys/types.h>
> > +#include <sys/socket.h>
> > +#include <sys/un.h>
> > +#include <errno.h>
> > +#include <inttypes.h>
> > +#include <stdbool.h>
> > +#include <stddef.h>
> > +#include <stdint.h>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <limits.h>
> > +#include <unistd.h>
> > +
> > +#include <linux/audit.h>
> > +#include <linux/capability.h>
> > +#include <linux/filter.h>
> > +#include <linux/seccomp.h>
> > +
> > +#include "seccomp_pesto.h"
> > +#include "pesto_util.h"
> > +#include "pesto.h"
> > +
> > +#define die(...)							\
> > +	do {								\
> > +		FPRINTF(stderr, __VA_ARGS__);				\
> > +		FPRINTF(stderr, "\n");					\
> > +		exit(EXIT_FAILURE);					\
> > +	} while (0)
> > +
> > +/**
> > + * main() - Entry point and whole program with loop
> > + * @argc:	Argument count
> > + * @argv:	Arguments: socket path, operation, port specifiers
> > + *
> > + * Return: 0 on success, won't return on failure
> > + *
> > + * #syscalls:pesto connect write close exit_group fstat brk
> > + * #syscalls:pesto socket s390x:socketcall i686:socketcall
> > + * #syscalls:pesto recvfrom recvmsg arm:recv ppc64le:recv
> > + * #syscalls:pesto sendto sendmsg arm:send ppc64le:send
> > + */
> > +int main(int argc, char **argv)
> > +{
> > +	struct sockaddr_un a = { AF_UNIX, "" };
> > +	struct pesto_hello hello;
> > +	struct sock_fprog prog;
> > +	uint32_t s_version;
> > +	int ret, s;
> > +
> > +	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");
> > +
> > +	if (argc < 2)
> > +		die("Usage: %s CONTROLPATH", argv[0]);
> 
> While CONTROLPATH is a bit more descriptive, I think it's also harder
> to read compared to PATH, and after all it's not adding much.

Fair enough, done.

> > +
> > +	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
> > +		die("Failed to create AF_UNIX socket: %s", strerror(errno));
> > +
> > +	ret = snprintf(a.sun_path, sizeof(a.sun_path), "%s", argv[1]);
> > +	if (ret <= 0 || ret >= (int)sizeof(a.sun_path))
> > +		die("Invalid socket path \"%s\"", argv[1]);
> > +
> > +	ret = connect(s, (struct sockaddr *)&a, sizeof(a));
> > +	if (ret < 0) {
> > +		die("Failed to connect to %s: %s",
> > +		    a.sun_path, strerror(errno));
> > +	}
> > +
> > +	ret = read_all_buf(s, &hello, sizeof(hello));
> > +	if (ret < 0)
> > +		die("Couldn't read server greeting: %s", strerror(errno));
> > +
> > +	if (memcmp(hello.magic, PESTO_SERVER_MAGIC, sizeof(hello.magic)))
> > +		die("Bad magic number from server");
> > +
> > +	s_version = ntohl(hello.version);
> > +
> > +	if (s_version > PESTO_PROTOCOL_VERSION) {
> > +		die("Unknown server protocol version %"PRIu32" > %"PRIu32"\n",
> > +		    s_version, PESTO_PROTOCOL_VERSION);
> > +	}
> > +
> > +	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");
> > +	}
> > +
> > +	return 0;
> > +}
> > diff --git a/pesto.h b/pesto.h
> > new file mode 100644
> > index 00000000..92d4df3a
> > --- /dev/null
> > +++ b/pesto.h
> > @@ -0,0 +1,34 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + * Copyright Red Hat
> > + * Author: David Gibson <david@gibson.dropbear.id.au>
> > + *
> > + * Definitions and functions used by both client and server of the configuration
> > + * update protocol (pesto).
> 
> The answer is probably not obvious now, but eventually we should figure
> out what we want to call "pesto": for me it was just the client, from
> this description it sounds like the protocol as well (or Bad Acronym
> Sounds Indeed Logical?). Maybe we'll never need to name the protocol
> though.

Yeah, I've been using it a bit sloppily for both.  Or "pesto" (client)
versus "pesto protocol".

> > + */
> > +
> > +#ifndef PESTO_H
> > +#define PESTO_H
> > +
> > +#include <assert.h>
> > +#include <stdint.h>
> > +
> > +#define PESTO_SERVER_MAGIC	"pesto:s"
> 
> Regardless of the consideration above, shouldn't this be "basil:s"?

I really don't care :).

> > +
> > +/* Version 0 is reserved for unreleased / unsupported experimental versions */
> > +#define PESTO_PROTOCOL_VERSION	0
> > +
> > +/**
> > + * 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/pesto_util.c b/pesto_util.c
> > new file mode 100644
> > index 00000000..8e6b43d4
> > --- /dev/null
> > +++ b/pesto_util.c
> > @@ -0,0 +1,62 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +/* ASST - Plug A Simple Socket Transport
> 
> :)

Oops, fixed.

> > + *  for qemu/UNIX domain socket mode
> > + *
> > + * 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
> > + *
> > + * pesto_util.c - Helpers used by both passt/pasta and pesto
> 
> Maybe we should call this common.c? It's a bit counter-intuitive to
> look for read_all_buf() as used by passt into pesto_util.c. I'm not
> fond of common.c either, it's less descriptive, but also potentially
> less misleading.

Maybe?  I thought about common.c, but I wan't to distinguish (a) that
this is shared specifically between pesto and passt, not with,
e.g. passt-repair and (b) these are shared utility functions, rather
than shared logic specifically regarding the protocol (that's in
pesto.h).

I'll keep it for now, but I'm open to better suggestions if either of
us comes up with one.

> > + * Copyright Red Hat
> > + * Author: Stefano Brivio <sbrivio@redhat.com>
> > + * Author: David Gibson <david@gibson.dropbear.id.au>
> > + */
> > +
> > +#include <assert.h>
> > +#include <errno.h>
> > +#include <unistd.h>
> > +#include <sys/types.h>
> > +
> > +#include "pesto_util.h"
> > +
> > +/**
> > + * read_all_buf() - Fill a whole buffer from a file descriptor
> > + * @fd:		File descriptor
> > + * @buf:	Pointer to base of buffer
> > + * @len:	Length of buffer
> > + *
> > + * Return: 0 on success, -1 on error (with errno set)
> > + *
> > + * #syscalls read
> > + */
> > +int read_all_buf(int fd, void *buf, size_t len)
> > +{
> > +	size_t left = len;
> > +	char *p = buf;
> > +
> > +	while (left) {
> > +		ssize_t rc;
> > +
> > +		assert(left <= len);
> > +
> > +		do
> > +			rc = read(fd, p, left);
> > +		while ((rc < 0) && errno == EINTR);
> > +
> > +		if (rc < 0)
> > +			return -1;
> > +
> > +		if (rc == 0) {
> > +			errno = ENODATA;
> > +			return -1;
> > +		}
> > +
> > +		p += rc;
> > +		left -= rc;
> > +	}
> > +	return 0;
> > +}
> > diff --git a/pesto_util.h b/pesto_util.h
> > new file mode 100644
> > index 00000000..1fc5d9fd
> > --- /dev/null
> > +++ b/pesto_util.h
> > @@ -0,0 +1,19 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later
> > + * Copyright Red Hat
> > + * Author: David Gibson <david@gibson.dropbear.id.au>
> > + *
> > + * Helper functions used by both passt/pasta and pesto (the configuration update
> > + * client).
> > + */
> > +
> > +#ifndef PESTO_UTIL_H
> > +#define PESTO_UTIL_H
> > +
> > +#include <stddef.h>
> > +
> > +/* FPRINTF() intentionally silences cert-err33-c clang-tidy warnings */
> > +#define FPRINTF(f, ...)	(void)fprintf(f, __VA_ARGS__)
> > +
> > +int read_all_buf(int fd, void *buf, size_t len);
> > +
> > +#endif /* PESTO_UTIL_H */
> > diff --git a/util.c b/util.c
> > index 22318c00..1571850b 100644
> > --- a/util.c
> > +++ b/util.c
> > @@ -866,44 +866,6 @@ int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip)
> >  	return 0;
> >  }
> >  
> > -/**
> > - * read_all_buf() - Fill a whole buffer from a file descriptor
> > - * @fd:		File descriptor
> > - * @buf:	Pointer to base of buffer
> > - * @len:	Length of buffer
> > - *
> > - * Return: 0 on success, -1 on error (with errno set)
> > - *
> > - * #syscalls read
> > - */
> > -int read_all_buf(int fd, void *buf, size_t len)
> > -{
> > -	size_t left = len;
> > -	char *p = buf;
> > -
> > -	while (left) {
> > -		ssize_t rc;
> > -
> > -		assert(left <= len);
> > -
> > -		do
> > -			rc = read(fd, p, left);
> > -		while ((rc < 0) && errno == EINTR);
> > -
> > -		if (rc < 0)
> > -			return -1;
> > -
> > -		if (rc == 0) {
> > -			errno = ENODATA;
> > -			return -1;
> > -		}
> > -
> > -		p += rc;
> > -		left -= rc;
> > -	}
> > -	return 0;
> > -}
> > -
> >  /**
> >   * read_remainder() - Read the tail of an IO vector from a file descriptor
> >   * @fd:		File descriptor
> > diff --git a/util.h b/util.h
> > index dcb79afe..74227a68 100644
> > --- a/util.h
> > +++ b/util.h
> > @@ -20,6 +20,7 @@
> >  #include <net/ethernet.h>
> >  
> >  #include "log.h"
> > +#include "pesto_util.h"
> >  
> >  #define VERSION_BLOB							       \
> >  	VERSION "\n"							       \
> > @@ -242,7 +243,6 @@ int write_file(const char *path, const char *buf);
> >  intmax_t read_file_integer(const char *path, intmax_t fallback);
> >  int write_all_buf(int fd, const void *buf, size_t len);
> >  int write_remainder(int fd, const struct iovec *iov, size_t iovcnt, size_t skip);
> > -int read_all_buf(int fd, void *buf, size_t len);
> >  int read_remainder(int fd, const struct iovec *iov, size_t cnt, size_t skip);
> >  void close_open_files(int argc, char **argv);
> >  bool snprintf_check(char *str, size_t size, const char *format, ...);
> > @@ -314,9 +314,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);
> >  
> >  /*
> 
> The rest looks good to me, maybe we could just keep it like it is while
> building stuff on top of this patch and address the comments later, no
> particular preference (not a big effort either way).
> 
> -- 
> Stefano
> 

-- 
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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/5] treewide: Spell ASSERT() as assert()
  2026-03-17  0:39     ` David Gibson
@ 2026-03-17  9:36       ` Stefano Brivio
  0 siblings, 0 replies; 13+ messages in thread
From: Stefano Brivio @ 2026-03-17  9:36 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev

On Tue, 17 Mar 2026 11:39:42 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> On Tue, Mar 17, 2026 at 01:02:34AM +0100, Stefano Brivio wrote:
> > On Mon, 16 Mar 2026 16:46:28 +1100
> > David Gibson <david@gibson.dropbear.id.au> wrote:
> >   
> > > +++ b/util.h
> > > @@ -73,10 +73,14 @@ void abort_with_msg(const char *fmt, ...)
> > >   * Therefore, avoid using the usual do while wrapper we use to force the macro
> > >   * to act like a single statement requiring a ';'.
> > >   */
> > > -#define ASSERT_WITH_MSG(expr, ...)					\
> > > +#define assert_with_msg(expr, ...)					\
> > >  	((expr) ? (void)0 : abort_with_msg(__VA_ARGS__))
> > > -#define ASSERT(expr)							\
> > > -	ASSERT_WITH_MSG((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
> > > +/* The standard library assert() hits our seccomp filter and dies before it can
> > > + * actually print a message.  So, replace it with our own version.
> > > + */
> > > +#undef assert
> > > +#define assert(expr)							\
> > > +	assert_with_msg((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
> > >  			__func__, __FILE__, __LINE__, STRINGIFY(expr))  
> > 
> > While looking this up to make sure it's specified as a macro (it is,
> > and this builds against musl as well), I realised that POSIX.1-2024
> > says:
> > 
> >   https://pubs.opengroup.org/onlinepubs/9799919799/functions/assert.html
> > 
> >   Forcing a definition of the name NDEBUG, either from the compiler
> >   command line or with the preprocessor control statement #define NDEBUG
> >   ahead of the #include <assert.h> statement, shall stop assertions from
> >   being compiled into the program.
> > 
> > ...so, I wonder, now that it's called assert(), should we define it as
> > "do { } while(0)" #ifdef NDEBUG, for correctness (and maybe somebody
> > has obscure usages for NDEBUG which we shouldn't sabotage)?  
> 
> I like the idea in principle.  Actually implementing it turns out to
> be kind of a pain in the arse, because if we actually try to compile
> with -DNDEBUG then we get a much of warnings due to reaching the end
> of functions (assert(0) stopped us otherwise) or unused variables
> (they're only used in the assert expression or message).
> 
> A project for some other time, I think.

Well but it's just (probably harmless) warnings right? I tried with
this on top of your patch:

---
diff --git a/util.h b/util.h
index dcb79af..77b59bc 100644
--- a/util.h
+++ b/util.h
@@ -75,13 +75,18 @@ void abort_with_msg(const char *fmt, ...)
  */
 #define assert_with_msg(expr, ...)					\
 	((expr) ? (void)0 : abort_with_msg(__VA_ARGS__))
+
 /* The standard library assert() hits our seccomp filter and dies before it can
  * actually print a message.  So, replace it with our own version.
  */
 #undef assert
+#ifdef NDEBUG
+#define assert(expr)	do { } while(0)
+#else
 #define assert(expr)							\
 	assert_with_msg((expr), "ASSERTION FAILED in %s (%s:%d): %s",	\
 			__func__, __FILE__, __LINE__, STRINGIFY(expr))
+#endif
 
 #ifdef P_tmpdir
 #define TMPDIR		P_tmpdir
---

and 'CFLAGS="-DNDEBUG" make' gives me some stuff such as, for example:

util.c: In function ‘sock_l4_’:
util.c:90:14: warning: ‘proto’ may be used uninitialized [-Wmaybe-uninitialized]
   90 |         fd = socket(af, socktype, proto);
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~

because an invalid value for it isn't caught by assert(0) anymore, but
from a quick review I'd say it's all intended and implied because of the
missing assert() calls.

Sure, the output with NDEBUG is not pretty, but we won't be able to "fix"
most of that anyway. If somebody passes it as extra CFLAGS, I would expect
they know what they're doing. Nobody will build distribution packages or
anything "official" with it, I think.

Either way, should I go ahead and merge this (in the original version or
amended for NDEBUG, I'm fine with both)?

-- 
Stefano


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 5/5] pesto: Introduce stub configuration interface and tool
  2026-03-17  0:48     ` David Gibson
@ 2026-03-17  9:36       ` Stefano Brivio
  0 siblings, 0 replies; 13+ messages in thread
From: Stefano Brivio @ 2026-03-17  9:36 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev

On Tue, 17 Mar 2026 11:48:50 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> On Tue, Mar 17, 2026 at 01:02:46AM +0100, Stefano Brivio wrote:
>
> > [...]
> >
> > Note: we should remember to tackle your own comments from
> > <aYqw6M2G9n-tv9Qm@zatzit>, eventually (not necessarily now, but fd_conf
> > just reminded me).  
> 
> Uh, don't remember what that mail was and I'm not immediately sure how
> to search for it based on message-id.

No need to search, we use public-inbox, so:

  https://archives.passt.top/passt-dev/aYqw6M2G9n-tv9Qm@zatzit

(or, at least in claws-mail, select "Extended" in the search box, then
'i MSG_ID').

> > > [...]
> > >
> > > +++ b/pesto.1
> > > @@ -0,0 +1,47 @@
> > > +.\" SPDX-License-Identifier: GPL-2.0-or-later
> > > +.\" Copyright Red Hat
> > > +.\" Author: David Gibson <david@gibson.dropbear.id.au>
> > > +.TH pesto 1
> > > +
> > > +.SH NAME
> > > +.B pesto
> > > +\- View or alter configuration of a running \fBpasst\fR(1) or
> > > +\fBpasta\fR(1).  
> > 
> > ...process/instance?  
> 
> I had that initially, but I was trying to keep it to one line.

Ah, oops, I see. On the other hand:

\- Configure a running \fBpasst\fR(1) or \fBpasta\fR(1) instance.

fits on one line. Whatever, both are fine by me, i just thought you
forgot a word.

-- 
Stefano


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-03-17  9:36 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-16  5:46 [PATCH 0/5] RFC: Stub dynamic update implementation David Gibson
2026-03-16  5:46 ` [PATCH 1/5] Makefile: Use $^ to avoid duplication in static checker rules David Gibson
2026-03-16  5:46 ` [PATCH 2/5] doc: Fix formatting of (DEPRECATED) notes in man page David Gibson
2026-03-16  5:46 ` [PATCH 3/5] pif: Remove unused PIF_NAMELEN David Gibson
2026-03-16  5:46 ` [PATCH 4/5] treewide: Spell ASSERT() as assert() David Gibson
2026-03-17  0:02   ` Stefano Brivio
2026-03-17  0:39     ` David Gibson
2026-03-17  9:36       ` Stefano Brivio
2026-03-16  5:46 ` [PATCH 5/5] pesto: Introduce stub configuration interface and tool David Gibson
2026-03-17  0:02   ` Stefano Brivio
2026-03-17  0:48     ` David Gibson
2026-03-17  9:36       ` Stefano Brivio
2026-03-17  0:02 ` [PATCH 0/5] RFC: Stub dynamic update implementation Stefano Brivio

Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).