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, 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;