add Linux network namespace support for TUN device

Change-Id: Idd0ad8fa9c8e7ba0aeec1b52947598d4d297b620
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 @@
 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 @@
 		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 @@
 			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 @@
 	/* 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 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 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 @@
 		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 @@
 		free(forwarding);
 	}
 
+#if defined(__linux__)
+	if ((options.createif) && (options.netns)) {
+		restore_ns(&oldmask);
+	}
+#endif
+
 	ipset(iph, &addr);
 
 	state = 2;		/* Connected */
@@ -1543,6 +1581,9 @@
 	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 @@
 	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 @@
 	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 @@
 			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));