pespin has uploaded this change for review.

View Change

Implement logic to perform IPv6 SLAAC in GTPU tunnel

The IPv6 SLAAC prcoedure is not yet triggered in this patch; it will be
triggered by cups_client's new command in follow-up patch.

Change-Id: I837a4d7ec7c134412ab4a2e09909670e81ecbeea
---
M daemon/Makefile.am
A daemon/checksum.c
A daemon/checksum.h
M daemon/gtp_endpoint.c
M daemon/gtp_tunnel.c
A daemon/icmpv6.c
A daemon/icmpv6.h
M daemon/internal.h
M daemon/main.c
M daemon/tun_device.c
10 files changed, 605 insertions(+), 6 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-uecups refs/changes/56/42456/1
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 89afe7a..fe8b027 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -26,7 +26,9 @@
$(NULL)

noinst_HEADERS = \
+ checksum.h \
gtp.h \
+ icmpv6.h \
internal.h \
$(NULL)

@@ -35,6 +37,7 @@
$(NULL)

osmo_uecups_daemon_SOURCES = \
+ checksum.c \
cups_client.c \
utility.c \
tun_device.c \
@@ -42,5 +45,6 @@
gtp_endpoint.c \
gtp_tunnel.c \
daemon_vty.c \
+ icmpv6.c \
main.c \
$(NULL)
diff --git a/daemon/checksum.c b/daemon/checksum.c
new file mode 100644
index 0000000..4b23897
--- /dev/null
+++ b/daemon/checksum.c
@@ -0,0 +1,211 @@
+/*
+ *
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * IP/TCP/UDP checksumming routines
+ *
+ * Authors: Jorge Cwik, <jorge@laser.satlink.net>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Tom May, <ftom@netcom.com>
+ * Andreas Schwab, <schwab@issan.informatik.uni-dortmund.de>
+ * Lots of code moved from tcp.c and ip.c; see those files
+ * for more names.
+ *
+ * 03/02/96 Jes Sorensen, Andreas Schwab, Roman Hodek:
+ * Fixed some nasty bugs, causing some horrible crashes.
+ * A: At some points, the sum (%0) was used as
+ * length-counter instead of the length counter
+ * (%1). Thanks to Roman Hodek for pointing this out.
+ * B: GCC seems to mess up if one uses too many
+ * data-registers to hold input values and one tries to
+ * specify d0 and d1 as scratch registers. Letting gcc
+ * choose these registers itself solves the problem.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/* Revised by Kenneth Albanowski for m68knommu. Basic problem: unaligned access
+ kills, so most of the assembly has to go. */
+
+#if defined(__FreeBSD__)
+#define _KERNEL /* needed on FreeBSD 10.x for s6_addr32 */
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/endian.h>
+#endif
+
+#include "checksum.h"
+#include <arpa/inet.h>
+
+static inline unsigned short from32to16(unsigned int x)
+{
+ /* add up 16-bit and 16-bit for 16+c bit */
+ x = (x & 0xffff) + (x >> 16);
+ /* add up carry.. */
+ x = (x & 0xffff) + (x >> 16);
+ return x;
+}
+
+static unsigned int do_csum(const unsigned char *buff, int len)
+{
+ int odd;
+ unsigned int result = 0;
+
+ if (len <= 0)
+ goto out;
+ odd = 1 & (unsigned long) buff;
+ if (odd) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ result += (*buff << 8);
+#else
+ result = *buff;
+#endif
+ len--;
+ buff++;
+ }
+ if (len >= 2) {
+ if (2 & (unsigned long) buff) {
+ result += *(unsigned short *) buff;
+ len -= 2;
+ buff += 2;
+ }
+ if (len >= 4) {
+ const unsigned char *end = buff + ((unsigned)len & ~3);
+ unsigned int carry = 0;
+ do {
+ unsigned int w = *(unsigned int *) buff;
+ buff += 4;
+ result += carry;
+ result += w;
+ carry = (w > result);
+ } while (buff < end);
+ result += carry;
+ result = (result & 0xffff) + (result >> 16);
+ }
+ if (len & 2) {
+ result += *(unsigned short *) buff;
+ buff += 2;
+ }
+ }
+ if (len & 1)
+#if BYTE_ORDER == LITTLE_ENDIAN
+ result += *buff;
+#else
+ result += (*buff << 8);
+#endif
+ result = from32to16(result);
+ if (odd)
+ result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
+out:
+ return result;
+}
+
+/*
+ * This is a version of ip_compute_csum() optimized for IP headers,
+ * which always checksum on 4 octet boundaries.
+ */
+uint16_t ip_fast_csum(const void *iph, unsigned int ihl)
+{
+ return (uint16_t)~do_csum(iph, ihl*4);
+}
+
+/*
+ * computes the checksum of a memory block at buff, length len,
+ * and adds in "sum" (32-bit)
+ *
+ * returns a 32-bit number suitable for feeding into itself
+ * or csum_tcpudp_magic
+ *
+ * this function must be called with even lengths, except
+ * for the last fragment, which may be odd
+ *
+ * it's best to have buff aligned on a 32-bit boundary
+ */
+uint32_t csum_partial(const void *buff, int len, uint32_t wsum)
+{
+ unsigned int sum = (unsigned int)wsum;
+ unsigned int result = do_csum(buff, len);
+
+ /* add in old sum, and carry.. */
+ result += sum;
+ if (sum > result)
+ result += 1;
+ return (uint32_t)result;
+}
+
+/*
+ * this routine is used for miscellaneous IP-like checksums, mainly
+ * in icmp.c
+ */
+uint16_t ip_compute_csum(const void *buff, int len)
+{
+ return (uint16_t)~do_csum(buff, len);
+}
+
+uint16_t csum_ipv6_magic(const struct in6_addr *saddr,
+ const struct in6_addr *daddr,
+ uint32_t len, uint8_t proto, uint32_t csum)
+{
+ int carry;
+ uint32_t ulen;
+ uint32_t uproto;
+ uint32_t sum = (uint32_t)csum;
+
+ sum += (uint32_t)saddr->s6_addr32[0];
+ carry = (sum < (uint32_t)saddr->s6_addr32[0]);
+ sum += carry;
+
+ sum += (uint32_t)saddr->s6_addr32[1];
+ carry = (sum < (uint32_t)saddr->s6_addr32[1]);
+ sum += carry;
+
+ sum += (uint32_t)saddr->s6_addr32[2];
+ carry = (sum < (uint32_t)saddr->s6_addr32[2]);
+ sum += carry;
+
+ sum += (uint32_t)saddr->s6_addr32[3];
+ carry = (sum < (uint32_t)saddr->s6_addr32[3]);
+ sum += carry;
+
+ sum += (uint32_t)daddr->s6_addr32[0];
+ carry = (sum < (uint32_t)daddr->s6_addr32[0]);
+ sum += carry;
+
+ sum += (uint32_t)daddr->s6_addr32[1];
+ carry = (sum < (uint32_t)daddr->s6_addr32[1]);
+ sum += carry;
+
+ sum += (uint32_t)daddr->s6_addr32[2];
+ carry = (sum < (uint32_t)daddr->s6_addr32[2]);
+ sum += carry;
+
+ sum += (uint32_t)daddr->s6_addr32[3];
+ carry = (sum < (uint32_t)daddr->s6_addr32[3]);
+ sum += carry;
+
+ ulen = (uint32_t)htonl((uint32_t) len);
+ sum += ulen;
+ carry = (sum < ulen);
+ sum += carry;
+
+ uproto = (uint32_t)htonl(proto);
+ sum += uproto;
+ carry = (sum < uproto);
+ sum += carry;
+
+ return csum_fold((uint32_t)sum);
+}
+
+/* fold a partial checksum */
+uint16_t csum_fold(uint32_t csum)
+{
+ uint32_t sum = (uint32_t)csum;
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = (sum & 0xffff) + (sum >> 16);
+ return (uint16_t)~sum;
+}
diff --git a/daemon/checksum.h b/daemon/checksum.h
new file mode 100644
index 0000000..4b22431
--- /dev/null
+++ b/daemon/checksum.h
@@ -0,0 +1,13 @@
+#pragma once
+#include <stdint.h>
+#include <netinet/in.h>
+
+uint16_t ip_fast_csum(const void *iph, unsigned int ihl);
+uint32_t csum_partial(const void *buff, int len, uint32_t wsum);
+uint16_t ip_compute_csum(const void *buff, int len);
+
+uint16_t csum_ipv6_magic(const struct in6_addr *saddr,
+ const struct in6_addr *daddr,
+ uint32_t len, uint8_t proto, uint32_t csum);
+
+uint16_t csum_fold(uint32_t csum);
diff --git a/daemon/gtp_endpoint.c b/daemon/gtp_endpoint.c
index 3340aee..8d3c2b0 100644
--- a/daemon/gtp_endpoint.c
+++ b/daemon/gtp_endpoint.c
@@ -13,6 +13,9 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>

