public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: passt-dev@passt.top
Subject: [PATCH 07/11] test: Add nsholder utility
Date: Fri, 02 Sep 2022 12:14:56 +1000	[thread overview]
Message-ID: <20220902021500.25358-8-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20220902021500.25358-1-david@gibson.dropbear.id.au>

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

In our test scripts we need to do some ugly parsing of /proc and/or pstree
output in order to get the PIDs of processes running in namespaces so that
we can connect to those namespaces with nsenter or pasta.

This is actually a pretty tricky problem with standard tools.  To determine
the PID from the outside of the namespace we need to know how the process
of interest is related to the unshare or pasta process (child? one of
several children? grandchild?) as well as then parsing /proc or ps output.
This is slightly awkward now, and will get worse with future changes I'd
like to make to have processes are dispatched.

The obvious solution would be to have the process of interest (which we
control) report its own PID, but that doesn't work easily, because it is in
a PID namepace and sees only its local PID not the global PID we need to
address it from outside.

To handle this, add a small custom tool, "nsholder".  This takes a path
and a mode parameter.  In "hold" mode it will create a unix domain socket
bound to the path and listening.  In "pid" mode it will get the "hold"ing
process's pid via the unix socket using SO_PEERCRED, which translates
between PID namespaces.  In "stop" mode it will send a message to the
socket causing the "hold"ing process to clean up and exit.

Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au>
---
 test/.gitignore |   1 +
 test/Makefile   |   8 +++-
 test/nsholder.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 125 insertions(+), 1 deletion(-)
 create mode 100644 test/nsholder.c

diff --git a/test/.gitignore b/test/.gitignore
index 129ddc0..d477a42 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -10,3 +10,4 @@ QEMU_EFI.fd
 *.start
 *.stop
 *.js
+nsholder
diff --git a/test/Makefile b/test/Makefile
index f11c4b5..e0dc7ac 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -56,10 +56,13 @@ DOWNLOAD_ASSETS = mbuto \
 	$(DEBIAN_IMGS) $(FEDORA_IMGS) $(OPENSUSE_IMGS) $(UBUNTU_IMGS)
 LOCAL_ASSETS = mbuto.img QEMU_EFI.fd \
 	$(DEBIAN_IMGS:%=prepared-%) $(FEDORA_IMGS:%=prepared-%) \
-	$(UBUNTU_NEW_IMGS:%=prepared-%)
+	$(UBUNTU_NEW_IMGS:%=prepared-%) \
+	nsholder
 
 ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
 
+CFLAGS = -Wall -Werror
+
 assets: $(ASSETS)
 
 mbuto:
@@ -68,6 +71,9 @@ mbuto:
 mbuto.img: passt.mbuto mbuto
 	./mbuto/mbuto -p ./$< -c lz4 -f $@
 
+nsholder: nsholder.c
+	$(CC) $(CFLAGS) -o $@ $^
+
 QEMU_EFI.fd:
 	./find-arm64-firmware.sh $@
 
