9
0
Fork 0

ggsn: add support for GTP kernel data encapsulation

This patch adds the -g, --gtpnl=device option that allows you to
enable the GTP kernel tunneling mode in openggsn. You have to specify
the real downlink device that will be used to tunnel traffic, eg.

	-g=eth0

This means that the gtp0 device will be created and it will use eth0
as the real device to encapsulate packet coming from the Internet that
are addressed to the MS (so the tunnel devuce encapsulates these IP
packets in GTP packets when traveling to the SGSN).

Alternatively, you can also add this to the ggsn.conf configuration file:

	gtpnl eth0

The device has to be the real device that can route packets to the SGSN,
if you select the wrong device, the kernel routing code may not find a
way to reach the SSGN, you've been warned.

Therefore, if this option is set, the operational becomes the following:

1) A gtp0 device is created via rtnetlink and configure the socket
   encapsulation infrastructure in the kernel.
2) Whenever a PDP context is created, this adds the necessary tunnel
   configuration via genetlink GTP interface.
3) Whenever a PDP context is destroyed, this deletes the tunnel via
   genetlink GTP interface.
4) Destroy the gtp0 device if ggsn is stopped, including all of the
   existing tunnels.

You require the osmo-ggsn.git tree, which contains the kernel module
gtp.ko and the libgtpnl library that you have to compile and install.
Make sure you have loaded the gtp.ko kernel module before launching
the ggsn daemon using the kernel driver mode, otherwise you will get
a nice "operation not supported" error message ;-).

This patch also adds supports for "ipup" configuration option to invoke
an external script after the gtp0 device has been brought up. Typical
command to add the route to reach the MS behind the GGSN is required,
eg. ip route add 10.0.0.0/8 dev gtp0.

The (horrible) ggsn parser has been manually extended to support the
new configuration option. That code doesn't look nice, but it just
mimics what we already have there for consistency, please don't blame
me for that.

If you want to run in debugging mode, I suggest you to use:

	sudo ggsn -c ggsn.conf -f -d

Note that you do have to run openggsn as root to bring up the gtp0
device. You have to see this message that announce that the GTP kernel
mode is enabled.

openggsn[1106]: ggsn.c: 656: Using the GTP kernel mode (genl ID is 25)

This patch also automagically sets up route to reach MS from Internet
just like tun mode does. This is fundamental to get this working,
better don't leave to the admin, he may forget to add this route.

In this patch, I tried to encapsulate this new feature as much as
possible as Harald initially suggested.

To compile this feature, you have to pass --enable-gtp-kernel, ie.

./configire --enable-gtp-kernel

Otherwise, the code to interact with the gtp kernel part is not compiled.
This commit is contained in:
Pablo Neira Ayuso 2014-03-24 18:06:53 +01:00
parent 746b944ad6
commit 3f58223436
8 changed files with 383 additions and 18 deletions

View File

@ -45,6 +45,17 @@ AC_SUBST(EXEC_LDADD)
# FIXME: Replace `main' with a function in `-links':
#AC_CHECK_LIB([inks], [main])
dnl GTP kernel dependencies
AC_ARG_ENABLE([gtp-kernel],
AS_HELP_STRING([--enable-gtp-kernel], [Build GTP tunneling kernel]),
[enable_gtp_kernel="$enableval"], [enable_gtp_kernel="no"])
if test "x$enable_gtp_kernel" = "xyes"; then
PKG_CHECK_MODULES([LIBGTPNL], [libgtpnl >= 1.0.0])
fi
AM_CONDITIONAL([ENABLE_GTP_KERNEL], [test "$enable_gtp_kernel" = "yes"])
# Checks for header files.
AC_HEADER_STDC
AC_HEADER_SYS_WAIT
@ -116,3 +127,7 @@ AC_CONFIG_FILES([Makefile
libgtp.pc
openggsn.spec])
AC_OUTPUT
echo "
openggsn Configuration:
GTP kernel support: ${enable_gtp_kernel}"

View File

@ -4,7 +4,15 @@ AM_LDFLAGS = @EXEC_LDFLAGS@
AM_CFLAGS = -O2 -D_GNU_SOURCE -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb
if ENABLE_GTP_KERNEL
AM_CFLAGS += -DGTP_KERNEL
ggsn_LDADD = @EXEC_LDADD@ -lgtp -lgtpnl -L../gtp ../lib/libmisc.a
else
ggsn_LDADD = @EXEC_LDADD@ -lgtp -L../gtp ../lib/libmisc.a
endif
ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a
ggsn_SOURCES = ggsn.c cmdline.c cmdline.h
ggsn_SOURCES = ggsn.c cmdline.c cmdline.h gtp-kernel.h
if ENABLE_GTP_KERNEL
ggsn_SOURCES += gtp-kernel.c
endif

