| #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 <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(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 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, |
| size_t prefixlen, const char *net_arg) |
| { |
| if (gtp_dev_create(-1, GTP_DEVNAME, gsn->fd0, gsn->fd1u) < 0) { |
| SYS_ERR(DGGSN, LOGL_ERROR, 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(DGGSN, LOGL_ERROR, 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(DGGSN, LOGL_ERROR, 0, |
| "cannot lookup GTP genetlink ID\n"); |
| return -1; |
| } |
| if (debug) { |
| SYS_ERR(DGGSN, LOGL_NOTICE, 0, |
| "Using the GTP kernel mode (genl ID is %d)\n", |
| gtp_nl.genl_id); |
| } |
| |
| DEBUGP(DGGSN, "Setting route to reach %s via %s\n", |
| net_arg, GTP_DEVNAME); |
| |
| if (gtp_dev_config(GTP_DEVNAME, net, prefixlen) < 0) { |
| SYS_ERR(DGGSN, LOGL_ERROR, 0, |
| "Cannot add route to reach network %s\n", |
| 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, net_arg); |
| cmd[sizeof(cmd)-1] = '\0'; |
| |
| err = system(cmd); |
| if (err < 0) { |
| SYS_ERR(DGGSN, LOGL_ERROR, 0, |
| "Failed to launch script `%s'", ipup); |
| return -1; |
| } |
| } |
| SYS_ERR(DGGSN, LOGL_NOTICE, 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_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) |
| { |
| 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_i_tei(t, pdp->teid_own); |
| } |
| |
| 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; |
| } |