diff options
Diffstat (limited to 'ggsn/gtp-kernel.c')
-rw-r--r-- | ggsn/gtp-kernel.c | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/ggsn/gtp-kernel.c b/ggsn/gtp-kernel.c new file mode 100644 index 0000000..16e51ac --- /dev/null +++ b/ggsn/gtp-kernel.c @@ -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; +} |