View File

@ -1,5 +1,5 @@
/*
File autogenerated by gengetopt version 2.22.6
File autogenerated by gengetopt version 2.22.5
generated with the following command:
gengetopt --conf-parser
@ -29,8 +29,6 @@ const char *gengetopt_args_info_purpose = "";
const char *gengetopt_args_info_usage = "Usage: " CMDLINE_PARSER_PACKAGE " [OPTIONS]...";
const char *gengetopt_args_info_versiontext = "";
const char *gengetopt_args_info_description = "";
const char *gengetopt_args_info_help[] = {
@ -39,8 +37,8 @@ const char *gengetopt_args_info_help[] = {
" -f, --fg Run in foreground (default=off)",
" -d, --debug Run in debug mode (default=off)",
" -c, --conf=STRING Read configuration file (default=`/etc/ggsn.conf')",
" --pidfile=STRING Filename of process id file\n (default=`/var/run/ggsn.pid')",
" --statedir=STRING Directory of nonvolatile data\n (default=`/var/lib/ggsn/')",
" --pidfile=STRING Filename of process id file \n (default=`/var/run/ggsn.pid')",
" --statedir=STRING Directory of nonvolatile data \n (default=`/var/lib/ggsn/')",
" -l, --listen=STRING Local interface",
" -n, --net=STRING Network (default=`192.168.0.0/24')",
" --ipup=STRING Script to run after link-up",
@ -53,6 +51,7 @@ const char *gengetopt_args_info_help[] = {
" -a, --apn=STRING Access point name (default=`internet')",
" -q, --qos=INT Requested quality of service (default=`0x0b921f')",
" --logfile=STRING Logfile for errors",
" -g, --gtpnl=STRING GTP kernel support (default=`eth0')",
0
};
@ -121,6 +120,7 @@ void clear_given (struct gengetopt_args_info *args_info)
args_info->apn_given = 0 ;
args_info->qos_given = 0 ;
args_info->logfile_given = 0 ;
args_info->gtpnl_given = 0 ;
}
static
@ -159,6 +159,8 @@ void clear_args (struct gengetopt_args_info *args_info)
args_info->qos_orig = NULL;
args_info->logfile_arg = NULL;
args_info->logfile_orig = NULL;
args_info->gtpnl_arg = gengetopt_strdup ("eth0");
args_info->gtpnl_orig = NULL;
}
@ -186,6 +188,7 @@ void init_args_info(struct gengetopt_args_info *args_info)
args_info->apn_help = gengetopt_args_info_help[16] ;
args_info->qos_help = gengetopt_args_info_help[17] ;
args_info->logfile_help = gengetopt_args_info_help[18] ;
args_info->gtpnl_help = gengetopt_args_info_help[19] ;
}
@ -195,9 +198,6 @@ cmdline_parser_print_version (void)
printf ("%s %s\n",
(strlen(CMDLINE_PARSER_PACKAGE_NAME) ? CMDLINE_PARSER_PACKAGE_NAME : CMDLINE_PARSER_PACKAGE),
CMDLINE_PARSER_VERSION);
if (strlen(gengetopt_args_info_versiontext) > 0)
printf("\n%s\n", gengetopt_args_info_versiontext);
}
static void print_help_common(void) {
@ -297,6 +297,8 @@ cmdline_parser_release (struct gengetopt_args_info *args_info)
free_string_field (&(args_info->qos_orig));
free_string_field (&(args_info->logfile_arg));
free_string_field (&(args_info->logfile_orig));
free_string_field (&(args_info->gtpnl_arg));
free_string_field (&(args_info->gtpnl_orig));
@ -365,6 +367,8 @@ cmdline_parser_dump(FILE *outfile, struct gengetopt_args_info *args_info)
write_into_file(outfile, "qos", args_info->qos_orig, 0);
if (args_info->logfile_given)
write_into_file(outfile, "logfile", args_info->logfile_orig, 0);
if (args_info->gtpnl_given)
write_into_file(outfile, "gtpnl", args_info->gtpnl_orig, 0);
i = EXIT_SUCCESS;
@ -589,17 +593,19 @@ cmdline_parser_internal (
{
int c; /* Character of the parsed option. */
int error_occurred = 0;
int error = 0;
struct gengetopt_args_info local_args_info;
int override;
int initialize;
int check_required;
int check_ambiguity;
package_name = argv[0];
override = params->override;
initialize = params->initialize;
check_required = params->check_required;
check_ambiguity = params->check_ambiguity;
if (initialize)
@ -636,10 +642,11 @@ cmdline_parser_internal (
{ "apn", 1, NULL, 'a' },
{ "qos", 1, NULL, 'q' },
{ "logfile", 1, NULL, 0 },
{ "gtpnl", 1, NULL, 'g' },
{ 0, 0, 0, 0 }
};
c = getopt_long (argc, argv, "hVfdc:l:n:a:q:", long_options, &option_index);
c = getopt_long (argc, argv, "hVfdc:l:n:a:q:g:", long_options, &option_index);
if (c == -1) break; /* Exit from `while (1)' loop. */
@ -735,6 +742,18 @@ cmdline_parser_internal (
goto failure;
break;
case 'g': /* GTP kernel support. */
if (update_arg( (void *)&(args_info->gtpnl_arg),
&(args_info->gtpnl_orig), &(args_info->gtpnl_given),
&(local_args_info.gtpnl_given), optarg, 0, "eth0", ARG_STRING,
check_ambiguity, override, 0, 0,
"gtpnl", 'g',
additional_error))
goto failure;
break;
case 0: /* Long option with no short option */
/* Filename of process id file. */
@ -894,7 +913,7 @@ cmdline_parser_internal (
cmdline_parser_release (&local_args_info);
if ( error_occurred )
if ( error )
return (EXIT_FAILURE);
return 0;

View File

@ -32,4 +32,5 @@ option "timelimit" - "Exit after timelimit seconds" int default="0" no
option "apn" a "Access point name" string default="internet" no
option "qos" q "Requested quality of service" int default="0x0b921f" no
option "logfile" - "Logfile for errors" string no
option "gtpnl" g "GTP kernel support" string default="eth0" no

View File

@ -1,6 +1,6 @@
/** @file cmdline.h
* @brief The header file for the command line option parser
* generated by GNU Gengetopt version 2.22.6
* generated by GNU Gengetopt version 2.22.5
* http://www.gnu.org/software/gengetopt.
* DO NOT modify this file, since it can be overwritten
* @author GNU Gengetopt by Lorenzo Bettini */
@ -92,6 +92,9 @@ struct gengetopt_args_info
char * logfile_arg; /**< @brief Logfile for errors. */
char * logfile_orig; /**< @brief Logfile for errors original value given at command line. */
const char *logfile_help; /**< @brief Logfile for errors help description. */
char * gtpnl_arg; /**< @brief GTP kernel support (default='eth0'). */
char * gtpnl_orig; /**< @brief GTP kernel support original value given at command line. */
const char *gtpnl_help; /**< @brief GTP kernel support help description. */
unsigned int help_given ; /**< @brief Whether help was given. */
unsigned int version_given ; /**< @brief Whether version was given. */
@ -112,6 +115,7 @@ struct gengetopt_args_info
unsigned int apn_given ; /**< @brief Whether apn was given. */
unsigned int qos_given ; /**< @brief Whether qos was given. */
unsigned int logfile_given ; /**< @brief Whether logfile was given. */
unsigned int gtpnl_given ; /**< @brief Whether gtpnl was given. */
} ;
@ -129,8 +133,6 @@ struct cmdline_parser_params
extern const char *gengetopt_args_info_purpose;
/** @brief the usage string of the program */
extern const char *gengetopt_args_info_usage;
/** @brief the description string of the program */
extern const char *gengetopt_args_info_description;
/** @brief all the lines making the help output */
extern const char *gengetopt_args_info_help[];

View File

@ -53,6 +53,7 @@
#include "../gtp/pdp.h"
#include "../gtp/gtp.h"
#include "cmdline.h"
#include "gtp-kernel.h"
int end = 0;
int maxfd = 0; /* For select() */
@ -150,6 +151,13 @@ int delete_context(struct pdp_t *pdp)
ippool_freeip(ippool, (struct ippoolm_t *)pdp->peer);
else
sys_err(LOG_ERR, __FILE__, __LINE__, 0, "Peer not defined!");
if (gtp_kernel_tunnel_del(pdp)) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"Cannot delete tunnel from kernel: %s\n",
strerror(errno));
}
return 0;
}
@ -184,6 +192,11 @@ int create_context_ind(struct pdp_t *pdp)
pdp->ipif = tun; /* TODO */
member->peer = pdp;
if (gtp_kernel_tunnel_add(pdp) < 0) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"Cannot add tunnel to kernel: %s\n", strerror(errno));
}
gtp_create_context_resp(gsn, pdp, GTPCAUSE_ACC_REQ);
return 0; /* Success */
}
@ -275,6 +288,8 @@ int main(int argc, char **argv)
printf("pidfile: %s\n", args_info.pidfile_arg);
if (args_info.statedir_arg)
printf("statedir: %s\n", args_info.statedir_arg);
if (args_info.gtpnl_arg)
printf("gtpnl: %s\n", args_info.gtpnl_arg);
printf("timelimit: %d\n", args_info.timelimit_arg);
}
@ -318,6 +333,8 @@ int main(int argc, char **argv)
printf("pidfile: %s\n", args_info.pidfile_arg);
if (args_info.statedir_arg)
printf("statedir: %s\n", args_info.statedir_arg);
if (args_info.gtpnl_arg)
printf("gtpnl: %s\n", args_info.gtpnl_arg);
printf("timelimit: %d\n", args_info.timelimit_arg);
}
@ -517,10 +534,18 @@ int main(int argc, char **argv)
if (gsn->fd1u > maxfd)
maxfd = gsn->fd1u;
/* use GTP kernel module for data packet encapsulation */
if (gtp_kernel_init(gsn, &net, &mask, &args_info) < 0)
goto err;
gtp_set_cb_data_ind(gsn, encaps_tun);
gtp_set_cb_delete_context(gsn, delete_context);
gtp_set_cb_create_context_ind(gsn, create_context_ind);
/* skip the configuration of the tun0 if we're using the gtp0 device */
if (gtp_kernel_enabled())
goto skip_tun;
/* Create a tunnel interface */
if (debug)
printf("Creating tun interface\n");
@ -548,6 +573,8 @@ int main(int argc, char **argv)
if (ipup)
tun_runscript(tun, ipup);
skip_tun:
/******************************************************************/
/* Main select loop */
/******************************************************************/
@ -578,7 +605,7 @@ int main(int argc, char **argv)
break;
}
if (tun->fd != -1 && FD_ISSET(tun->fd, &fds) &&
if (tun && tun->fd != -1 && FD_ISSET(tun->fd, &fds) &&
tun_decaps(tun) < 0) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"TUN read failed (fd)=(%d)", tun->fd);
@ -594,11 +621,13 @@ int main(int argc, char **argv)
gtp_decaps1u(gsn);
}
err:
gtp_kernel_stop();
cmdline_parser_free(&args_info);
ippool_free(ippool);
gtp_free(gsn);
tun_free(tun);
if (tun)
tun_free(tun);
return 1;

