]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
nsenter: Provide an option to join target process's socket net namespace
authorDmitry Safonov <0x7f454c46@gmail.com>
Fri, 13 Sep 2024 22:29:55 +0000 (23:29 +0100)
committerDmitry Safonov <0x7f454c46@gmail.com>
Mon, 23 Sep 2024 21:59:26 +0000 (22:59 +0100)
The network namespace of a socket can be different from the target
process. Previously there were some userspace issues where a
net-namespace was held alive by a socket leak. For this purpose Arista's
linux kernel has a patch to provide socket => netns map by procfs pid/fd
directory links.

Add nsenter option to join the network namespace of a target process'
socket.

Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com>
configure.ac
include/pidfd-utils.h
sys-utils/nsenter.1.adoc
sys-utils/nsenter.c

index 0ca2ebcf77247552a30fcccb532be1c52e5ae4ec..c18802e9e93cf1532fae39ce1a9f3d7b93b774d8 100644 (file)
@@ -628,6 +628,7 @@ AC_CHECK_FUNCS([ \
        ntp_gettime \
        open_tree \
        personality \
+       pidfd_getfd \
        pidfd_open \
        pidfd_send_signal \
        posix_fadvise \
@@ -682,6 +683,7 @@ AS_IF([test "x$ul_cv_syscall_setns" = xno], [
    have_setns_syscall="no"
 ])
 
+UL_CHECK_SYSCALL([pidfd_getfd])
 UL_CHECK_SYSCALL([pidfd_open])
 UL_CHECK_SYSCALL([pidfd_send_signal])
 UL_CHECK_SYSCALL([close_range])
index 4ac9f79d99f73fd1a680cdbd1668bbfb6aa0a566..d43bad26fe26214444348822c58087ada97e3426 100644 (file)
@@ -31,6 +31,13 @@ static inline int pidfd_open(pid_t pid, unsigned int flags)
 }
 #  endif
 
+#  ifndef HAVE_PIDFD_GETFD
+static inline int pidfd_getfd(int pidfd, int targetfd, unsigned int flags)
+{
+       return syscall(SYS_pidfd_getfd, pidfd, targetfd, flags);
+}
+#  endif
+
 #  define UL_HAVE_PIDFD 1
 
 # endif        /* SYS_pidfd_send_signal */
@@ -52,6 +59,14 @@ static inline int pidfd_open(pid_t pid __attribute__((unused)),
        errno = ENOSYS;
        return -1;
 }
+
+static inline int pidfd_getfd(int pidfd __attribute__((unused)),
+                             int targetfd __attribute__((unused)),
+                             unsigned int flags __attribute__((unused)))
+{
+       errno = ENOSYS;
+       return -1;
+}
 #endif
 
 #endif /* UTIL_LINUX_PIDFD_UTILS */
index 58dd125482507e62f4466cc9934d72d673e2021d..31b5c4d7db5d2962761d17c5f38eced81c0f1695 100644 (file)
@@ -91,6 +91,9 @@ Enter the IPC namespace. If no file is specified, enter the IPC namespace of the
 *-n*, *--net*[=_file_]::
 Enter the network namespace. If no file is specified, enter the network namespace of the target process. If _file_ is specified, enter the network namespace specified by _file_.
 
+*-N*, *--net-socket* _fd_::
+Enter the network namespace of the target process's socket. It requires *--target* process specified.
+
 *-p*, *--pid*[=_file_]::
 Enter the PID namespace. If no file is specified, enter the PID namespace of the target process. If _file_ is specified, enter the PID namespace specified by _file_.
 
index 0f5babaada42cc183f0d8484f6dee4fb8d20caff..eb41149e0c70e6744f3d285e49f6a2fde9e407fe 100644 (file)
@@ -25,6 +25,7 @@
 #include <sys/statfs.h>
 
 #include <sys/ioctl.h>
+#include <linux/sockios.h>
 #ifdef HAVE_LINUX_NSFS_H
 # include <linux/nsfs.h>
 #endif
@@ -49,6 +50,7 @@
 #include "caputils.h"
 #include "statfs_magic.h"
 #include "pathnames.h"
+#include "pidfd-utils.h"
 
 static struct namespace_file {
        int nstype;
@@ -92,6 +94,7 @@ static void __attribute__((__noreturn__)) usage(void)
        fputs(_(" -u, --uts[=<file>]     enter UTS namespace (hostname etc)\n"), out);
        fputs(_(" -i, --ipc[=<file>]     enter System V IPC namespace\n"), out);
        fputs(_(" -n, --net[=<file>]     enter network namespace\n"), out);
+       fputs(_(" -N, --net-socket <fd>  enter socket's network namespace (use with --target)\n"), out);
        fputs(_(" -p, --pid[=<file>]     enter pid namespace\n"), out);
        fputs(_(" -C, --cgroup[=<file>]  enter cgroup namespace\n"), out);
        fputs(_(" -U, --user[=<file>]    enter user namespace\n"), out);
@@ -189,6 +192,40 @@ static void open_namespace_fd(int nstype, const char *path)
        assert(nsfile->nstype);
 }
 
+static void open_target_sk_netns(int pid, int sock_fd)
+{
+       struct namespace_file *nsfile;
+       struct stat sb;
+       int pidfd, sk, nsfd;
+
+       for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
+               if (nsfile->nstype == CLONE_NEWNET)
+                       break;
+       }
+       assert(nsfile->nstype);
+
+       pidfd = pidfd_open(pid, 0);
+       if (pidfd < 0)
+               err(EXIT_FAILURE, _("failed to pidfd_open() for %d"), pid);
+
+       sk = pidfd_getfd(pidfd, sock_fd, 0);
+       if (sk < 0)
+               err(EXIT_FAILURE, _("pidfd_getfd(%d, %u)"), pidfd, sock_fd);
+
+       if (fstat(sk, &sb) < 0)
+               err(EXIT_FAILURE, _("fstat(%d)"), sk);
+
+       nsfd = ioctl(sk, SIOCGSKNS);
+       if (nsfd < 0)
+               err(EXIT_FAILURE, _("ioctl(%d, SIOCGSKNS)"), sk);
+
+       if (nsfile->fd >= 0)
+               close(nsfile->fd);
+       nsfile->fd = nsfd;
+       close(sk);
+       close(pidfd);
+}
+
 static int get_ns_ino(const char *path, ino_t *ino)
 {
        struct stat st;
@@ -329,6 +366,7 @@ int main(int argc, char *argv[])
                { "uts", optional_argument, NULL, 'u' },
                { "ipc", optional_argument, NULL, 'i' },
                { "net", optional_argument, NULL, 'n' },
+               { "net-socket", required_argument, NULL, 'N' },
                { "pid", optional_argument, NULL, 'p' },
                { "user", optional_argument, NULL, 'U' },
                { "cgroup", optional_argument, NULL, 'C' },
@@ -365,6 +403,7 @@ int main(int argc, char *argv[])
        uid_t uid = 0;
        gid_t gid = 0;
        int keepcaps = 0;
+       int sock_fd = -1;
        struct ul_env_list *envls;
 #ifdef HAVE_LIBSELINUX
        bool selinux = 0;
@@ -376,7 +415,7 @@ int main(int argc, char *argv[])
        close_stdout_atexit();
 
        while ((c =
-               getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::ecFZ",
+               getopt_long(argc, argv, "+ahVt:m::u::i::n::N:p::C::U::T::S:G:r::w::W::ecFZ",
                            longopts, NULL)) != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
@@ -413,6 +452,10 @@ int main(int argc, char *argv[])
                        else
                                namespaces |= CLONE_NEWNET;
                        break;
+               case 'N':
+                       sock_fd = str2num_or_err(optarg, 10, _("failed to parse file descriptor"),
+                                                0, INT_MAX);
+                       break;
                case 'p':
                        if (optarg)
                                open_namespace_fd(CLONE_NEWPID, optarg);
@@ -562,6 +605,13 @@ int main(int argc, char *argv[])
                namespaces |= nsfile->nstype;
        }
 
+       if (sock_fd != -1) {
+               if (!namespace_target_pid)
+                       errx(EXIT_FAILURE, _("--net-socket needs target PID"));
+               open_target_sk_netns(namespace_target_pid, sock_fd);
+               namespaces |= CLONE_NEWNET;
+       }
+
        /* for user namespaces we always set UID and GID (default is 0)
         * and clear root's groups if --preserve-credentials is no specified */
        if ((namespaces & CLONE_NEWUSER) && !preserve_cred) {