diff options
author | Pau Espin Pedrol <pespin@sysmocom.de> | 2023-01-19 16:54:47 +0100 |
---|---|---|
committer | Pau Espin Pedrol <pespin@sysmocom.de> | 2023-01-24 12:38:20 +0100 |
commit | 8a5014be084249be515d0b3d088e9cb21d7552bc (patch) | |
tree | 050c9d0b44038562e848014651dac4b1480542f5 | |
parent | 4ce820ab8d0820c664afb07e9ea62fdc41eb00ad (diff) |
Introduce netns API
Write a new API and implementation to manage network namespace related
operations.
This will be used by the upcoming tundev module.
Change-Id: I0f2fba2fa42250a07211a7b7f479498f27c529da
-rw-r--r-- | TODO-RELEASE | 1 | ||||
-rw-r--r-- | include/osmocom/core/Makefile.am | 1 | ||||
-rw-r--r-- | include/osmocom/core/netns.h | 24 | ||||
-rw-r--r-- | src/core/Makefile.am | 1 | ||||
-rw-r--r-- | src/core/netns.c | 208 |
5 files changed, 235 insertions, 0 deletions
diff --git a/TODO-RELEASE b/TODO-RELEASE index 86363e14..f9b31a0f 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -9,3 +9,4 @@ #library what description / commit summary line libosmocore new API osmo_sockaddr_is_any(), osmo_sockaddr_netmask_to_prefixlen() libosmocore ABI breakage OSMO_NUM_DLIB change affecting internal_cat[] +libosmocore new API osmo_netns_*() diff --git a/include/osmocom/core/Makefile.am b/include/osmocom/core/Makefile.am index 1b0a1ce5..e88ce899 100644 --- a/include/osmocom/core/Makefile.am +++ b/include/osmocom/core/Makefile.am @@ -35,6 +35,7 @@ osmocore_HEADERS = \ stats.h \ macaddr.h \ msgb.h \ + netns.h \ panic.h \ prbs.h \ prim.h \ diff --git a/include/osmocom/core/netns.h b/include/osmocom/core/netns.h new file mode 100644 index 00000000..5bbf2242 --- /dev/null +++ b/include/osmocom/core/netns.h @@ -0,0 +1,24 @@ +/*! \file netns.h + * Network namespace convenience functions. */ + +#pragma once +#if (!EMBEDDED) + +#if defined(__linux__) + +#include <signal.h> + +struct osmo_netns_switch_state { + sigset_t prev_sigmask; + int prev_nsfd; +}; + +int osmo_netns_open_fd(const char *name); +int osmo_netns_switch_enter(int nsfd, struct osmo_netns_switch_state *state); +int osmo_netns_switch_exit(struct osmo_netns_switch_state *state); + + +#endif /* defined(__linux__) */ + +#endif /* (!EMBEDDED) */ +/*! @} */ diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 9b812cef..216330a8 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -48,6 +48,7 @@ libosmocore_la_SOURCES = \ loggingrb.c \ macaddr.c \ msgb.c \ + netns.c \ panic.c \ prbs.c \ prim.c \ diff --git a/src/core/netns.c b/src/core/netns.c new file mode 100644 index 00000000..56b0c3ae --- /dev/null +++ b/src/core/netns.c @@ -0,0 +1,208 @@ + +/* Network namespace convenience functions + * (C) 2023 by sysmocom - s.m.f.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__) */ + +/*! @} */ |