240
ggsn/gtp-kernel.c Normal file
View File

@ -0,0 +1,240 @@
#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 <syslog.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.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 "cmdline.h"
#include <libgtpnl/gtp.h>
#include <libgtpnl/gtpnl.h>
#include <libmnl/libmnl.h>
#include "gtp-kernel.h"
static void pdp_debug(struct pdp_t *pdp)
{
int i;
uint64_t teid;
if (!debug)
return;
printf("version %u\n", pdp->version);
if (pdp->version == 0) {
teid = pdp_gettid(pdp->imsi, pdp->nsapi);
printf("flowid %u\n", pdp->flru);
} else {
teid = pdp->teid_gn; /* GTPIE_TEI_DI */
}
printf("teid %llx\n", teid);
printf("address (%u)\n", pdp->eua.l);
/* Byte 0: 0xf1 == IETF */
/* Byte 1: 0x21 == IPv4 */
/* Byte 2-6: IPv4 address */
for (i = 0; i < 6; i++)
printf("%x ", pdp->eua.v[i] & 0xff); /* GTPIE_EUA */
printf("\n");
printf("sgsn-addr (%u)\n", pdp->gsnrc.l);
for (i = 0; i < 4; i++)
printf("%x ", pdp->gsnrc.v[i] & 0xff); /* GTPIE_GSN_ADDR */
printf("\n");
}
static int mask2prefix(struct in_addr *mask)
{
uint32_t tmp = ntohl(mask->s_addr);
int k;
for (k=0; tmp > 0; k++)
tmp = (tmp << 1);
return k;
}
static struct {
int genl_id;
struct mnl_socket *nl;
bool enabled;
} gtp_nl;
/* Always forces the kernel to allocate gtp0. If it exists it hits EEXIST */
#define GTP_DEVNAME "gtp0"
int gtp_kernel_init(struct gsn_t *gsn, struct in_addr *net,
struct in_addr *mask,
struct gengetopt_args_info *args_info)
{
if (!args_info->gtpnl_given)
return 0;
if (gtp_dev_create(GTP_DEVNAME, args_info->gtpnl_orig,
gsn->fd0, gsn->fd1u) < 0) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"cannot create GTP tunnel device: %s\n",
strerror(errno));
return -1;
}
gtp_nl.enabled = true;
gtp_nl.nl = genl_socket_open();
if (gtp_nl.nl == NULL) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"cannot create genetlink socket\n");
return -1;
}
gtp_nl.genl_id = genl_lookup_family(gtp_nl.nl, "gtp");
if (gtp_nl.genl_id < 0) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"cannot lookup GTP genetlink ID\n");
return -1;
}
if (debug) {
sys_err(LOG_NOTICE, __FILE__, __LINE__, 0,
"Using the GTP kernel mode (genl ID is %d)\n",
gtp_nl.genl_id);
}
if (debug) {
printf("Setting route to reach %s via %s\n",
args_info->net_arg, GTP_DEVNAME);
}
if (gtp_dev_config(GTP_DEVNAME, net, mask2prefix(mask)) < 0) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"Cannot add route to reach network %s\n",
args_info->net_arg);
}
/* launch script if it is set to bring up the route to reach
* the MS, eg. ip ro add 10.0.0.0/8 dev gtp0. Better add this
* using native rtnetlink interface given that we know the
* MS network mask, later.
*/
if (ipup) {
char cmd[1024];
int err;
/* eg. /home/ggsn/ipup gtp0 10.0.0.0/8 */
snprintf(cmd, sizeof(cmd), "%s %s %s",
ipup, GTP_DEVNAME, args_info->net_arg);
cmd[sizeof(cmd)-1] = '\0';
err = system(cmd);
if (err < 0) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"Failed to launch script `%s'", ipup);
return -1;
}
}
sys_err(LOG_NOTICE, __FILE__, __LINE__, 0, "GTP kernel configured\n");
return 0;
}
void gtp_kernel_stop(void)
{
if (!gtp_nl.enabled)
return;
gtp_dev_destroy(GTP_DEVNAME);
}
int gtp_kernel_tunnel_add(struct pdp_t *pdp)
{
struct in_addr ms, sgsn;
struct gtp_tunnel *t;
int ret;
if (!gtp_nl.enabled)
return 0;
pdp_debug(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(GTP_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_tid(t, pdp->teid_gn); /* GTPIE_TEI_DI */
}
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)
{
struct gtp_tunnel *t;
int ret;
if (!gtp_nl.enabled)
return 0;
pdp_debug(pdp);
t = gtp_tunnel_alloc();
if (t == NULL)
return -1;
gtp_tunnel_set_ifidx(t, if_nametoindex(GTP_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_tid(t, pdp->teid_gn); /* GTPIE_TEI_DI */
}
ret = gtp_del_tunnel(gtp_nl.genl_id, gtp_nl.nl, t);
gtp_tunnel_free(t);
return ret;
}
int gtp_kernel_enabled(void)
{
return gtp_nl.enabled;
}

51
ggsn/gtp-kernel.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef _GTP_KERNEL_H_
#define _GTP_KERNEL_H_
struct gengetopt_args_info;
extern int debug;
extern char *ipup;
#ifdef GTP_KERNEL
int gtp_kernel_init(struct gsn_t *gsn, struct in_addr *net,
struct in_addr *mask,
struct gengetopt_args_info *args_info);
void gtp_kernel_stop(void);
int gtp_kernel_tunnel_add(struct pdp_t *pdp);
int gtp_kernel_tunnel_del(struct pdp_t *pdp);
int gtp_kernel_enabled(void);
#else
static inline int gtp_kernel_init(struct gsn_t *gsn, struct in_addr *net,
struct in_addr *mask,
struct gengetopt_args_info *args_info)
{
if (args_info->gtpnl_given) {
sys_err(LOG_ERR, __FILE__, __LINE__, 0,
"ggsn compiled without GTP kernel support!\n");
return -1;
}
return 0;
}
static inline void gtp_kernel_stop(void) {}
static inline int gtp_kernel_tunnel_add(struct pdp_t *pdp)
{
return 0;
}
static inline int gtp_kernel_tunnel_del(struct pdp_t *pdp)
{
return 0;
}
static inline int gtp_kernel_enabled(void)
{
return 0;
}
#endif
#endif /* _GTP_KERNEL_H_ */