pespin has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-uecups/+/42456?usp=email )


Change subject: Implement logic to perform IPv6 SLAAC in GTPU tunnel
......................................................................

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, <[email protected]>
+ *             Arnt Gulbrandsen, <[email protected]>
+ *             Tom May, <[email protected]>
+ *             Andreas Schwab, <[email protected]>
+ *             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 <[email protected]>
+ *
+ * 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 https://gerrit.osmocom.org/c/osmo-uecups/+/42456?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: osmo-uecups
Gerrit-Branch: master
Gerrit-Change-Id: I837a4d7ec7c134412ab4a2e09909670e81ecbeea
Gerrit-Change-Number: 42456
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <[email protected]>

Reply via email to