From b629240a352c4455e410c32aec1d67a00e2995db Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Fri, 5 Oct 2018 13:58:45 +0100 Subject: add Linux network namespace support for TUN device Change-Id: Idd0ad8fa9c8e7ba0aeec1b52947598d4d297b620 --- lib/Makefile.am | 4 +- lib/netns.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/netns.h | 35 +++++++++++++ sgsnemu/cmdline.c | 51 ++++++++++++++---- sgsnemu/cmdline.ggo | 1 + sgsnemu/cmdline.h | 8 +++ sgsnemu/sgsnemu.c | 60 ++++++++++++++++++++- 7 files changed, 294 insertions(+), 12 deletions(-) create mode 100644 lib/netns.c create mode 100644 lib/netns.h diff --git a/lib/Makefile.am b/lib/Makefile.am index 533d777..f2c5dc9 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,10 +1,10 @@ noinst_LIBRARIES = libmisc.a -noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h util.h +noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h netns.h util.h AM_CFLAGS = -O2 -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS) -libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c util.c +libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c netns.c util.c if ENABLE_GTP_KERNEL AM_CFLAGS += -DGTP_KERNEL $(LIBGTPNL_CFLAGS) diff --git a/lib/netns.c b/lib/netns.c new file mode 100644 index 0000000..6734b5d --- /dev/null +++ b/lib/netns.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2014-2017, Travelping GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#if defined(__linux__) + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netns.h" + +#define NETNS_PATH "/var/run/netns" + +static int default_nsfd; + +int switch_ns(int nsfd, sigset_t *oldmask) +{ + sigset_t intmask; + + sigfillset(&intmask); + sigprocmask(SIG_BLOCK, &intmask, oldmask); + + return setns(nsfd, CLONE_NEWNET); +} + +void restore_ns(sigset_t *oldmask) +{ + setns(default_nsfd, CLONE_NEWNET); + + sigprocmask(SIG_SETMASK, oldmask, NULL); +} + +int open_ns(int nsfd, const char *pathname, int flags) +{ + sigset_t intmask, oldmask; + int fd; + int errsv; + + sigfillset(&intmask); + sigprocmask(SIG_BLOCK, &intmask, &oldmask); + + setns(nsfd, CLONE_NEWNET); + fd = open(pathname, flags); + errsv = errno; + setns(default_nsfd, CLONE_NEWNET); + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + errno = errsv; + return fd; +} + +int socket_ns(int nsfd, int domain, int type, int protocol) +{ + sigset_t intmask, oldmask; + int sk; + int errsv; + + sigfillset(&intmask); + sigprocmask(SIG_BLOCK, &intmask, &oldmask); + + setns(nsfd, CLONE_NEWNET); + sk = socket(domain, type, protocol); + errsv = errno; + setns(default_nsfd, CLONE_NEWNET); + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + errno = errsv; + return sk; +} + +void init_netns() +{ + if ((default_nsfd = open("/proc/self/ns/net", O_RDONLY)) < 0) { + perror("init_netns"); + exit(EXIT_FAILURE); + } +} + +int get_nsfd(const char *name) +{ + int r; + sigset_t intmask, oldmask; + char path[MAXPATHLEN] = NETNS_PATH; + + r = mkdir(path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + if (r < 0 && errno != EEXIST) + return r; + + snprintf(path, sizeof(path), "%s/%s", NETNS_PATH, name); + r = open(path, O_RDONLY|O_CREAT|O_EXCL, 0); + if (r < 0) { + if (errno == EEXIST) + return open(path, O_RDONLY); + + return r; + } + close(r); + + sigfillset(&intmask); + sigprocmask(SIG_BLOCK, &intmask, &oldmask); + + unshare(CLONE_NEWNET); + mount("/proc/self/ns/net", path, "none", MS_BIND, NULL); + + setns(default_nsfd, CLONE_NEWNET); + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + return open(path, O_RDONLY); +} + +#endif diff --git a/lib/netns.h b/lib/netns.h new file mode 100644 index 0000000..168e44f --- /dev/null +++ b/lib/netns.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014-2017, Travelping GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#ifndef __NETNS_H +#define __NETNS_H + +#if defined(__linux__) + +void init_netns(void); + +int switch_ns(int nsfd, sigset_t *oldmask); +void restore_ns(sigset_t *oldmask); + +int open_ns(int nsfd, const char *pathname, int flags); +int socket_ns(int nsfd, int domain, int type, int protocol); +int get_nsfd(const char *name); + +#endif + +#endif diff --git a/sgsnemu/cmdline.c b/sgsnemu/cmdline.c index b062533..13d0295 100644 --- a/sgsnemu/cmdline.c +++ b/sgsnemu/cmdline.c @@ -72,6 +72,7 @@ const char *gengetopt_args_info_help[] = { " --ipup=STRING Script to run after link-up", " --ipdown=STRING Script to run after link-down", " --tun-device=STRING Name of the local network interface", + " --netns=STRING Network namespace to use", "\n Mode: pinghost\n generate ICMP payload inside G-PDU without setting up tun interface", " --pinghost=STRING Ping remote host", " --pingrate=INT Number of ping req per second (default=`1')", @@ -163,6 +164,7 @@ void clear_given(struct gengetopt_args_info *args_info) args_info->ipup_given = 0; args_info->ipdown_given = 0; args_info->tun_device_given = 0; + args_info->netns_given = 0; args_info->pinghost_given = 0; args_info->pingrate_given = 0; args_info->pingsize_given = 0; @@ -244,6 +246,8 @@ void clear_args(struct gengetopt_args_info *args_info) args_info->ipdown_orig = NULL; args_info->tun_device_arg = NULL; args_info->tun_device_orig = NULL; + args_info->netns_arg = NULL; + args_info->netns_orig = NULL; args_info->pinghost_arg = NULL; args_info->pinghost_orig = NULL; args_info->pingrate_arg = 1; @@ -300,13 +304,14 @@ void init_args_info(struct gengetopt_args_info *args_info) args_info->ipup_help = gengetopt_args_info_help[35]; args_info->ipdown_help = gengetopt_args_info_help[36]; args_info->tun_device_help = gengetopt_args_info_help[37]; - args_info->pinghost_help = gengetopt_args_info_help[39]; - args_info->pingrate_help = gengetopt_args_info_help[40]; - args_info->pingsize_help = gengetopt_args_info_help[41]; - args_info->pingcount_help = gengetopt_args_info_help[42]; - args_info->pingquiet_help = gengetopt_args_info_help[43]; - args_info->no_tx_gpdu_seq_help = gengetopt_args_info_help[44]; - args_info->pdp_type_help = gengetopt_args_info_help[45]; + args_info->netns_help = gengetopt_args_info_help[38]; + args_info->pinghost_help = gengetopt_args_info_help[40]; + args_info->pingrate_help = gengetopt_args_info_help[41]; + args_info->pingsize_help = gengetopt_args_info_help[42]; + args_info->pingcount_help = gengetopt_args_info_help[43]; + args_info->pingquiet_help = gengetopt_args_info_help[44]; + args_info->no_tx_gpdu_seq_help = gengetopt_args_info_help[45]; + args_info->pdp_type_help = gengetopt_args_info_help[46]; } @@ -432,6 +437,8 @@ static void cmdline_parser_release(struct gengetopt_args_info *args_info) free_string_field(&(args_info->ipdown_orig)); free_string_field(&(args_info->tun_device_arg)); free_string_field(&(args_info->tun_device_orig)); + free_string_field(&(args_info->netns_arg)); + free_string_field(&(args_info->netns_orig)); free_string_field(&(args_info->pinghost_arg)); free_string_field(&(args_info->pinghost_orig)); free_string_field(&(args_info->pingrate_orig)); @@ -545,6 +552,8 @@ int cmdline_parser_dump(FILE * outfile, struct gengetopt_args_info *args_info) if (args_info->tun_device_given) write_into_file(outfile, "tun-device", args_info->tun_device_orig, 0); + if (args_info->netns_given) + write_into_file(outfile, "netns", args_info->netns_orig, 0); if (args_info->pinghost_given) write_into_file(outfile, "pinghost", args_info->pinghost_orig, 0); @@ -709,6 +718,12 @@ cmdline_parser_required2(struct gengetopt_args_info *args_info, prog_name, (additional_error ? additional_error : "")); error_occurred = 1; } + if (args_info->netns_given && !args_info->createif_given) { + fprintf(stderr, + "%s: '--netns' option depends on option 'createif'%s\n", + prog_name, (additional_error ? additional_error : "")); + error_occurred = 1; + } if (args_info->pingrate_given && !args_info->pinghost_given) { fprintf(stderr, "%s: '--pingrate' option depends on option 'pinghost'%s\n", @@ -954,6 +969,7 @@ cmdline_parser_internal(int argc, char **argv, {"ipup", 1, NULL, 0}, {"ipdown", 1, NULL, 0}, {"tun-device", 1, NULL, 0}, + {"netns", 1, NULL, 0}, {"pinghost", 1, NULL, 0}, {"pingrate", 1, NULL, 0}, {"pingsize", 1, NULL, 0}, @@ -1493,6 +1509,22 @@ cmdline_parser_internal(int argc, char **argv, additional_error)) goto failure; + } + /* Network namespace to use. */ + else if (strcmp + (long_options[option_index].name, + "netns") == 0) { + args_info->createif_mode_counter += 1; + + if (update_arg((void *)&(args_info->netns_arg), + &(args_info->netns_orig), + &(args_info->netns_given), + &(local_args_info.netns_given), + optarg, 0, 0, ARG_STRING, + check_ambiguity, override, 0, 0, + "netns", '-', additional_error)) + goto failure; + } /* Ping remote host. */ else if (strcmp @@ -1609,11 +1641,12 @@ cmdline_parser_internal(int argc, char **argv, int createif_given[] = { args_info->createif_given, args_info->net_given, args_info->defaultroute_given, args_info->ipup_given, - args_info->ipdown_given, args_info->tun_device_given, -1 + args_info->ipdown_given, args_info->tun_device_given, + args_info->netns_given, -1 }; const char *createif_desc[] = { "--createif", "--net", "--defaultroute", "--ipup", - "--ipdown", "--tun-device", 0 + "--ipdown", "--tun-device", "--netns", 0 }; int pinghost_given[] = { args_info->pinghost_given, args_info->pingrate_given, diff --git a/sgsnemu/cmdline.ggo b/sgsnemu/cmdline.ggo index 0f415f5..0d074aa 100644 --- a/sgsnemu/cmdline.ggo +++ b/sgsnemu/cmdline.ggo @@ -59,6 +59,7 @@ modeoption "defaultroute" - "Create default route" flag dependon=" modeoption "ipup" - "Script to run after link-up" string dependon="createif" no mode="createif" modeoption "ipdown" - "Script to run after link-down" string dependon="createif" no mode="createif" modeoption "tun-device" - "Name of the local network interface" string dependon="createif" no mode="createif" +modeoption "netns" - "Network namespace to use" string dependon="createif" no mode="createif" modeoption "pinghost" - "Ping remote host" string no mode="pinghost" modeoption "pingrate" - "Number of ping req per second" int default="1" dependon="pinghost" no mode="pinghost" diff --git a/sgsnemu/cmdline.h b/sgsnemu/cmdline.h index 24f772b..c7c8521 100644 --- a/sgsnemu/cmdline.h +++ b/sgsnemu/cmdline.h @@ -242,6 +242,12 @@ extern "C" { /**< @brief Name of the local network interface original value given at command line. */ const char *tun_device_help; /**< @brief Name of the local network interface help description. */ + char *netns_arg; + /**< @brief Network namespace to use. */ + char *netns_orig; + /**< @brief Network namespace to use original value given at command line. */ + const char *netns_help; + /**< @brief Network namespace to use help description. */ char *pinghost_arg; /**< @brief Ping remote host. */ char *pinghost_orig; @@ -355,6 +361,8 @@ extern "C" { /**< @brief Whether ipdown was given. */ unsigned int tun_device_given; /**< @brief Whether tun-device was given. */ + unsigned int netns_given; + /**< @brief Whether netns was given. */ unsigned int pinghost_given; /**< @brief Whether pinghost was given. */ unsigned int pingrate_given; diff --git a/sgsnemu/sgsnemu.c b/sgsnemu/sgsnemu.c index 7904c49..2c0ce1b 100644 --- a/sgsnemu/sgsnemu.c +++ b/sgsnemu/sgsnemu.c @@ -50,6 +50,7 @@ #include "../lib/tun.h" #include "../lib/ippool.h" #include "../lib/syserr.h" +#include "../lib/netns.h" #include "../gtp/pdp.h" #include "../gtp/gtp.h" #include "cmdline.h" @@ -81,12 +82,16 @@ struct tun_t *tun = NULL; /* TUN instance */ int maxfd = 0; /* For select() */ int echoversion = 1; /* First try this version */ void *tall_sgsnemu_ctx; /* root talloc ctx */ +#if defined(__linux__) +int netns = -1; /* network namespace */ +#endif /* Struct with local versions of gengetopt options */ struct { int debug; /* Print debug messages */ int createif; /* Create local network interface */ char *tun_dev_name; + char *netns; struct in46_addr netaddr, destaddr, net; /* Network interface */ size_t prefixlen; char *ipup, *ipdown; /* Filename of scripts */ @@ -294,6 +299,8 @@ static int process_options(int argc, char **argv) printf("createif: %d\n", args_info.createif_flag); if (args_info.tun_device_arg) printf("tun-device: %s\n", args_info.tun_device_arg); + if (args_info.netns_arg) + printf("netns: %s\n", args_info.netns_arg); if (args_info.ipup_arg) printf("ipup: %s\n", args_info.ipup_arg); if (args_info.ipdown_arg) @@ -352,6 +359,8 @@ static int process_options(int argc, char **argv) printf("createif: %d\n", args_info.createif_flag); if (args_info.tun_device_arg) printf("tun-device: %s\n", args_info.tun_device_arg); + if (args_info.netns_arg) + printf("netns: %s\n", args_info.netns_arg); if (args_info.ipup_arg) printf("ipup: %s\n", args_info.ipup_arg); if (args_info.ipdown_arg) @@ -870,6 +879,7 @@ static int process_options(int argc, char **argv) /* createif */ options.createif = args_info.createif_flag; options.tun_dev_name = args_info.tun_device_arg; + options.netns = args_info.netns_arg; /* net */ /* Store net as in_addr net and mask */ @@ -1313,10 +1323,23 @@ static int create_ping(void *gsn, struct pdp_t *pdp, static int delete_context(struct pdp_t *pdp) { + if (tun && options.ipdown) { +#if defined(__linux__) + sigset_t oldmask; - if (tun && options.ipdown) + if ((options.netns)) { + switch_ns(netns, &oldmask); + } +#endif tun_runscript(tun, options.ipdown); +#if defined(__linux__) + if ((options.netns)) { + restore_ns(&oldmask); + } +#endif + } + ipdel((struct iphash_t *)pdp->peer[0]); memset(pdp->peer[0], 0, sizeof(struct iphash_t)); /* To be sure */ @@ -1377,6 +1400,9 @@ static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len) static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) { struct in46_addr addr; +#if defined(__linux__) + sigset_t oldmask; +#endif struct iphash_t *iph = (struct iphash_t *)cbp; @@ -1430,6 +1456,12 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) break; } +#if defined(__linux__) + if ((options.createif) && (options.netns)) { + switch_ns(netns, &oldmask); + } +#endif + if ((options.createif) && (!options.net.len)) { size_t prefixlen = 32; if (addr.len == 16) @@ -1470,6 +1502,12 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) free(forwarding); } +#if defined(__linux__) + if ((options.createif) && (options.netns)) { + restore_ns(&oldmask); + } +#endif + ipset(iph, &addr); state = 2; /* Connected */ @@ -1543,6 +1581,9 @@ int main(int argc, char **argv) struct timezone tz; /* Used for calculating ping times */ struct timeval tv; int diff; +#if defined(__linux__) + sigset_t oldmask; +#endif signal(SIGTERM, signal_handler); signal(SIGHUP, signal_handler); @@ -1552,6 +1593,10 @@ int main(int argc, char **argv) msgb_talloc_ctx_init(tall_sgsnemu_ctx, 0); osmo_init_logging2(tall_sgsnemu_ctx, &log_info); +#if defined(__linux__) + init_netns(); +#endif + /* Process options given in configuration file and command line */ if (process_options(argc, argv)) exit(1); @@ -1575,6 +1620,13 @@ int main(int argc, char **argv) else gtp_set_cb_data_ind(gsn, encaps_ping); +#if defined(__linux__) + if ((options.createif) && (options.netns)) { + netns = get_nsfd(options.netns); + switch_ns(netns, &oldmask); + } +#endif + if (options.createif) { printf("Setting up interface\n"); /* Create a tunnel interface */ @@ -1600,6 +1652,12 @@ int main(int argc, char **argv) tun_runscript(tun, options.ipup); } +#if defined(__linux__) + if ((options.createif) && (options.netns)) { + restore_ns(&oldmask); + } +#endif + /* Initialise hash tables */ memset(&iphash, 0, sizeof(iphash)); memset(&iparr, 0, sizeof(iparr)); -- cgit v1.2.3