From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=tamu.edu Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=tamu.edu header.i=@tamu.edu header.a=rsa-sha256 header.s=ppae6d7b header.b=j1tliAB0; dkim-atps=neutral Received: from mx0b-00178102.pphosted.com (mx0b-00178102.pphosted.com [148.163.139.245]) by passt.top (Postfix) with ESMTPS id 410345A0269 for ; Thu, 16 Jul 2026 21:03:06 +0200 (CEST) Received: from pps.filterd (m0231226.ppops.net [127.0.0.1]) by mx0b-00178102.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 66GI8Btv3231249; Thu, 16 Jul 2026 14:03:01 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tamu.edu; h=cc :content-transfer-encoding:date:from:message-id:mime-version :subject:to; s=ppae6d7b; bh=Hv+H0kpSTSTx4ztT4PPAT4kpD34IquD+ncCj 2BnyTQI=; b=j1tliAB00B8Y1xlZO8fGaWQtySji86R532RC7ndpdt1iwaEdP2JR gehXw1X4jjbsTEx/abYuy9fEO/VpA0K4QrpD2I3ngAWjByofHA4oSw+rOyspUqxg PDvQjakmRXOjXu21Euy96I9cSpoxTJ8Ue4oGcyGTWbbnCL9p5yM7A35OS9lwzU5G DSQgh9IRLjpVV9EJgIfbCc9bN/08Zb/YTCN8mPjarlhScB+tzZcEDVBRtsZwH0OO R1/ogBbYwAgxrjTUWHjpXmJmcH8yvTz1IFGcbX+PvKy0vTpw5GAaBP25XNpnakzO 3IX20ya3A3C9TqkA6GxHVP/oZlJJL1JWgQ== Received: from bespin.localdomain ([128.194.2.191]) by mx0b-00178102.pphosted.com (PPS) with ESMTPS id 4feq06vt3s-1 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Thu, 16 Jul 2026 14:03:00 -0500 (CDT) Received: by bespin.localdomain (Postfix, from userid 1000) id 4329D65C5; Thu, 16 Jul 2026 14:03:00 -0500 (CDT) From: Richard Lawrence To: passt-dev@passt.top Subject: [PATCH] pasta: Add --pass-fds option to preserve specific file descriptors Date: Thu, 16 Jul 2026 14:03:00 -0500 Message-ID: <20260716190300.57944-1-rlawrence@tamu.edu> X-Mailer: git-send-email 2.52.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Proofpoint-Reinject: loops=2 maxloops=12 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNzE2MDE5NSBTYWx0ZWRfXyQTXASi88bcQ rfhaqb+vQ5k55u7PQKzVfHDzTLsO5PeypefnXn7lf7iNmUSvcQOuP7qgqXQ6oJyqXtDot1pg3i5 7re2GdpGIhaAq1ei19cwcmHKFp5X5gU+B2gal4cftTPs6QbzeAcL/f9WdUcbLQdxSfzrKvmKNNz WSBnzHPfFHsDmlW7wkJbpCl+zC5yShELng6DYbr9Qx4+gX0H7LqIWRtudqSxDlmcumyE7cLs6oj bvD0C8BCvpwOigdWT3xHoeuoT68NmpUO3grruYmNZb8xNghrr8cxBxyK7uHl/ta89nFNXrYVFdP DCnWmFsvonWtWTKSDa4bm7o3cJVEwNLGuwOZ8s8pSHWaQw/TomyLzqTAkgvbC+dZeev5n+hycmL lllNc74/OzFUrhhumDV6LcBQKGqhU0Ej/YW0/jD7aOR3wUu5JMxTXayk4K3e5F09f7jYiKf+6lb kKWJHhQmGiBAG61E8ag== X-Proofpoint-Spam-Info: AW1haW4tMjYwNzE2MDE5NSBTYWx0ZWRfX7hj23KMXD2j0 zRsJyR1Xd693JEtYh0aBBroud+4ZOECm4M9tPxlyt+H+U2SUeETudtMaO4lWkerwynQP7anKxyX QucpVx9ZiZ7lJta/vMf2F8wufSCflhI= X-Proofpoint-ORIG-GUID: q0XwZt4XgGBF1ppQE0zFleshgHCpEmVH X-Authority-Analysis: v=2.4 cv=O9sJeh9W c=1 sm=1 tr=0 ts=6a592ae4 cx=c_pps a=RM/V1YFJ8wA4X+FDJsk8mQ==:117 a=RM/V1YFJ8wA4X+FDJsk8mQ==:17 a=RAioF0-LDSMA:10 a=x7bEGLp0ZPQA:10 a=VkNPw1HP01LnGYTKEx00:22 a=HuL5yKgYSaDc2Nh3iM7q:22 a=7DFSJXxVznJstVW2-kzw:22 a=NEAV23lmAAAA:8 a=4AHMEiAgO-o-W8xcgNIA:9 X-Proofpoint-GUID: sIJ8nEY2yuVcl4YwjUZ9SzFsNTdp4epz X-MailFrom: rlawrence@bespin.localdomain X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation Message-ID-Hash: NXUZRJH5Y4D4PXPLH7GZVXPETGILMNBD X-Message-ID-Hash: NXUZRJH5Y4D4PXPLH7GZVXPETGILMNBD X-Mailman-Approved-At: Thu, 16 Jul 2026 21:39:56 +0200 CC: jbash@jbash.com, Richard Lawrence X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Introduce a new long-only command line option '--pass-fds' for pasta mode. This option accepts a comma-separated list of file descriptor numbers. It parses, validates, sorts, and deduplicates these file descriptors, and then skips closing them by calling close_range() on the gaps between them inside close_open_files(). The option is documented in the usage help and the passt.1 man page. Co-authored-by: rarensu <40872193+rarensu@users.noreply.github.com> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Signed-off-by: Richard Lawrence --- conf.c | 9 +++++- passt.1 | 5 ++++ util.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/conf.c b/conf.c index 5b6cc2b..0032d02 100644 --- a/conf.c +++ b/conf.c @@ -732,7 +732,9 @@ pasta_opts: " Don't copy all addresses to namespace\n" " --ns-mac-addr ADDR Set MAC address on tap interface\n" " --no-splice Disable inbound socket splicing\n" - " --splice-only Only enable loopback forwarding\n"); + " --splice-only Only enable loopback forwarding\n" + " --pass-fds FDS Comma-separated list of fds to pass to\n" + " the spawned command\n"); passt_exit(status); } @@ -1316,6 +1318,7 @@ void conf(struct ctx *c, int argc, char **argv) {"stats", required_argument, NULL, 31 }, {"conf-path", required_argument, NULL, 'c' }, {"chroot-fallback", no_argument, NULL, 32 }, + {"pass-fds", required_argument, NULL, 33 }, { 0 }, }; const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; @@ -1557,6 +1560,10 @@ void conf(struct ctx *c, int argc, char **argv) case 32: c->chroot_fallback = true; break; + case 33: + if (c->mode != MODE_PASTA) + die("--pass-fds is for pasta mode only"); + break; case 'd': c->debug = 1; c->quiet = 0; diff --git a/passt.1 b/passt.1 index 1570f6a..93c9b24 100644 --- a/passt.1 +++ b/passt.1 @@ -755,6 +755,11 @@ of local traffic in pasta\fR in the \fBNOTES\fR for more details. Do not create a tap device in the namespace. In this mode, \fIpasta\fR only forwards loopback traffic between namespaces. +.TP +.BR \-\-pass\-fds " " \fIfds\fR +Pass a comma-separated list of file descriptors to the spawned command. +These file descriptors will be kept open. + .SH EXAMPLES .SS \fBpasta diff --git a/util.c b/util.c index 4bc5d6f..c73fd74 100644 --- a/util.c +++ b/util.c @@ -932,29 +932,102 @@ const char *str_ee_origin(const struct sock_extended_err *ee) void close_open_files(int argc, char **argv) { const struct option optfd[] = { { "fd", required_argument, NULL, 'F' }, + { "pass-fds", required_argument, NULL, 33 }, { 0 }, }; long fd = -1; - int name, rc; + const char *pass_fds_arg = NULL; + int name, rc = 0; + int old_opterr = opterr; + opterr = 0; do { name = getopt_long(argc, argv, "-:F:", optfd, NULL); if (name == 'F') fd = conf_tap_fd(optarg); + else if (name == 33) + pass_fds_arg = optarg; } while (name != -1); + opterr = old_opterr; - if (fd == -1) { - rc = close_range(STDERR_FILENO + 1, ~0U, CLOSE_RANGE_UNSHARE); - } else if (fd == STDERR_FILENO + 1) { /* Still a single range */ - rc = close_range(STDERR_FILENO + 2, ~0U, CLOSE_RANGE_UNSHARE); - } else { - rc = close_range(STDERR_FILENO + 1, fd - 1, - CLOSE_RANGE_UNSHARE); - if (!rc) - rc = close_range(fd + 1, ~0U, CLOSE_RANGE_UNSHARE); + #define MAX_PASS_FDS 1024 + unsigned int keep_fds[MAX_PASS_FDS]; + int keep_fds_cnt = 0; + + if (fd != -1 && fd > STDERR_FILENO) { + keep_fds[keep_fds_cnt++] = (unsigned int)fd; + } + + if (pass_fds_arg) { + const char *p = pass_fds_arg; + while (*p) { + char *endptr; + unsigned long val = strtoul(p, &endptr, 10); + if (p == endptr) { + die("Invalid --pass-fds option: %s", pass_fds_arg); + } + if (val > INT_MAX || val <= STDERR_FILENO) { + die("Invalid file descriptor in --pass-fds: %lu", val); + } + if (keep_fds_cnt >= MAX_PASS_FDS) { + die("Too many file descriptors in --pass-fds"); + } + keep_fds[keep_fds_cnt++] = (unsigned int)val; + if (*endptr == ',') { + p = endptr + 1; + if (*p == '\0') { + die("Invalid --pass-fds option: trailing comma"); + } + } else if (*endptr == '\0') { + p = endptr; + } else { + die("Invalid character in --pass-fds option: %s", pass_fds_arg); + } + } } + /* Sort keep_fds in ascending order */ + for (int i = 1; i < keep_fds_cnt; i++) { + unsigned int key = keep_fds[i]; + int j = i - 1; + while (j >= 0 && keep_fds[j] > key) { + keep_fds[j + 1] = keep_fds[j]; + j--; + } + keep_fds[j + 1] = key; + } + + /* Remove duplicates */ + int write_idx = 0; + for (int i = 0; i < keep_fds_cnt; i++) { + if (i == 0 || keep_fds[i] != keep_fds[i - 1]) { + keep_fds[write_idx++] = keep_fds[i]; + } + } + keep_fds_cnt = write_idx; + + /* Close ranges */ + unsigned int current = STDERR_FILENO + 1; + for (int i = 0; i < keep_fds_cnt; i++) { + unsigned int f = keep_fds[i]; + if (current < f) { + int call_rc = close_range(current, f - 1, CLOSE_RANGE_UNSHARE); + if (call_rc != 0) + rc = call_rc; + } + current = f + 1; + } + if (keep_fds_cnt == 0 || keep_fds[keep_fds_cnt - 1] < ~0U) { + int call_rc = close_range(current, ~0U, CLOSE_RANGE_UNSHARE); + if (call_rc != 0) + rc = call_rc; + } + + /* Note: We assume that the parent process did not set FD_CLOEXEC on the + * passed file descriptors, so we do not explicitly clear it here. + */ + if (rc) { if (errno == ENOSYS || errno == EINVAL) { /* This probably means close_range() or the -- 2.52.0