#include <pthread.h>

@@ -22,6 +25,7 @@
#include <osmocom/core/logging.h>

#include "gtp.h"
+#include "icmpv6.h"
#include "internal.h"

#define LOGEP(ep, lvl, fmt, args ...) \
@@ -41,6 +45,51 @@
* GTP Endpoint (UDP socket)
***********************************************************************/

+static void handle_router_adv(struct gtp_tunnel *t, struct ip6_hdr *ip6h, struct icmpv6_radv_hdr *ra, size_t ra_len)
+{
+ struct icmpv6_opt_hdr *opt_hdr;
+ struct icmpv6_opt_prefix *opt_prefix;
+ int rc;
+ struct in6_addr rm;
+ char ip6strbuf[2][INET6_ADDRSTRLEN];
+ memset(&rm, 0, sizeof(rm));
+
+ LOGT(t, LOGL_INFO, "Received ICMPv6 Router Advertisement\n");
+
+ foreach_icmpv6_opt(ra, ra_len, opt_hdr) {
+ if (opt_hdr->type == ICMPv6_OPT_TYPE_PREFIX_INFO) {
+ opt_prefix = (struct icmpv6_opt_prefix *)opt_hdr;
+ size_t prefix_len_bytes = (opt_prefix->prefix_len + 7)/8;
+ LOGT(t, LOGL_DEBUG, "Parsing OPT Prefix info (prefix_len=%u): %s\n",
+ opt_prefix->prefix_len,
+ osmo_hexdump((const unsigned char *)opt_prefix->prefix, prefix_len_bytes));
+
+ memcpy(&t->user_addr_ipv6_prefix.u.sin6.sin6_addr,
+ opt_prefix->prefix,
+ prefix_len_bytes);
+ memset(&((uint8_t *)&t->user_addr_ipv6_prefix.u.sin6.sin6_addr)[prefix_len_bytes],
+ 0, 16 - prefix_len_bytes);
+
+ /* Pick second address in the prefix: */
+ memcpy(&t->user_addr.u.sin6.sin6_addr,
+ &t->user_addr_ipv6_prefix.u.sin6.sin6_addr,
+ sizeof(t->user_addr_ipv6_prefix.u.sin6.sin6_addr));
+ ((uint8_t *)&t->user_addr.u.sin6.sin6_addr)[15] = 2;
+
+ LOGT(t, LOGL_INFO, "Adding global IPv6 prefix %s/%u address %s\n",
+ inet_ntop(AF_INET6, &t->user_addr_ipv6_prefix.u.sin6.sin6_addr, &ip6strbuf[0][0], sizeof(ip6strbuf[0])),
+ opt_prefix->prefix_len,
+ inet_ntop(AF_INET6, &t->user_addr.u.sin6.sin6_addr, &ip6strbuf[1][0], sizeof(ip6strbuf[1])));
+
+ if ((rc = osmo_netdev_add_addr(t->tun_dev->netdev, &t->user_addr, 64)) < 0) {
+ LOGT(t, LOGL_ERROR, "Cannot add global IPv6 user addr %s to tun device: %s\n",
+ inet_ntop(AF_INET6, &t->user_addr.u.sin6.sin6_addr, &ip6strbuf[1][0], sizeof(ip6strbuf[1])),
+ strerror(-rc));
+ }
+ }
+ }
+}
+
static void handle_gtp1u(struct gtp_endpoint *ep, const uint8_t *buffer, unsigned int nread)
{
struct gtp_daemon *d = ep->d;
@@ -50,6 +99,7 @@
int rc, outfd;
uint32_t teid;
uint16_t gtp_len;
+ char ip6strbuf[200];

if (nread < sizeof(*gtph)) {
LOGEP_NC(ep, LOGL_NOTICE, "Short read: %u < %lu\n", nread, sizeof(*gtph));
@@ -114,6 +164,54 @@
return;
}
outfd = t->tun_dev->fd;
+
+ struct iphdr *iph = (struct iphdr *)payload;
+ struct ip6_hdr *ip6h;
+ struct icmpv6_radv_hdr *ra;
+ switch (iph->version) {
+ case 4:
+ if (t->user_addr.u.sa.sa_family != AF_INET) {
+ LOGT(t, LOGL_NOTICE, "Rx GTPU payload for unexpected IPv4 %s in non-IPv4 PDP Context\n",
+ inet_ntop(AF_INET, &iph->daddr, ip6strbuf, sizeof(ip6strbuf)));
+ goto unlock_ret;
+ }
+ if (memcmp(&iph->daddr, &t->user_addr.u.sin.sin_addr, 4) != 0) {
+ LOGT(t, LOGL_NOTICE, "Rx GTPU payload for unknown dst IP addr %s\n",
+ inet_ntop(AF_INET, &iph->daddr, ip6strbuf, sizeof(ip6strbuf)));
+ goto unlock_ret;
+ }
+ break;
+ case 6:
+ ip6h = (struct ip6_hdr *)payload;
+ if (t->user_addr.u.sa.sa_family != AF_INET6) {
+ LOGT(t, LOGL_NOTICE, "Rx GTPU payload for unexpected IPv6 %s in non-IPv6 PDP Context\n",
+ inet_ntop(AF_INET6, &ip6h->ip6_dst, ip6strbuf, sizeof(ip6strbuf)));
+ goto unlock_ret;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&ip6h->ip6_dst)) {
+ if (memcmp(&ip6h->ip6_dst, &t->user_addr_ipv6_ll.u.sin6.sin6_addr, 16) != 0) {
+ LOGT(t, LOGL_NOTICE, "Rx GTPU payload for unknown link-local dst IP addr %s\n",
+ inet_ntop(AF_INET6, &ip6h->ip6_dst, ip6strbuf, sizeof(ip6strbuf)));
+ goto unlock_ret;
+ }
+ if ((ra = icmpv6_validate_router_adv(payload, gtp_len))) {
+ size_t ra_len = (uint8_t *)ra - (uint8_t *)payload;
+ handle_router_adv(t, (struct ip6_hdr *)payload, ra, ra_len);
+ goto unlock_ret;
+ }
+ /* Match by global IPv6 /64 prefix allocated through SLAAC: */
+ } else if (memcmp(&ip6h->ip6_dst, &t->user_addr.u.sin6.sin6_addr, 8) != 0) {
+ LOGT(t, LOGL_NOTICE, "Rx GTPU payload for unknown global dst IP addr %s\n",
+ inet_ntop(AF_INET6, &ip6h->ip6_dst, ip6strbuf, sizeof(ip6strbuf)));
+ goto unlock_ret;
+ }
+ break;
+ default:
+ LOGT(t, LOGL_NOTICE, "Rx GTPU payload with unknown IP version %d\n", iph->version);
+ goto unlock_ret;
+ }
+
+ outfd = t->tun_dev->fd;
pthread_rwlock_unlock(&d->rwlock);

