| |
| /* Network namespace convenience functions |
| * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
| * |
| * All Rights Reserved |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include "config.h" |
| |
| /*! \addtogroup netns |
| * @{ |
| * Network namespace convenience functions |
| * |
| * \file netns.c */ |
| |
| #if defined(__linux__) |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sched.h> |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <sys/mount.h> |
| #include <sys/param.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| |
| #include <osmocom/core/utils.h> |
| #include <osmocom/core/netns.h> |
| |
| #define NETNS_PREFIX_PATH "/var/run/netns" |
| #define NETNS_CURRENT_PATH "/proc/self/ns/net" |
| |
| /*! Open a file descriptor for the current network namespace. |
| * \returns fd of the current network namespace on success; negative in case of error |
| */ |
| static int netns_open_current_fd(void) |
| { |
| int fd; |
| /* store the default namespace for later reference */ |
| if ((fd = open(NETNS_CURRENT_PATH, O_RDONLY)) < 0) |
| return -errno; |
| return fd; |
| } |
| |
| /*! switch to a (non-default) namespace, store existing signal mask in oldmask. |
| * \param[in] nsfd file descriptor representing the namespace to which we shall switch |
| * \param[out] state caller-provided memory location to which state of previous netns is stored |
| * \returns 0 on success; negative on error */ |
| int osmo_netns_switch_enter(int nsfd, struct osmo_netns_switch_state *state) |
| { |
| sigset_t intmask; |
| int rc; |
| |
| state->prev_nsfd = -1; |
| |
| if (sigfillset(&intmask) < 0) |
| return -errno; |
| if ((rc = sigprocmask(SIG_BLOCK, &intmask, &state->prev_sigmask)) != 0) |
| return -rc; |
| state->prev_nsfd = netns_open_current_fd(); |
| |
| if (setns(nsfd, CLONE_NEWNET) < 0) { |
| /* restore old mask if we couldn't switch the netns */ |
| sigprocmask(SIG_SETMASK, &state->prev_sigmask, NULL); |
| close(state->prev_nsfd); |
| state->prev_nsfd = -1; |
| return -errno; |
| } |
| return 0; |
| } |
| |
| /*! switch back to the previous namespace, restoring signal mask. |
| * \param[in] state information about previous netns, filled by osmo_netns_switch_enter() |
| * \returns 0 on successs; negative on error */ |
| int osmo_netns_switch_exit(struct osmo_netns_switch_state *state) |
| { |
| if (state->prev_nsfd < 0) |
| return -EINVAL; |
| |
| int rc; |
| if (setns(state->prev_nsfd, CLONE_NEWNET) < 0) |
| return -errno; |
| |
| close(state->prev_nsfd); |
| state->prev_nsfd = -1; |
| |
| if ((rc = sigprocmask(SIG_SETMASK, &state->prev_sigmask, NULL)) != 0) |
| return -rc; |
| return 0; |
| } |
| |
| static int create_netns(const char *name) |
| { |
| char path[MAXPATHLEN]; |
| sigset_t intmask, oldmask; |
| int fd, prev_nsfd; |
| int rc, rc2; |
| |
| /* create /var/run/netns, if it doesn't exist already */ |
| rc = mkdir(NETNS_PREFIX_PATH, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); |
| if (rc < 0 && errno != EEXIST) |
| return rc; |
| |
| /* create /var/run/netns/[name], if it doesn't exist already */ |
| rc = snprintf(path, sizeof(path), "%s/%s", NETNS_PREFIX_PATH, name); |
| if (rc >= sizeof(path)) |
| return -ENAMETOOLONG; |
| fd = open(path, O_RDONLY|O_CREAT|O_EXCL, 0); |
| if (fd < 0) |
| return -errno; |
| if (close(fd) < 0) |
| return -errno; |
| |
| /* mask off all signals, store old signal mask */ |
| if (sigfillset(&intmask) < 0) |
| return -errno; |
| if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0) |
| return -rc; |
| |
| prev_nsfd = netns_open_current_fd(); |
| if (prev_nsfd < 0) |
| return prev_nsfd; |
| |
| /* create a new network namespace */ |
| if (unshare(CLONE_NEWNET) < 0) { |
| rc = -errno; |
| goto restore_sigmask; |
| } |
| if (mount(NETNS_CURRENT_PATH, path, "none", MS_BIND, NULL) < 0) { |
| rc = -errno; |
| goto restore_sigmask; |
| } |
| |
| /* switch back to previous namespace */ |
| if (setns(prev_nsfd, CLONE_NEWNET) < 0) { |
| rc = -errno; |
| goto restore_sigmask; |
| } |
| |
| restore_sigmask: |
| close(prev_nsfd); |
| |
| /* restore process mask */ |
| if ((rc2 = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0) |
| return -rc2; |
| |
| /* might have been set above in case mount fails */ |
| if (rc < 0) |
| return rc; |
| |
| /* finally, open the created namespace file descriptor from previous ns */ |
| if ((fd = open(path, O_RDONLY)) < 0) |
| return -errno; |
| |
| return fd; |
| } |
| |
| /*! Open a file descriptor for the network namespace with provided name. |
| * Creates /var/run/netns/ directory if it doesn't exist already. |
| * \param[in] name Name of the network namespace (in /var/run/netns/) |
| * \returns File descriptor of network namespace; negative in case of error |
| */ |
| int osmo_netns_open_fd(const char *name) |
| { |
| int rc; |
| int fd; |
| char path[MAXPATHLEN]; |
| |
| /* path = /var/run/netns/[name] */ |
| rc = snprintf(path, sizeof(path), "%s/%s", NETNS_PREFIX_PATH, name); |
| if (rc >= sizeof(path)) |
| return -ENAMETOOLONG; |
| |
| /* If netns already exists, simply open it: */ |
| fd = open(path, O_RDONLY); |
| if (fd >= 0) |
| return fd; |
| |
| /* The netns doesn't exist yet, let's create it: */ |
| fd = create_netns(name); |
| return fd; |
| } |
| |
| #endif /* defined(__linux__) */ |
| |
| /*! @} */ |