Move kernel GTP support from ggsn/ to lib/

This way, the IP address / route handling between TUN devices and kernel
GTP can be shared, which will provide not only a unified codebase but
also a more consistent behavior.

This also paves the road for to use kernel GTP from sgsnemu in the future.

Related: OS#3214
Change-Id: Ic53a971136edd0d8871fbd6746d7b0090ce3a188
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 55348ad..b6e7aba 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,7 +1,12 @@
 noinst_LIBRARIES = libmisc.a
 
-noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h
+noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.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
+
+if ENABLE_GTP_KERNEL
+AM_CFLAGS += -DGTP_KERNEL $(LIBGTPNL_CFLAGS)
+libmisc_a_SOURCES += gtp-kernel.c
+endif
diff --git a/lib/gtp-kernel.c b/lib/gtp-kernel.c
new file mode 100644
index 0000000..48811bc
--- /dev/null
+++ b/lib/gtp-kernel.c
@@ -0,0 +1,160 @@
+#ifdef __linux__
+#define _GNU_SOURCE 1		/* strdup() prototype, broken arpa/inet.h */
+#endif
+
+#include "../config.h"
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include <libgtpnl/gtp.h>
+#include <libgtpnl/gtpnl.h>
+#include <libmnl/libmnl.h>
+
+#include <errno.h>
+
+#include <time.h>
+
+#include "../lib/tun.h"
+#include "../lib/syserr.h"
+#include "../gtp/pdp.h"
+#include "../gtp/gtp.h"
+
+#include <libgtpnl/gtp.h>
+#include <libgtpnl/gtpnl.h>
+#include <libmnl/libmnl.h>
+
+#include "gtp-kernel.h"
+
+static void pdp_debug(const char *prefix, const char *devname, struct pdp_t *pdp)
+{
+	struct in46_addr ia46;
+	struct in_addr ia;
+
+	in46a_from_eua(&pdp->eua, &ia46);
+	gsna2in_addr(&ia, &pdp->gsnrc);
+
+	LOGPDPX(DGGSN, LOGL_DEBUG, pdp, "%s %s v%u TEID %"PRIx64" EUA=%s SGSN=%s\n", prefix,
+		devname, pdp->version,
+		pdp->version == 0 ? pdp_gettid(pdp->imsi, pdp->nsapi) : pdp->teid_gn,
+		in46a_ntoa(&ia46), inet_ntoa(ia));
+}
+
+static struct {
+	int			genl_id;
+	struct mnl_socket	*nl;
+} gtp_nl;
+
+static int gtp_kernel_init_once(void)
+{
+	/* only initialize once */
+	if (gtp_nl.nl)
+		return 0;
+
+	gtp_nl.nl = genl_socket_open();
+	if (gtp_nl.nl == NULL) {
+		LOGP(DGGSN, LOGL_ERROR, "cannot create genetlink socket\n");
+		return -1;
+	}
+	gtp_nl.genl_id = genl_lookup_family(gtp_nl.nl, "gtp");
+	if (gtp_nl.genl_id < 0) {
+		LOGP(DGGSN, LOGL_ERROR, "cannot lookup GTP genetlink ID\n");
+		genl_socket_close(gtp_nl.nl);
+		gtp_nl.nl = NULL;
+		return -1;
+	}
+	LOGP(DGGSN, LOGL_NOTICE, "Initialized GTP kernel mode (genl ID is %d)\n", gtp_nl.genl_id);
+
+	return 0;
+}
+
+int gtp_kernel_create(int dest_ns, const char *devname, int fd0, int fd1u)
+{
+	if (gtp_kernel_init_once() < 0)
+		return -1;
+
+	return gtp_dev_create(dest_ns, devname, fd0, fd1u);
+}
+
+int gtp_kernel_create_sgsn(int dest_ns, const char *devname, int fd0, int fd1u)
+{
+	if (gtp_kernel_init_once() < 0)
+		return -1;
+
+	return gtp_dev_create_sgsn(dest_ns, devname, fd0, fd1u);
+}
+
+void gtp_kernel_stop(const char *devname)
+{
+	gtp_dev_destroy(devname);
+}
+
+int gtp_kernel_tunnel_add(struct pdp_t *pdp, const char *devname)
+{
+	struct in_addr ms, sgsn;
+	struct gtp_tunnel *t;
+	int ret;
+
+	pdp_debug(__func__, devname, pdp);
+
+	t = gtp_tunnel_alloc();
+	if (t == NULL)
+		return -1;
+
+	memcpy(&ms, &pdp->eua.v[2], sizeof(struct in_addr));
+	memcpy(&sgsn, &pdp->gsnrc.v[0], sizeof(struct in_addr));
+
+	gtp_tunnel_set_ifidx(t, if_nametoindex(devname));
+	gtp_tunnel_set_version(t, pdp->version);
+	gtp_tunnel_set_ms_ip4(t, &ms);
+	gtp_tunnel_set_sgsn_ip4(t, &sgsn);
+	if (pdp->version == 0) {
+		gtp_tunnel_set_tid(t, pdp_gettid(pdp->imsi, pdp->nsapi));
+		gtp_tunnel_set_flowid(t, pdp->flru);
+	} else {
+		gtp_tunnel_set_i_tei(t, pdp->teid_own);
+		/* use the TEI advertised by SGSN when sending packets
+		 * towards the SGSN */
+		gtp_tunnel_set_o_tei(t, pdp->teid_gn);
+	}
+
+	ret = gtp_add_tunnel(gtp_nl.genl_id, gtp_nl.nl, t);
+	gtp_tunnel_free(t);
+
+	return ret;
+}
+
+int gtp_kernel_tunnel_del(struct pdp_t *pdp, const char *devname)
+{
+	struct gtp_tunnel *t;
+	int ret;
+
+	pdp_debug(__func__, devname, pdp);
+
+	t = gtp_tunnel_alloc();
+	if (t == NULL)
+		return -1;
+
+	gtp_tunnel_set_ifidx(t, if_nametoindex(devname));
+	gtp_tunnel_set_version(t, pdp->version);
+	if (pdp->version == 0) {
+		gtp_tunnel_set_tid(t, pdp_gettid(pdp->imsi, pdp->nsapi));
+		gtp_tunnel_set_flowid(t, pdp->flru);
+	} else {
+		gtp_tunnel_set_i_tei(t, pdp->teid_own);
+	}
+
+	ret = gtp_del_tunnel(gtp_nl.genl_id, gtp_nl.nl, t);
+	gtp_tunnel_free(t);
+
+	return ret;
+}
diff --git a/lib/gtp-kernel.h b/lib/gtp-kernel.h
new file mode 100644
index 0000000..464352c
--- /dev/null
+++ b/lib/gtp-kernel.h
@@ -0,0 +1,38 @@
+#ifndef _GTP_KERNEL_H_
+#define _GTP_KERNEL_H_
+
+struct gengetopt_args_info;
+
+extern int debug;
+extern char *ipup;
+
+#ifdef GTP_KERNEL
+int gtp_kernel_create(int dest_ns, const char *devname, int fd0, int fd1u);
+int gtp_kernel_create_sgsn(int dest_ns, const char *devname, int fd0, int fd1u);
+void gtp_kernel_stop(const char *devname);
+
+int gtp_kernel_tunnel_add(struct pdp_t *pdp, const char *devname);
+int gtp_kernel_tunnel_del(struct pdp_t *pdp, const char *devname);
+
+#else
+static inline int gtp_kernel_create(int dest_ns, const char *devname, int fd0, int fd1u)
+{
+	SYS_ERR(DGGSN, LOGL_ERROR, 0, "ggsn compiled without GTP kernel support!\n");
+	return -1;
+}
+#define gtp_kernel_create_sgsn gtp_kernel_create
+
+static inline void gtp_kernel_stop(const char *devname) {}
+
+static inline int gtp_kernel_tunnel_add(struct pdp_t *pdp, const char *devname)
+{
+	return 0;
+}
+
+static inline int gtp_kernel_tunnel_del(struct pdp_t *pdp, const char *devname)
+{
+	return 0;
+}
+
+#endif
+#endif /* _GTP_KERNEL_H_ */
diff --git a/lib/tun.c b/lib/tun.c
index 6498945..fa4c37d 100644
--- a/lib/tun.c
+++ b/lib/tun.c
@@ -19,6 +19,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -57,6 +58,7 @@
 
 #include "tun.h"
 #include "syserr.h"
