mirror of https://gerrit.osmocom.org/libosmocore
209 lines
5.3 KiB
C
209 lines
5.3 KiB
C
|
|
/* 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__) */
|
|
|
|
/*! @} */
|