add libgtpnl

This patch adds the libgtpnl library. Harald mentioned that he wanted
that the specific code that is added is well encapsulated, so let's
start a small library to interact with the GTP kernel module via netlink
interface.

This was done a bit while in the rush, so the interfaces are not nice
at all and the tools need to be ported on top of this library.

This library will be used to integrate openggsn with the GTP kernel
module.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9deb3b5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# Dependency and object files
+.*.d
+*.o
+*.lo
+*.la
+.deps
+.libs
+.dirstamp
+
+# Generated by autoconf/configure
+Makefile
+Makefile.in
+config.*
+configure
+autom4te.cache
+stamp-h1
+aclocal.m4
+libgtpnl.pc
+libtool
+build-aux
+
+# Debian package build temporary files
+build-stamp
diff --git a/Make_global.am b/Make_global.am
new file mode 100644
index 0000000..1654f10
--- /dev/null
+++ b/Make_global.am
@@ -0,0 +1,24 @@
+# This is _NOT_ the library release version, it's an API version.
+# Extracted from Chapter 6 "Library interface versions" of the libtool docs.
+#
+# <snippet>
+# Here are a set of rules to help you update your library version information:
+#
+# 1. Start with version information of `0:0:0' for each libtool library.
+# 2. Update the version information only immediately before a public release
+# of your software. More frequent updates are unnecessary, and only guarantee
+# that the current interface number gets larger faster.
+# 3. If the library source code has changed at all since the last update,
+# then increment revision (`c:r:a' becomes `c:r+1:a').
+# 4. If any interfaces have been added, removed, or changed since the last
+# update, increment current, and set revision to 0.
+# 5. If any interfaces have been added since the last public release, then
+# increment age.
+# 6. If any interfaces have been removed since the last public release, then
+# set age to 0.
+# </snippet>
+#
+LIBVERSION=0:0:0
+
+AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_srcdir}/include ${LIBMNL_CFLAGS}
+AM_CFLAGS = ${regular_CFLAGS} ${GCC_FVISIBILITY_HIDDEN}
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..cb72faf
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,11 @@
+include $(top_srcdir)/Make_global.am
+
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = src include tools
+DIST_SUBDIRS = src include tools
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgtpnl.pc
+
+${pkgconfig_DATA}: ${top_builddir}/config.status
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..a9845ac
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,34 @@
+dnl Process this file with autoconf to create configure.
+
+AC_INIT([libgtpnl], [1.0.0])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CANONICAL_HOST
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([foreign tar-pax no-dist-gzip dist-bzip2 1.6 subdir-objects])
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl Dependencies
+PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.0])
+
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_EXEEXT
+AC_DISABLE_STATIC
+LT_INIT
+CHECK_GCC_FVISIBILITY
+case "$host" in
+*-*-linux* | *-*-uclinux*) ;;
+*) AC_MSG_ERROR([Linux only, dude!]);;
+esac
+
+regular_CPPFLAGS="-D_FILE_OFFSET_BITS=64 -D_REENTRANT"
+regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \
+	-Wmissing-prototypes -Wshadow -Wstrict-prototypes \
+	-Wformat=2 -pipe"
+AC_SUBST([regular_CPPFLAGS])
+AC_SUBST([regular_CFLAGS])
+AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/libgtpnl/Makefile include/linux/Makefile tools/Makefile libgtpnl.pc])
+AC_OUTPUT
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..bafe5e3
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = libgtpnl linux
diff --git a/include/libgtpnl/Makefile.am b/include/libgtpnl/Makefile.am
new file mode 100644
index 0000000..b1c2bee
--- /dev/null
+++ b/include/libgtpnl/Makefile.am
@@ -0,0 +1 @@
+pkginclude_HEADERS = gtpnl.h
diff --git a/include/libgtpnl/gtpnl.h b/include/libgtpnl/gtpnl.h
new file mode 100644
index 0000000..00a60d8
--- /dev/null
+++ b/include/libgtpnl/gtpnl.h
@@ -0,0 +1,26 @@
+#ifndef _LIBGTPNL_H_
+#define _LIBGTPNL_H_
+
+#include <stdint.h>
+
+struct mnl_socket;
+struct nlmsghdr;
+
+struct mnl_socket *genl_socket_open(void);
+struct nlmsghdr *genl_nlmsg_build_hdr(char *buf, uint16_t type, uint16_t flags,
+				      uint32_t seq, uint8_t cmd);
+int genl_socket_talk(struct mnl_socket *nl, struct nlmsghdr *nlh, uint32_t seq,
+		     int (*cb)(const struct nlmsghdr *nlh, void *data),
+		     void *data);
+int genl_lookup_family(struct mnl_socket *nl, const char *family);
+
+int gtp_dev_create(const char *ifname);
+
+int gtp_add_tunnel(int genl_id, struct mnl_socket *nl, const char *ifname,
+		   const char *ms_addr, const char *sgsn_addr, uint64_t tid,
+		   int gtp_version);
+int gtp_del_tunnel(int genl_id, struct mnl_socket *nl, const char *ifname,
+		   uint64_t tid, uint32_t gtp_version);
+int gtp_list_tunnel(int genl_id, struct mnl_socket *nl);
+
+#endif
diff --git a/include/linux/Makefile.am b/include/linux/Makefile.am
new file mode 100644
index 0000000..082198e
--- /dev/null
+++ b/include/linux/Makefile.am
@@ -0,0 +1 @@
+noinst_HEADERS = gtp_nl.h
diff --git a/include/linux/gtp_nl.h b/include/linux/gtp_nl.h
new file mode 100644
index 0000000..968824c
--- /dev/null
+++ b/include/linux/gtp_nl.h
@@ -0,0 +1,45 @@
+#ifndef _UAPI_LINUX_GTP_H_
+#define _UAPI_LINUX_GTP_H__
+
+enum {
+	IFLA_GTP_UNSPEC,
+	IFLA_GTP_LOCAL_ADDR_IPV4,
+	IFLA_GTP_HASHSIZE,
+	__IFLA_GTP_MAX,
+};
+#define IFLA_GTP_MAX (__IFLA_GTP_MAX - 1)
+
+enum gtp_genl_cmds {
+	GTP_CMD_TUNNEL_NEW,
+	GTP_CMD_TUNNEL_DELETE,
+	GTP_CMD_TUNNEL_GET,
+
+	GTP_CMD_TUNNEL_MAX,
+};
+
+enum gtp_version {
+	GTP_V0 = 0,
+	GTP_V1,
+};
+
+enum gtp_cfg_attrs {
+	GTPA_CFG_UNSPEC = 0,
+	GTPA_CFG_LINK,
+	GTPA_CFG_LOCAL_ADDR_IPV4,
+	GTPA_CFG_HSIZE,
+	__GTPA_CFG_MAX,
+};
+#define GTPA_CFG_MAX (__GTPA_CFG_MAX + 1)
+
+enum gtp_attrs {
+	GTPA_UNSPEC = 0,
+	GTPA_LINK,
+	GTPA_VERSION,
+	GTPA_TID,	/* 64 bits for GTPv1 */
+	GTPA_SGSN_ADDRESS,
+	GTPA_MS_ADDRESS,
+	__GTPA_MAX,
+};
+#define GTPA_MAX (__GTPA_MAX + 1)
+
+#endif /* _UAPI_LINUX_GTP_H_ */
diff --git a/libgtpnl.pc.in b/libgtpnl.pc.in
new file mode 100644
index 0000000..2dad5fe
--- /dev/null
+++ b/libgtpnl.pc.in
@@ -0,0 +1,15 @@
+# libgtpnl pkg-config file
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libgtpnl
+Description: Netlink library for GTP Linux kernel implementation
+URL: http://git.osmocom.org/libgtpnl/
+Version: @VERSION@
+Requires:
+Conflicts:
+Libs: -L${libdir} -lgtpnl
+Cflags: -I${includedir}
diff --git a/m4/gcc4_visibility.m4 b/m4/gcc4_visibility.m4
new file mode 100644
index 0000000..214d3f3
--- /dev/null
+++ b/m4/gcc4_visibility.m4
@@ -0,0 +1,21 @@
+
+# GCC 4.x -fvisibility=hidden
+
+AC_DEFUN([CHECK_GCC_FVISIBILITY], [
+	AC_LANG_PUSH([C])
+	saved_CFLAGS="$CFLAGS"
+	CFLAGS="$saved_CFLAGS -fvisibility=hidden"
+	AC_CACHE_CHECK([whether compiler accepts -fvisibility=hidden],
+	  [ac_cv_fvisibility_hidden], AC_COMPILE_IFELSE(
+		[AC_LANG_SOURCE()],
+		[ac_cv_fvisibility_hidden=yes],
+		[ac_cv_fvisibility_hidden=no]
+	))
+	if test "$ac_cv_fvisibility_hidden" = "yes"; then
+		AC_DEFINE([HAVE_VISIBILITY_HIDDEN], [1],
+			[True if compiler supports -fvisibility=hidden])
+		AC_SUBST([GCC_FVISIBILITY_HIDDEN], [-fvisibility=hidden])
+	fi
+	CFLAGS="$saved_CFLAGS"
+	AC_LANG_POP([C])
+])
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..a9b072f
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,5 @@
+include $(top_srcdir)/Make_global.am
+lib_LTLIBRARIES = libgtpnl.la
+
+libgtpnl_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libgtpnl.map -version-info $(LIBVERSION)
+libgtpnl_la_SOURCES = genl.c gtp-genl.c gtp-rtnl.c libgtpnl.map
diff --git a/src/genl.c b/src/genl.c
new file mode 100644
index 0000000..099b317
--- /dev/null
+++ b/src/genl.c
@@ -0,0 +1,146 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+
+#include <libgtpnl/gtpnl.h>
+
+#include "internal.h"
+
+struct nlmsghdr *
+genl_nlmsg_build_hdr(char *buf, uint16_t type, uint16_t flags, uint32_t seq,
+		     uint8_t cmd)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr *genl;
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type = type;
+	nlh->nlmsg_flags = NLM_F_REQUEST | flags;
+	nlh->nlmsg_seq = seq;
+
+	genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+	genl->cmd = cmd;
+	genl->version = 0;
+
+	return nlh;
+}
+EXPORT_SYMBOL(genl_nlmsg_build_hdr);
+
+static int genl_ctrl_validate_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_OK;
+
+	switch(type) {
+	case CTRL_ATTR_FAMILY_ID:
+		if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) {
+			perror("mnl_attr_validate");
+			return MNL_CB_ERROR;
+		}
+		break;
+	}
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int genl_ctrl_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+	struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+	int32_t *genl_id = data;
+
+	mnl_attr_parse(nlh, sizeof(*genl), genl_ctrl_validate_cb, tb);
+	if (tb[CTRL_ATTR_FAMILY_ID])
+		*genl_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+	else
+		*genl_id = -1;
+
+	return MNL_CB_OK;
+}
+
+struct mnl_socket *genl_socket_open(void)
+{
+	struct mnl_socket *nl;
+
+	nl = mnl_socket_open(NETLINK_GENERIC);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		return NULL;
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		return NULL;
+	}
+
+	return nl;
+}
+EXPORT_SYMBOL(genl_socket_open);
+
+int genl_socket_talk(struct mnl_socket *nl, struct nlmsghdr *nlh, uint32_t seq,
+		     int (*cb)(const struct nlmsghdr *nlh, void *data),
+		     void *data)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	int ret;
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		return -1;
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	while (ret > 0) {
+		ret = mnl_cb_run(buf, ret, seq, mnl_socket_get_portid(nl),
+				 cb, data);
+		if (ret <= 0)
+			break;
+		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(genl_socket_talk);
+
+static struct nlmsghdr *
+genl_nlmsg_build_lookup(char *buf, const char *subsys_name)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr *genl;
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type	= GENL_ID_CTRL;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	nlh->nlmsg_seq = time(NULL);
+
+	genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+	genl->cmd = CTRL_CMD_GETFAMILY;
+	genl->version = 1;
+
+	mnl_attr_put_u32(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL);
+	mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, subsys_name);
+
+	return nlh;
+}
+
+int genl_lookup_family(struct mnl_socket *nl, const char *family)
+{
+	int32_t genl_id;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh = genl_nlmsg_build_lookup(buf, "gtp");
+	int err;
+
+	err = genl_socket_talk(nl, nlh, nlh->nlmsg_seq, genl_ctrl_cb, &genl_id);
+	if (err < 0)
+		return -1;
+
+	return genl_id;
+}
+EXPORT_SYMBOL(genl_lookup_family);
diff --git a/src/gtp-genl.c b/src/gtp-genl.c
new file mode 100644
index 0000000..0dd5473
--- /dev/null
+++ b/src/gtp-genl.c
@@ -0,0 +1,177 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <inttypes.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+
+#include <libgtpnl/gtpnl.h>
+
+#include <net/if.h>
+#include <linux/gtp_nl.h>
+
+#include "internal.h"
+
+static void gtp_build_payload(struct nlmsghdr *nlh, uint64_t tid,
+			      uint32_t ifidx, uint32_t sgsn_addr,
+			      uint32_t ms_addr, uint32_t version)
+{
+	mnl_attr_put_u32(nlh, GTPA_VERSION, version);
+	mnl_attr_put_u32(nlh, GTPA_LINK, ifidx);
+	mnl_attr_put_u32(nlh, GTPA_SGSN_ADDRESS, sgsn_addr);
+	mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, ms_addr);
+	mnl_attr_put_u64(nlh, GTPA_TID, tid);
+}
+
+int gtp_add_tunnel(int genl_id, struct mnl_socket *nl, const char *ifname,
+		   const char *ms_addr, const char *sgsn_addr, uint64_t tid,
+		   int gtp_version)
+{
+	uint32_t gtp_ifidx;
+	struct in_addr ms, sgsn;
+	struct nlmsghdr *nlh;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	uint32_t seq = time(NULL);
+
+	gtp_ifidx = if_nametoindex(ifname);
+	if (gtp_ifidx == 0){
+		fprintf(stderr, "wrong GTP interface %s\n", ifname);
+		return -1;
+	}
+
+	if (inet_aton(ms_addr, &ms) < 0) {
+		perror("bad address for ms");
+		return -1;
+	}
+
+	if (inet_aton(sgsn_addr, &sgsn) < 0) {
+		perror("bad address for sgsn");
+		return -1;
+	}
+
+	if (gtp_version > GTP_V1) {
+		fprintf(stderr, "wrong GTP version %u, use v0 or v1\n",
+			gtp_version);
+		return -1;
+	}
+
+	nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_EXCL | NLM_F_ACK, ++seq,
+				   GTP_CMD_TUNNEL_NEW);
+	gtp_build_payload(nlh, tid, gtp_ifidx, sgsn.s_addr,
+			  ms.s_addr, gtp_version);
+
+	if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0)
+		perror("genl_socket_talk");
+
+	return 0;
+}
+EXPORT_SYMBOL(gtp_add_tunnel);
+
+int gtp_del_tunnel(int genl_id, struct mnl_socket *nl, const char *ifname,
+		   uint64_t tid, uint32_t gtp_version)
+{
+	uint32_t gtp_ifidx;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	uint32_t seq = time(NULL);
+
+	gtp_ifidx = if_nametoindex(ifname);
+
+	nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_ACK, ++seq,
+				   GTP_CMD_TUNNEL_DELETE);
+	gtp_build_payload(nlh, tid, gtp_ifidx, 0, 0, gtp_version);
+
+	if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0)
+		perror("genl_socket_talk");
+
+	return 0;
+}
+EXPORT_SYMBOL(gtp_del_tunnel);
+
+struct gtp_pdp {
+	uint32_t	version;
+	uint64_t	tid;
+	struct in_addr	sgsn_addr;
+	struct in_addr	ms_addr;
+};
+
+static int genl_gtp_validate_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_OK;
+
+	switch(type) {
+	case GTPA_TID:
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0) {
+			perror("mnl_attr_validate");
+			return MNL_CB_ERROR;
+		}
+		break;
+	case GTPA_SGSN_ADDRESS:
+	case GTPA_MS_ADDRESS:
+	case GTPA_VERSION:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+			perror("mnl_attr_validate");
+			return MNL_CB_ERROR;
+		}
+		break;
+	default:
+		break;
+	}
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nlattr *tb[GTPA_MAX + 1] = {};
+	struct gtp_pdp pdp;
+	struct genlmsghdr *genl;
+
+	mnl_attr_parse(nlh, sizeof(*genl), genl_gtp_validate_cb, tb);
+	if (tb[GTPA_TID])
+		pdp.tid = mnl_attr_get_u64(tb[GTPA_TID]);
+	if (tb[GTPA_SGSN_ADDRESS]) {
+		pdp.sgsn_addr.s_addr =
+			mnl_attr_get_u32(tb[GTPA_SGSN_ADDRESS]);
+	}
+	if (tb[GTPA_MS_ADDRESS]) {
+		pdp.ms_addr.s_addr = mnl_attr_get_u32(tb[GTPA_MS_ADDRESS]);
+	}
+	if (tb[GTPA_VERSION]) {
+		pdp.version = mnl_attr_get_u32(tb[GTPA_VERSION]);
+	}
+
+	printf("version %u ", pdp.version);
+	printf("tid %"PRIu64" ms_addr %s ", pdp.tid, inet_ntoa(pdp.sgsn_addr));
+	printf("sgsn_addr %s\n", inet_ntoa(pdp.ms_addr));
+
+	return MNL_CB_OK;
+}
+
+int gtp_list_tunnel(int genl_id, struct mnl_socket *nl)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	uint32_t seq = time(NULL);
+
+	nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_DUMP, 0,
+				   GTP_CMD_TUNNEL_GET);
+
+	if (genl_socket_talk(nl, nlh, seq, genl_gtp_attr_cb, NULL) < 0) {
+		perror("genl_socket_talk");
+		return 0;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(gtp_list_tunnel);
diff --git a/src/gtp-rtnl.c b/src/gtp-rtnl.c
new file mode 100644
index 0000000..2cd43ef
--- /dev/null
+++ b/src/gtp-rtnl.c
@@ -0,0 +1,82 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#include <libmnl/libmnl.h>
+#include <net/if.h>
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+
+#include <libgtpnl/gtpnl.h>
+
+#include <linux/gtp_nl.h>
+
+#include "internal.h"
+
+int gtp_dev_create(const char *ifname)
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct ifinfomsg *ifm;
+	int ret;
+	unsigned int seq, portid;
+	struct nlattr *nest, *nest2;
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type	= RTM_NEWLINK;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+	nlh->nlmsg_seq = seq = time(NULL);
+	ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+	ifm->ifi_family = AF_INET;
+	ifm->ifi_change |= IFF_UP;
+	ifm->ifi_flags |= IFF_UP;
+
+	mnl_attr_put_u32(nlh, IFLA_LINK, if_nametoindex(ifname));
+	nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+	mnl_attr_put_str(nlh, IFLA_INFO_KIND, "gtp");
+	nest2 = mnl_attr_nest_start(nlh, IFLA_INFO_DATA);
+	mnl_attr_put_u32(nlh, IFLA_GTP_LOCAL_ADDR_IPV4, 0);
+	mnl_attr_put_u32(nlh, IFLA_GTP_HASHSIZE, 131072);
+	mnl_attr_nest_end(nlh, nest2);
+	mnl_attr_nest_end(nlh, nest);
+
+	nl = mnl_socket_open(NETLINK_ROUTE);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		return -1;
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		return -1;
+	}
+	portid = mnl_socket_get_portid(nl);
+
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len,
+			  sizeof(struct ifinfomsg));
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		return -1;
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret == -1) {
+		perror("read");
+		return -1;
+	}
+
+	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+	if (ret == -1){
+		perror("callback");
+		return -1;
+	}
+
+	mnl_socket_close(nl);
+
+	return 0;
+}
+EXPORT_SYMBOL(gtp_dev_create);
diff --git a/src/internal.h b/src/internal.h
new file mode 100644
index 0000000..3a88d1a
--- /dev/null
+++ b/src/internal.h
@@ -0,0 +1,12 @@
+#ifndef INTERNAL_H
+#define INTERNAL_H 1
+
+#include "config.h"
+#ifdef HAVE_VISIBILITY_HIDDEN
+#	define __visible	__attribute__((visibility("default")))
+#	define EXPORT_SYMBOL(x)	typeof(x) (x) __visible
+#else
+#	define EXPORT_SYMBOL
+#endif
+
+#endif
diff --git a/src/libgtpnl.map b/src/libgtpnl.map
new file mode 100644
index 0000000..ead02e0
--- /dev/null
+++ b/src/libgtpnl.map
@@ -0,0 +1,13 @@
+LIBGTPNL_1.0 {
+global:
+  genl_socket_open;
+  genl_nlmsg_build_hdr;
+  genl_socket_talk;
+  genl_lookup_family;
+  gtp_dev_create;
+  gtp_add_tunnel;
+  gtp_del_tunnel;
+  gtp_list_tunnel;
+
+local: *;
+};
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..7880f3c
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,10 @@
+include $(top_srcdir)/Make_global.am
+
+check_PROGRAMS = gtp-link-add		\
+		 gtp-tunnel
+
+gtp_link_add_SOURCES = gtp-link-add.c gtpnl.c
+gtp_link_add_LDADD = ../src/libgtpnl.la ${LIBMNL_LIBS}
+
+gtp_tunnel_SOURCES = gtp-tunnel.c gtpnl.c
+gtp_tunnel_LDADD = ../src/libgtpnl.la ${LIBMNL_LIBS}
diff --git a/tools/gtp-link-add.c b/tools/gtp-link-add.c
new file mode 100644
index 0000000..c9c50c8
--- /dev/null
+++ b/tools/gtp-link-add.c
@@ -0,0 +1,82 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/if.h>
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+
+#include <linux/gtp_nl.h>
+
+int main(int argc, char *argv[])
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct ifinfomsg *ifm;
+	int ret;
+	unsigned int seq, portid, change = 0, flags = 0;
+	struct nlattr *nest, *nest2;
+
+	if (argc != 2) {
+		printf("Usage: %s [ifname]\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type	= RTM_NEWLINK;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+	nlh->nlmsg_seq = seq = time(NULL);
+	ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+	ifm->ifi_family = AF_INET;
+	ifm->ifi_change |= IFF_UP;
+	ifm->ifi_flags |= IFF_UP;
+
+	mnl_attr_put_u32(nlh, IFLA_LINK, if_nametoindex(argv[1]));
+	nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+	mnl_attr_put_str(nlh, IFLA_INFO_KIND, "gtp");
+	nest2 = mnl_attr_nest_start(nlh, IFLA_INFO_DATA);
+	mnl_attr_put_u32(nlh, IFLA_GTP_LOCAL_ADDR_IPV4, 0);
+	mnl_attr_put_u32(nlh, IFLA_GTP_HASHSIZE, 131072);
+	mnl_attr_nest_end(nlh, nest2);
+	mnl_attr_nest_end(nlh, nest);
+
+	nl = mnl_socket_open(NETLINK_ROUTE);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		exit(EXIT_FAILURE);
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		exit(EXIT_FAILURE);
+	}
+	portid = mnl_socket_get_portid(nl);
+
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len,
+			  sizeof(struct ifinfomsg));
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret == -1) {
+		perror("read");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+	if (ret == -1){
+		perror("callback");
+		exit(EXIT_FAILURE);
+	}
+
+	mnl_socket_close(nl);
+
+	return 0;
+}
diff --git a/tools/gtp-tunnel.c b/tools/gtp-tunnel.c
new file mode 100644
index 0000000..ec091db
--- /dev/null
+++ b/tools/gtp-tunnel.c
@@ -0,0 +1,214 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+
+#include <linux/gtp_nl.h>
+#include <libgtpnl/gtpnl.h>
+
+static int
+add_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
+{
+	uint32_t gtp_ifidx;
+	struct in_addr ms, sgsn;
+	struct nlmsghdr *nlh;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	uint32_t seq = time(NULL), gtp_version;
+
+	if (argc != 7) {
+		printf("%s add <gtp device> <v0|v1> <tid> <ms-addr> <sgsn-addr>\n",
+			argv[0]);
+		return EXIT_FAILURE;
+	}
+	gtp_ifidx = if_nametoindex(argv[2]);
+	if (gtp_ifidx == 0) {
+		fprintf(stderr, "wrong GTP interface %s\n", argv[2]);
+		return EXIT_FAILURE;
+	}
+
+	if (inet_aton(argv[5], &ms) < 0) {
+		perror("bad address for ms");
+		exit(EXIT_FAILURE);
+	}
+
+	if (inet_aton(argv[6], &sgsn) < 0) {
+		perror("bad address for sgsn");
+		exit(EXIT_FAILURE);
+	}
+
+	if (strcmp(argv[3], "v0") == 0)
+		gtp_version = GTP_V0;
+	else if (strcmp(argv[3], "v1") == 0)
+		gtp_version = GTP_V1;
+	else {
+		fprintf(stderr, "wrong GTP version %s, use v0 or v1\n",
+			argv[3]);
+		return EXIT_FAILURE;
+	}
+
+	nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_EXCL | NLM_F_ACK, ++seq,
+				   GTP_CMD_TUNNEL_NEW);
+	gtp_build_payload(nlh, atoi(argv[4]), gtp_ifidx, sgsn.s_addr,
+			  ms.s_addr, gtp_version);
+
+	if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0)
+		perror("genl_socket_talk");
+
+	return 0;
+}
+
+static int
+del_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
+{
+	uint32_t gtp_ifidx;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	uint32_t seq = time(NULL);
+
+	if (argc != 5) {
+		printf("%s add <gtp device> <version> <tid>\n",
+			argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	gtp_ifidx = if_nametoindex(argv[2]);
+
+	nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_ACK, ++seq,
+				   GTP_CMD_TUNNEL_DELETE);
+	gtp_build_payload(nlh, atoi(argv[4]), gtp_ifidx, 0, 0, atoi(argv[3]));
+
+	if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0)
+		perror("genl_socket_talk");
+
+	return 0;
+}
+
+struct gtp_pdp {
+	uint32_t	version;
+	uint64_t	tid;
+	struct in_addr	sgsn_addr;
+	struct in_addr	ms_addr;
+};
+
+static int genl_gtp_validate_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_OK;
+
+	switch(type) {
+	case GTPA_TID:
+		if (mnl_attr_validate(attr, MNL_TYPE_U64) < 0) {
+			perror("mnl_attr_validate");
+			return MNL_CB_ERROR;
+		}
+		break;
+	case GTPA_SGSN_ADDRESS:
+	case GTPA_MS_ADDRESS:
+	case GTPA_VERSION:
+		if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+			perror("mnl_attr_validate");
+			return MNL_CB_ERROR;
+		}
+		break;
+	default:
+		break;
+	}
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nlattr *tb[GTPA_MAX + 1] = {};
+	struct gtp_pdp pdp;
+	struct genlmsghdr *genl;
+
+	mnl_attr_parse(nlh, sizeof(*genl), genl_gtp_validate_cb, tb);
+	if (tb[GTPA_TID])
+		pdp.tid = mnl_attr_get_u64(tb[GTPA_TID]);
+	if (tb[GTPA_SGSN_ADDRESS]) {
+		pdp.sgsn_addr.s_addr =
+			mnl_attr_get_u32(tb[GTPA_SGSN_ADDRESS]);
+	}
+	if (tb[GTPA_MS_ADDRESS]) {
+		pdp.ms_addr.s_addr = mnl_attr_get_u32(tb[GTPA_MS_ADDRESS]);
+	}
+	if (tb[GTPA_VERSION]) {
+		pdp.version = mnl_attr_get_u32(tb[GTPA_VERSION]);
+	}
+
+	printf("version %u ", pdp.version);
+	printf("tid %llu ms_addr %s ", pdp.tid, inet_ntoa(pdp.sgsn_addr));
+	printf("sgsn_addr %s\n", inet_ntoa(pdp.ms_addr));
+
+	return MNL_CB_OK;
+}
+
+static int
+list_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	uint32_t seq = time(NULL);
+
+	nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_DUMP, 0,
+				   GTP_CMD_TUNNEL_GET);
+
+	if (genl_socket_talk(nl, nlh, seq, genl_gtp_attr_cb, NULL) < 0) {
+		perror("genl_socket_talk");
+		return 0;
+	}
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	unsigned int portid;
+	int32_t genl_id;
+	int ret;
+
+	if (argc < 2) {
+		printf("%s <add|delete|list> [<options,...>]\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	nl = genl_socket_open();
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		exit(EXIT_FAILURE);
+	}
+
+	genl_id = genl_lookup_family(nl, "gtp");
+	if (genl_id < 0) {
+		printf("not found gtp genl family\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (strncmp(argv[1], "add", strlen(argv[1])) == 0)
+		ret = add_tunnel(argc, argv, genl_id, nl);
+	else if (strncmp(argv[1], "delete", strlen(argv[1])) == 0)
+		ret = del_tunnel(argc, argv, genl_id, nl);
+	else if (strncmp(argv[1], "list", strlen(argv[1])) == 0)
+		ret = list_tunnel(argc, argv, genl_id, nl);
+	else {
+		printf("Unknown command `%s'\n", argv[1]);
+		exit(EXIT_FAILURE);
+	}
+
+	mnl_socket_close(nl);
+
+	return ret;
+}
diff --git a/tools/gtpnl.c b/tools/gtpnl.c
new file mode 100644
index 0000000..6b2f1e2
--- /dev/null
+++ b/tools/gtpnl.c
@@ -0,0 +1,13 @@
+#include <stdint.h>
+#include <libmnl/libmnl.h>
+#include <linux/gtp_nl.h>
+
+void gtp_build_payload(struct nlmsghdr *nlh, uint64_t tid, uint32_t ifidx,
+		       uint32_t sgsn_addr, uint32_t ms_addr, uint32_t version)
+{
+	mnl_attr_put_u32(nlh, GTPA_VERSION, version);
+	mnl_attr_put_u32(nlh, GTPA_LINK, ifidx);
+	mnl_attr_put_u32(nlh, GTPA_SGSN_ADDRESS, sgsn_addr);
+	mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, ms_addr);
+	mnl_attr_put_u64(nlh, GTPA_TID, tid);
+}