1
0
mirror of https://passt.top/passt synced 2024-06-30 06:52:40 +00:00

test: Add nsholder utility

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@gibson.dropbear.id.au>
This commit is contained in:
David Gibson 2022-09-12 20:56:20 +10:00 committed by Stefano Brivio
parent 2f87265d8b
commit a8c32c85d5
3 changed files with 147 additions and 1 deletions

1
test/.gitignore vendored
View File

@ -10,3 +10,4 @@ QEMU_EFI.fd
*.start
*.stop
*.js
nsholder

View File

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

139
test/nsholder.c Normal file
View File

@ -0,0 +1,139 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
/* nsholder - maintain a namespace to be entered by other processes
*
* Copyright Red Hat
* Author: David Gibson <david@gibson.dropbear.id.au>
*
* Can run in 3 modes:
*
* nsholder <path> hold
* Designed to be run inside a namespace, opens a Unix domain
* control socket at <path> and waits until instructed to stop
* with "nsholder <path> stop"
* nsholder <path> pid
* Prints the PID of the nsholder hold process with control
* socket <path>. This is given in the PID namespace where
* nsholder pid is executed, not the one where nsholder hold is
* running
* nsholder <path> stop
* Instruct the nsholder hold with control socket at <path> to exit.
*/
#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);
}