/* 3) write to TUN device */
@@ -122,6 +220,11 @@
LOGEP_NC(ep, LOGL_FATAL, "Error writing to tun device %s\n", strerror(errno));
exit(1);
}
+ return;
+
+unlock_ret:
+ pthread_rwlock_unlock(&d->rwlock);
+ return;
}

/* One thread for reading from each GTP/UDP socket (GTP decapsulation -> tun)
diff --git a/daemon/gtp_tunnel.c b/daemon/gtp_tunnel.c
index 4c6b2c5..8447614 100644
--- a/daemon/gtp_tunnel.c
+++ b/daemon/gtp_tunnel.c
@@ -14,10 +14,9 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>

-#include "internal.h"
+#include "icmpv6.h"

-#define LOGT(t, lvl, fmt, args ...) \
- LOGP(DGT, lvl, "%s: " fmt, (t)->name, ## args)
+#include "internal.h"

/***********************************************************************
* GTP Tunnel
@@ -58,7 +57,8 @@
t->rx_teid = cpars->rx_teid;
t->tx_teid = cpars->tx_teid;
memcpy(&t->exthdr, &cpars->exthdr, sizeof(t->exthdr));
- memcpy(&t->user_addr, &cpars->user_addr, sizeof(t->user_addr));
+ memcpy(&t->user_addr_ipv6_ll, &cpars->user_addr, sizeof(t->user_addr));
+ memcpy(&t->user_addr, &t->user_addr_ipv6_ll, sizeof(t->user_addr_ipv6_ll));
memcpy(&t->remote_udp, &cpars->remote_udp, sizeof(t->remote_udp));

if ((rc = osmo_netdev_add_addr(t->tun_dev->netdev, &t->user_addr, 32)) < 0) {
@@ -173,3 +173,40 @@

return rc;
}
+
+/* Called with d->rwlock locked, tx_gtp1u_pk() will unlock. */
+static int _gtp_tunnel_tx_icmpv6_rs(struct gtp_tunnel *t)
+{
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(t->user_addr.u.sa.sa_family == AF_INET6);
+
+ msg = icmpv6_construct_rs(&t->user_addr.u.sin6.sin6_addr);
+
+ pthread_rwlock_rdlock(&t->d->rwlock);
+ rc = tx_gtp1u_pkt(t, msg->head, msgb_data(msg), msgb_length(msg));
+ /* pthread_rwlock_unlock() was called inside tx_gtp1u_pkt(). */
+ if (rc < 0)
+ LOGT(t, LOGL_FATAL, "Error Writing to UDP socket: %s\n", strerror(errno));
+ msgb_free(msg);
+ return 0;
+}
+
+int gtp_tunnel_tx_icmpv6_rs(struct gtp_daemon *d, const struct osmo_sockaddr *bind_addr, uint32_t rx_teid)
+{
+ struct gtp_endpoint *ep;
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ ep = _gtp_endpoint_find(d, bind_addr);
+ if (ep) {
+ /* find tunnel for rx TEID within endpoint */
+ struct gtp_tunnel *t = _gtp_tunnel_find_r(d, rx_teid, ep);
+ if (t) {
+ return _gtp_tunnel_tx_icmpv6_rs(t);
+ /* pthread_rwlock_unlock() was called inside _gtp_tunnel_tx_icmpv6_rs()->tx_gtp1u_pkt(). */
+ }
+ }
+ pthread_rwlock_unlock(&d->rwlock);
+ return -ENOENT;
+}
diff --git a/daemon/icmpv6.c b/daemon/icmpv6.c
new file mode 100644
index 0000000..39e80b9
--- /dev/null
+++ b/daemon/icmpv6.c
@@ -0,0 +1,117 @@
+/* Minimal ICMPv6 code for generating router advertisements as required by
+ * relevant 3GPP specs for a GGSN with IPv6 PDP contexts */
+
+/* (C) 2017 by 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
+ * notice and this permission notice is included in all copies or
+ * substantial portions of the software.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <netinet/in.h>
+#if defined(__FreeBSD__)
+#include <sys/types.h> /* FreeBSD 10.x needs this before ip6.h */
+#include <sys/endian.h>
+#endif
+#include <netinet/ip6.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+
+#include "checksum.h"
+#include "icmpv6.h"
+#include "internal.h"
+
+/* 29.061 11.2.1.3.4 IPv6 Router Configuration Variables in GGSN */
+#define GGSN_MaxRtrAdvInterval 21600 /* 6 hours */
+#define GGSN_MinRtrAdvInterval 16200 /* 4.5 hours */
+#define GGSN_AdvValidLifetime 0xffffffff /* infinite */
+#define GGSN_AdvPreferredLifetime 0xffffffff /* infinite */
+
+/* RFC3307 link-local scope multicast address */
+const struct in6_addr all_router_mcast_addr = {
+ .s6_addr = { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }
+};
+
+/* Prepends the ipv6 header and returns checksum content */
+uint16_t icmpv6_prepend_ip6hdr(struct msgb *msg, const struct in6_addr *saddr,
+ const struct in6_addr *daddr)
+{
+ uint32_t len;
+ uint16_t skb_csum;
+ struct ip6_hdr *i6h;
+
+ /* checksum */
+ skb_csum = csum_partial(msgb_data(msg), msgb_length(msg), 0);
+ len = msgb_length(msg);
+ skb_csum = csum_ipv6_magic(saddr, daddr, len, IPPROTO_ICMPV6, skb_csum);
+
+ /* Push IPv6 header in front of ICMPv6 packet */
+ i6h = (struct ip6_hdr *) msgb_push(msg, sizeof(*i6h));
+ /* 4 bits version, 8 bits TC, 20 bits flow-ID */
+ i6h->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000);
+ i6h->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len);
+ i6h->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
+ i6h->ip6_ctlun.ip6_un1.ip6_un1_hlim = 255;
+ i6h->ip6_src = *saddr;
+ i6h->ip6_dst = *daddr;
+ return skb_csum;
+}
+
+/*! construct a RFC4861 compliant ICMPv6 router soliciation
+ * \param[in] saddr Source IPv6 address for router advertisement
+ * \param[in] daddr Destination IPv6 address for router advertisement IPv6 header
+ * \param[in] prefix The single prefix to be advertised (/64 implied!)
+ * \returns callee-allocated message buffer containing router advertisement */
+struct msgb *icmpv6_construct_rs(const struct in6_addr *saddr)
+{
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "IPv6 RS");
+ struct icmpv6_rsol_hdr *rs;
+ OSMO_ASSERT(msg);
+ rs = (struct icmpv6_rsol_hdr *) msgb_put(msg, sizeof(*rs));
+ rs->hdr.type = 133; /* see RFC4861 4.1 */
+ rs->hdr.code = 0; /* see RFC4861 4.1 */
+ rs->hdr.csum = 0; /* updated below */
+ rs->reserved = 0; /* see RFC4861 4.1 */
+
+ rs->hdr.csum = icmpv6_prepend_ip6hdr(msg, saddr, &all_router_mcast_addr);
+
+ return msg;
+}
+
+/* Validate an ICMPv6 router advertisement according to RFC4861 6.1.2.
+ Returns pointer packet header on success, NULL otherwise. */
+struct icmpv6_radv_hdr *icmpv6_validate_router_adv(const uint8_t *pack, unsigned len)
+{
+ const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
+ const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h));
+
+ /* ICMP length (derived from IP length) is 16 or more octets */
+ if (len < sizeof(*ip6h) + 16)
+ return NULL;
+
+ if (ic6h->type != 134) /* router advertismenet type */
+ return NULL;
+
+ /*Routers must use their link-local address */
+ if (!IN6_IS_ADDR_LINKLOCAL(&ip6h->ip6_src))
+ return NULL;
+ /* Hop limit field must have 255 */
+ if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_hlim != 255)
+ return NULL;
+ /* ICMP Code is 0 */
+ if (ic6h->code != 0)
+ return NULL;
+ /* ICMP length (derived from IP length) is 16 or more octets */
+ if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_plen < 16)
+ return NULL;
+ /* FIXME: All included options have a length > 0 */
+ /* FIXME: If IP source is unspecified, no source link-layer addr option */
+ return (struct icmpv6_radv_hdr *)ic6h;
+}
diff --git a/daemon/icmpv6.h b/daemon/icmpv6.h
new file mode 100644
index 0000000..751f0a9
--- /dev/null
+++ b/daemon/icmpv6.h
@@ -0,0 +1,100 @@
+#pragma once
+
+#include <stdbool.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/endian.h>
+
+#include "gtp.h"
+
+#define ICMPv6_OPT_TYPE_PREFIX_INFO 0x03
+
+#define foreach_icmpv6_opt(icmpv6_pkt, icmpv6_len, opt_hdr) \
+ for (opt_hdr = (struct icmpv6_opt_hdr *)(icmpv6_pkt)->options; \
+ (uint8_t *)(opt_hdr) + sizeof(struct icmpv6_opt_hdr) <= (((uint8_t *)(icmpv6_pkt)) + (icmpv6_len)); \
+ opt_hdr = (struct icmpv6_opt_hdr *)((uint8_t *)(opt_hdr) + (opt_hdr)->len) \
+ )
+
+struct icmpv6_hdr {
+ uint8_t type;
+ uint8_t code;
+ uint16_t csum;
+} __attribute__ ((packed));
+
+struct icmpv6_echo_hdr {
+ struct icmpv6_hdr hdr;
+ uint16_t ident; /* Identifier */
+ uint16_t seq; /* Sequence number */
+ uint8_t data[0]; /* Data */
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.1 */
+struct icmpv6_rsol_hdr {
+ struct icmpv6_hdr hdr;
+ uint32_t reserved;
+ uint8_t options[0];
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.2 */
+struct icmpv6_radv_hdr {
+ struct icmpv6_hdr hdr;
+ uint8_t cur_ho_limit;
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t res:6,
+ m:1,
+ o:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t o:1, m:1, res:6;
+#endif
+ uint16_t router_lifetime;
+ uint32_t reachable_time;
+ uint32_t retrans_timer;
+ uint8_t options[0];
+} __attribute__ ((packed));
+
+
+/* RFC4861 Section 4.6 */
+struct icmpv6_opt_hdr {
+ uint8_t type;
+ /* length in units of 8 octets, including type+len! */
+ uint8_t len;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.6.2 */
+struct icmpv6_opt_prefix {
+ struct icmpv6_opt_hdr hdr;
+ uint8_t prefix_len;
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t res:6,
+ a:1,
+ l:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t l:1, a:1, res:6;
+#endif
+ uint32_t valid_lifetime;
+ uint32_t preferred_lifetime;
+ uint32_t res2;
+ uint8_t prefix[16];
+} __attribute__ ((packed));
+
+/* RFC4861 Section 4.6.4 */
+struct icmpv6_opt_mtu {
+ struct icmpv6_opt_hdr hdr;
+ uint16_t reserved;
+ uint32_t mtu;
+} __attribute__ ((packed));
+
+uint16_t icmpv6_prepend_ip6hdr(struct msgb *msg, const struct in6_addr *saddr,
+ const struct in6_addr *daddr);
+
+struct msgb *icmpv6_construct_rs(const struct in6_addr *saddr);
+
+struct icmpv6_radv_hdr *icmpv6_validate_router_adv(const uint8_t *pack, unsigned len);
+
+
+/* RFC3307 link-local scope multicast address */
+extern const struct in6_addr all_router_mcast_addr;
diff --git a/daemon/internal.h b/daemon/internal.h
index ae2356b..2394e2c 100644
--- a/daemon/internal.h
+++ b/daemon/internal.h
@@ -14,6 +14,7 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/netdev.h>
+#include <osmocom/core/logging.h>

#include <osmocom/netif/stream.h>

@@ -35,6 +36,7 @@
DEP,
DGT,
DUECUPS,
+ DICMP6,
};


@@ -43,6 +45,7 @@
***********************************************************************/

struct gtp_daemon;
+struct gtp_tunnel;

/* local UDP socket for GTP communication */
struct gtp_endpoint {
@@ -199,6 +202,8 @@
uint32_t rx_teid;

/* End user Address (inner IP) */
+ struct osmo_sockaddr user_addr_ipv6_ll;
+ struct osmo_sockaddr user_addr_ipv6_prefix;
struct osmo_sockaddr user_addr;

/* Remote UDP IP/Port*/
@@ -241,7 +246,12 @@

void _gtp_tunnel_destroy(struct gtp_tunnel *t);
bool gtp_tunnel_destroy(struct gtp_daemon *d, const struct osmo_sockaddr *bind_addr, uint32_t rx_teid);
+int gtp_tunnel_tx_icmpv6_rs(struct gtp_daemon *d, const struct osmo_sockaddr *bind_addr, uint32_t rx_teid);

+int tx_gtp1u_pkt(struct gtp_tunnel *t, uint8_t *base_buffer, const uint8_t *payload, unsigned int payload_len);
+
+#define LOGT(t, lvl, fmt, args ...) \
+ LOGP(DGT, lvl, "%s: " fmt, (t)->name, ## args)

/***********************************************************************
* GTP Daemon
diff --git a/daemon/main.c b/daemon/main.c
index b00e5d3..c604f02 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -100,7 +100,11 @@
.description = "UE Control User Plane Separation",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
-
+ [DICMP6] = {
+ .name = "DICMP6",
+ .description = "ICMPv6 (SLAAC) PDU Session setup",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
};

static const struct log_info log_info = {
diff --git a/daemon/tun_device.c b/daemon/tun_device.c
index c12c694..1178c7a 100644
--- a/daemon/tun_device.c
+++ b/daemon/tun_device.c
@@ -139,7 +139,7 @@
}

/* Note: This function is called with d->rwlock locked, and it's responsible of unlocking it before returning. */
-static int tx_gtp1u_pkt(struct gtp_tunnel *t, uint8_t *base_buffer, const uint8_t *payload, unsigned int payload_len)
+int tx_gtp1u_pkt(struct gtp_tunnel *t, uint8_t *base_buffer, const uint8_t *payload, unsigned int payload_len)
{
struct gtp1_header *gtph;
unsigned int head_len = payload - base_buffer;

To view, visit change 42456. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-uecups
Gerrit-Branch: master
Gerrit-Change-Id: I837a4d7ec7c134412ab4a2e09909670e81ecbeea
Gerrit-Change-Number: 42456
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin@sysmocom.de>