diff --git a/test/nsholder.c b/test/nsholder.c
new file mode 100644
index 0000000..bfe3611
--- /dev/null
+++ b/test/nsholder.c
@@ -0,0 +1,117 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <linux/un.h>
+
+#define die(...)				\
+	do {					\
+		fprintf(stderr, __VA_ARGS__);	\
+		exit(1);			\
+	} while (0)
+
+static void usage(void)
+{
+	die("Usage: holder <socket path> hold|pid\n");
+}
+
+static void hold(int fd, const struct sockaddr_un *addr)
+{
+	int rc;
+
+	rc = bind(fd, (struct sockaddr *)addr, sizeof(*addr));
+	if (rc < 0)
+		die("bind(): %s\n", strerror(errno));
+
+	rc = listen(fd, 0);
+	if (rc < 0)
+		die("listen(): %s\n", strerror(errno));
+
+	printf("nsholder: local PID=%d  local UID=%d  local GID=%d\n",
+	       getpid(), getuid(), getgid());
+	do {
+		int afd = accept(fd, NULL, NULL);
+		char buf;
+
+		if (afd < 0)
+			die("accept(): %s\n", strerror(errno));
+
+		rc = read(afd, &buf, sizeof(buf));
+		if (rc < 0)
+			die("read(): %s\n", strerror(errno));
+	} while (rc == 0);
+
+	unlink(addr->sun_path);
+}
+
+static void pid(int fd, const struct sockaddr_un *addr)
+{
+	int rc;
+	struct ucred peercred;
+	socklen_t optlen = sizeof(peercred);
+
+	do {
+		rc = connect(fd, (struct sockaddr *)addr, sizeof(*addr));
+		if (rc < 0 && errno != ENOENT && errno != ECONNREFUSED)
+			die("connect(): %s\n", strerror(errno));
+	} while (rc < 0);
+
+	rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
+			&peercred, &optlen);
+	if (rc < 0)
+		die("getsockopet(SO_PEERCRED): %s\n", strerror(errno));
+
+	close(fd);
+
+	printf("%d\n", peercred.pid);
+}
+
+static void stop(int fd, const struct sockaddr_un *addr)
+{
+	int rc;
+	char buf = 'Q';
+
+	rc = connect(fd, (struct sockaddr *)addr, sizeof(*addr));
+	if (rc < 0)
+		die("connect(): %s\n", strerror(errno));
+
+	rc = write(fd, &buf, sizeof(buf));
+	if (rc < 0)
+		die("write(): %s\n",  strerror(errno));
+
+	close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+	int fd;
+	const char *sockname;
+	struct sockaddr_un sockaddr = {
+		.sun_family = AF_UNIX,
+	};
+
+	if (argc != 3)
+		usage();
+
+	sockname = argv[1];
+	strncpy(sockaddr.sun_path, sockname, UNIX_PATH_MAX);
+
+	fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+	if (fd < 0)
+		die("socket(): %s\n", strerror(errno));
+
+	if (strcmp(argv[2], "hold") == 0)
+		hold(fd, &sockaddr);
+	else if (strcmp(argv[2], "pid") == 0)
+		pid(fd, &sockaddr);
+	else if (strcmp(argv[2], "stop") == 0)
+		stop(fd, &sockaddr);
+	else
+		usage();
+
+	exit(0);
+}
-- 
@@ -0,0 +1,117 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <linux/un.h>
+
+#define die(...)				\
+	do {					\
+		fprintf(stderr, __VA_ARGS__);	\
+		exit(1);			\
+	} while (0)
+
+static void usage(void)
+{
+	die("Usage: holder <socket path> hold|pid\n");
+}
+
+static void hold(int fd, const struct sockaddr_un *addr)
+{
+	int rc;
+
+	rc = bind(fd, (struct sockaddr *)addr, sizeof(*addr));
+	if (rc < 0)
+		die("bind(): %s\n", strerror(errno));
+
+	rc = listen(fd, 0);
+	if (rc < 0)
+		die("listen(): %s\n", strerror(errno));
+
+	printf("nsholder: local PID=%d  local UID=%d  local GID=%d\n",
+	       getpid(), getuid(), getgid());
+	do {
+		int afd = accept(fd, NULL, NULL);
+		char buf;
+
+		if (afd < 0)
+			die("accept(): %s\n", strerror(errno));
+
+		rc = read(afd, &buf, sizeof(buf));
+		if (rc < 0)
+			die("read(): %s\n", strerror(errno));
+	} while (rc == 0);
+
+	unlink(addr->sun_path);
+}
+
+static void pid(int fd, const struct sockaddr_un *addr)
+{
+	int rc;
+	struct ucred peercred;
+	socklen_t optlen = sizeof(peercred);
+
+	do {
+		rc = connect(fd, (struct sockaddr *)addr, sizeof(*addr));
+		if (rc < 0 && errno != ENOENT && errno != ECONNREFUSED)
+			die("connect(): %s\n", strerror(errno));
+	} while (rc < 0);
+
+	rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
+			&peercred, &optlen);
+	if (rc < 0)
+		die("getsockopet(SO_PEERCRED): %s\n", strerror(errno));
+
+	close(fd);
+
+	printf("%d\n", peercred.pid);
+}
+
+static void stop(int fd, const struct sockaddr_un *addr)
+{
+	int rc;
+	char buf = 'Q';
+
+	rc = connect(fd, (struct sockaddr *)addr, sizeof(*addr));
+	if (rc < 0)
+		die("connect(): %s\n", strerror(errno));
+
+	rc = write(fd, &buf, sizeof(buf));
+	if (rc < 0)
+		die("write(): %s\n",  strerror(errno));
+
+	close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+	int fd;
+	const char *sockname;
+	struct sockaddr_un sockaddr = {
+		.sun_family = AF_UNIX,
+	};
+
+	if (argc != 3)
+		usage();
+
+	sockname = argv[1];
+	strncpy(sockaddr.sun_path, sockname, UNIX_PATH_MAX);
+
+	fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+	if (fd < 0)
+		die("socket(): %s\n", strerror(errno));
+
+	if (strcmp(argv[2], "hold") == 0)
+		hold(fd, &sockaddr);
+	else if (strcmp(argv[2], "pid") == 0)
+		pid(fd, &sockaddr);
+	else if (strcmp(argv[2], "stop") == 0)
+		stop(fd, &sockaddr);
+	else
+		usage();
+
+	exit(0);
+}
-- 
2.37.2


  parent reply	other threads:[~2022-09-02  2:14 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-09-02  2:14 [PATCH 00/11] Improve command dispatch in test scripts David Gibson
2022-09-02  2:14 ` [PATCH 01/11] test: Correctly match "background" with "wait" commands David Gibson
2022-09-02  2:14 ` [PATCH 02/11] test: Context execution helpers David Gibson
2022-09-02  2:14 ` [PATCH 03/11] test: Allow a tmux pane to watch commands executed in contexts David Gibson
2022-09-02  2:14 ` [PATCH 04/11] test: Integration of old-style pane execution and new context execution David Gibson
2022-09-02  2:14 ` [PATCH 05/11] test: Issue host commands via context for most tests David Gibson
2022-09-02  2:14 ` [PATCH 06/11] test: Use new-style contexts for passt pane in the pasta and passt tests David Gibson
2022-09-02  2:14 ` David Gibson [this message]
2022-09-02  2:14 ` [PATCH 08/11] test: Extend context system to run commands in namespace for pasta tests David Gibson
2022-09-02  2:14 ` [PATCH 09/11] test: Use context system for guest commands David Gibson
2022-09-02  2:14 ` [PATCH 10/11] test: Use context system for two_guests tests David Gibson
2022-09-02  2:15 ` [PATCH 11/11] test: Use new-style command issue for passt_in_ns tests David Gibson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220902021500.25358-8-david@gibson.dropbear.id.au \
    --to=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).