aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2017-09-24 21:54:59 +0800
committerHarald Welte <laforge@gnumonks.org>2017-10-10 08:56:09 +0800
commit6748dc90b8c2a0ec57a5d05eb28c20a5002afbbc (patch)
treeacb0478e925f8cfa67349ac6d6948262b30ce423
parent7bd7b6815aa937bba12c1cd82a0abbb58aab1fc6 (diff)
sgsnemu: Add IPv6 support via tun device and "-t v6 --createif"
The idea is to only implement the GTP-C plane and configure the right link-local source address on the tun-device and let the regular (Linux) kernel take care of sending router solicitations and accepting/processing the related router advertisement. This avoids a lot of complexity in sgsnemu. For this to work, you must have /proc/sys/net/ipv6/conf/$tun/accept_ra set to either 1 (works only if no IPv6 forwarding/routing configured on your sgsnemu-running system) or 2 (works even if forwarding/routing is configured). Change-Id: I57e4c53ee648e1efecfba3eea592d1129849557c Closes: OS#2518
-rw-r--r--sgsnemu/sgsnemu.c73
1 files changed, 61 insertions, 12 deletions
diff --git a/sgsnemu/sgsnemu.c b/sgsnemu/sgsnemu.c
index c181603..fa295da 100644
--- a/sgsnemu/sgsnemu.c
+++ b/sgsnemu/sgsnemu.c
@@ -1,6 +1,7 @@
/*
* OsmoGGSN - Gateway GPRS Support Node
* Copyright (C) 2002, 2003, 2004 Mondru AB.
+ * Copyright (C) 2017 Harald Welte <laforge@gnumonks.org>
*
* The contents of this file may be used under the terms of the GNU
* General Public License Version 2, provided that the above copyright
@@ -30,6 +31,7 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
+#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/stat.h>
@@ -930,8 +932,18 @@ int process_options(int argc, char **argv)
/* PDP Type */
if (!strcmp(args_info.pdp_type_arg, "v6"))
options.pdp_type = PDP_EUA_TYPE_v6;
- else
+ else if (!strcmp(args_info.pdp_type_arg, "v4"))
options.pdp_type = PDP_EUA_TYPE_v4;
+ else {
+ SYS_ERR(DSGSN, LOGL_ERROR, 0, "Unsupported/unknown PDP Type '%s'\n",
+ args_info.pdp_type_arg);
+ return -1;
+ }
+
+ if (options.pingcount && options.pdp_type != PDP_EUA_TYPE_v4) {
+ SYS_ERR(DSGSN, LOGL_ERROR, 0, "built-in ping only works with IPv4, use tun-device");
+ return -1;
+ }
return 0;
@@ -1287,19 +1299,46 @@ int delete_context(struct pdp_t *pdp)
return 0;
}
+/* Link-Local address prefix fe80::/64 */
+static const uint8_t ll_prefix[] = { 0xfe,0x80, 0,0, 0,0, 0,0 };
+
/* Callback for receiving messages from tun */
int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
{
struct iphash_t *ipm;
struct in46_addr src;
struct iphdr *iph = (struct iphdr *)pack;
+ struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
- src.len = 4;
- src.v4.s_addr = iph->saddr;
+ if (iph->version == 4) {
+ if (len < sizeof(*iph) || len < 4*iph->ihl) {
+ printf("Dropping packet with too short IP header\n");
+ return 0;
+ }
+ src.len = 4;
+ src.v4.s_addr = iph->saddr;
+ } else if (iph->version == 6) {
+ /* We only have a single entry in the hash table, and it consists of the link-local
+ * address "fe80::prefix". So we need to make sure to convert non-link-local source
+ * addresses to that format before looking up the hash table via ippool_getip() */
+ src.len = 16;
+ if (!memcmp(ip6h->ip6_src.s6_addr, ll_prefix, sizeof(ll_prefix))) {
+ /* is a link-local address, we can do the hash lookup 1:1 */
+ src.v6 = ip6h->ip6_src;
+ } else {
+ /* it is not a link-local address, so we must convert from the /64 prefix
+ * to the link-local format that's used in the hash table */
+ memcpy(&src.v6.s6_addr[0], ll_prefix, sizeof(ll_prefix));
+ memcpy(&src.v6.s6_addr[sizeof(ll_prefix)], ip6h->ip6_src.s6_addr, 16-sizeof(ll_prefix));
+ }
+ } else {
+ printf("Dropping packet with invalid IP version %u\n", iph->version);
+ return 0;
+ }
if (ipget(&ipm, &src)) {
printf("Dropping packet from invalid source address: %s\n",
- inet_ntoa(src.v4));
+ in46a_ntoa(&src));
return 0;
}
@@ -1350,17 +1389,27 @@ int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause)
}
printf("Received create PDP context response. IP address: %s\n",
- inet_ntoa(addr.v4));
+ in46a_ntoa(&addr));
+
+ switch (addr.len) {
+ case 16: /* IPv6 */
+ /* we have to enable the kernel to perform stateless autoconfiguration,
+ * i.e. send a router solicitation using the lover 64bits of the allocated
+ * EUA as interface identifier, as per 3GPP TS 29.061 Section 11.2.1.3.2 */
+ memcpy(addr.v6.s6_addr, ll_prefix, sizeof(ll_prefix));
+ printf("Derived IPv6 link-local address: %s\n", in46a_ntoa(&addr));
+ break;
+ case 4: /* IPv4 */
+ break;
+ }
if ((options.createif) && (!options.net.s_addr)) {
- struct in_addr m;
-#ifdef HAVE_INET_ATON
- inet_aton("255.255.255.255", &m);
-#else
- m.s_addr = -1;
-#endif
+ size_t prefixlen = 32;
+ if (addr.len == 16)
+ prefixlen = 64;
/* printf("Setting up interface and routing\n"); */
- tun_addaddr(tun, &addr.v4, &addr.v4, &m);
+ /* FIXME: use tun_addattr() not tun_setaddr() */
+ tun_setaddr(tun, &addr, &addr, prefixlen);
if (options.defaultroute) {
struct in_addr rm;
rm.s_addr = 0;