+#include "gtp-kernel.h"
 
 static int tun_setaddr4(struct tun_t *this, struct in_addr *addr,
 			struct in_addr *dstaddr, struct in_addr *netmask)
@@ -147,7 +149,7 @@
 	}
 }
 
-int tun_new(struct tun_t **tun, const char *dev_name)
+int tun_new(struct tun_t **tun, const char *dev_name, bool use_kernel, int fd0, int fd1u)
 {
 
 #if defined(__linux__)
@@ -170,31 +172,50 @@
 	(*tun)->routes = 0;
 
 #if defined(__linux__)
-	/* Open the actual tun device */
-	if (((*tun)->fd = open("/dev/net/tun", O_RDWR)) < 0) {
-		SYS_ERR(DTUN, LOGL_ERROR, errno, "open() failed");
-		goto err_free;
+	if (!use_kernel) {
+		/* Open the actual tun device */
+		if (((*tun)->fd = open("/dev/net/tun", O_RDWR)) < 0) {
+			SYS_ERR(DTUN, LOGL_ERROR, errno, "open() failed");
+			goto err_free;
+		}
+
+		/* Set device flags. For some weird reason this is also the method
+		   used to obtain the network interface name */
+		memset(&ifr, 0, sizeof(ifr));
+		if (dev_name)
+			strcpy(ifr.ifr_name, dev_name);
+		ifr.ifr_flags = IFF_TUN | IFF_NO_PI;	/* Tun device, no packet info */
+		if (ioctl((*tun)->fd, TUNSETIFF, (void *)&ifr) < 0) {
+			SYS_ERR(DTUN, LOGL_ERROR, errno, "ioctl() failed");
+			goto err_close;
+		}
+
+		strncpy((*tun)->devname, ifr.ifr_name, IFNAMSIZ);
+		(*tun)->devname[IFNAMSIZ - 1] = 0;
+
+		ioctl((*tun)->fd, TUNSETNOCSUM, 1);	/* Disable checksums */
+		return 0;
+	} else {
+		strncpy((*tun)->devname, dev_name, IFNAMSIZ);
+		(*tun)->devname[IFNAMSIZ - 1] = 0;
+		(*tun)->fd = -1;
+
+		if (gtp_kernel_create(-1, dev_name, fd0, fd1u) < 0) {
+			LOGP(DTUN, LOGL_ERROR, "cannot create GTP tunnel device: %s\n",
+				strerror(errno));
+			return -1;
+		}
+		LOGP(DTUN, LOGL_NOTICE, "GTP kernel configured\n");
+		return 0;
 	}
 
-	/* Set device flags. For some weird reason this is also the method
-	   used to obtain the network interface name */
-	memset(&ifr, 0, sizeof(ifr));
-	if (dev_name)
-		strcpy(ifr.ifr_name, dev_name);
-	ifr.ifr_flags = IFF_TUN | IFF_NO_PI;	/* Tun device, no packet info */
-	if (ioctl((*tun)->fd, TUNSETIFF, (void *)&ifr) < 0) {
-		SYS_ERR(DTUN, LOGL_ERROR, errno, "ioctl() failed");
-		goto err_close;
-	}
-
-	strncpy((*tun)->devname, ifr.ifr_name, IFNAMSIZ);
-	(*tun)->devname[IFNAMSIZ - 1] = 0;
-
-	ioctl((*tun)->fd, TUNSETNOCSUM, 1);	/* Disable checksums */
-	return 0;
-
 #elif defined(__FreeBSD__) || defined (__APPLE__)
 
+	if (use_kernel) {
+		LOGP(DTUN, LOGL_ERROR, "No kernel GTP-U support in FreeBSD!\n");
+		return -1;
+	}
+
 	/* Find suitable device */
 	for (devnum = 0; devnum < 255; devnum++) {	/* TODO 255 */
 		snprintf(devname, sizeof(devname), "/dev/tun%d", devnum);
@@ -249,10 +270,14 @@
 		netdev_delroute(&tun->dstaddr, &tun->addr, &tun->netmask);
 	}
 
-	if (close(tun->fd)) {
-		SYS_ERR(DTUN, LOGL_ERROR, errno, "close() failed");
+	if (tun->fd >= 0) {
+		if (close(tun->fd)) {
+			SYS_ERR(DTUN, LOGL_ERROR, errno, "close() failed");
+		}
 	}
 
+	gtp_kernel_stop(tun->devname);
+
 	/* TODO: For solaris we need to unlink streams */
 
 	free(tun);
diff --git a/lib/tun.h b/lib/tun.h
index e41ee69..6bf141f 100644
--- a/lib/tun.h
+++ b/lib/tun.h
@@ -13,6 +13,7 @@
 #ifndef _TUN_H
 #define _TUN_H
 
+#include <stdbool.h>
 #include <net/if.h>
 
 #include "../lib/in46_addr.h"
@@ -41,7 +42,7 @@
 	void *priv;
 };
 
-extern int tun_new(struct tun_t **tun, const char *dev_name);
+extern int tun_new(struct tun_t **tun, const char *dev_name, bool use_kernel, int fd0, int fd1u);
 extern int tun_free(struct tun_t *tun);
 extern int tun_decaps(struct tun_t *this);
 extern int tun_encaps(struct tun_t *tun, void *pack, unsigned len);