aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/checksum.c211
-rw-r--r--lib/checksum.h13
-rw-r--r--lib/debug.c4
-rw-r--r--lib/gtp-kernel.c17
-rw-r--r--lib/icmpv6.c242
-rw-r--r--lib/icmpv6.h100
-rw-r--r--lib/in46_addr.c6
-rw-r--r--lib/in46_addr.h8
-rw-r--r--lib/netdev.c79
-rw-r--r--lib/netdev.h6
-rw-r--r--lib/netns.c272
-rw-r--r--lib/netns.h35
-rw-r--r--lib/tun.c2
-rw-r--r--lib/util.c35
-rw-r--r--lib/util.h18
16 files changed, 1033 insertions, 19 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index b6e7aba..5bd9443 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,10 +1,10 @@
noinst_LIBRARIES = libmisc.a
-noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h
+noinst_HEADERS = gnugetopt.h ippool.h lookup.h syserr.h tun.h in46_addr.h netdev.h gtp-kernel.h netns.h util.h icmpv6.h checksum.h
AM_CFLAGS = -O2 -fno-builtin -Wall -DSBINDIR='"$(sbindir)"' -ggdb $(LIBOSMOCORE_CFLAGS)
-libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c
+libmisc_a_SOURCES = getopt1.c getopt.c ippool.c lookup.c tun.c debug.c in46_addr.c netdev.c netns.c util.c icmpv6.c checksum.c
if ENABLE_GTP_KERNEL
AM_CFLAGS += -DGTP_KERNEL $(LIBGTPNL_CFLAGS)
diff --git a/lib/checksum.c b/lib/checksum.c
new file mode 100644
index 0000000..4b23897
--- /dev/null
+++ b/lib/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/lib/checksum.h b/lib/checksum.h
new file mode 100644
index 0000000..4b22431
--- /dev/null
+++ b/lib/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/lib/debug.c b/lib/debug.c
index 83423dc..c79e493 100644
--- a/lib/debug.c
+++ b/lib/debug.c
@@ -24,12 +24,12 @@ static const struct log_info_cat default_categories[] = {
[DSGSN] = {
.name = "DSGSN",
.description = "SGSN Emulator",
- .enabled = 1, .loglevel = LOGL_NOTICE,
+ .enabled = 1, .loglevel = LOGL_INFO,
},
[DICMP6] = {
.name = "DICMP6",
.description = "ICMPv6",
- .enabled = 1, .loglevel = LOGL_DEBUG,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
};
diff --git a/lib/gtp-kernel.c b/lib/gtp-kernel.c
index 48811bc..f6df408 100644
--- a/lib/gtp-kernel.c
+++ b/lib/gtp-kernel.c
@@ -26,6 +26,8 @@
#include "../lib/tun.h"
#include "../lib/syserr.h"
+#include "../lib/util.h"
+#include "../lib/ippool.h"
#include "../gtp/pdp.h"
#include "../gtp/gtp.h"
@@ -37,16 +39,23 @@
static void pdp_debug(const char *prefix, const char *devname, struct pdp_t *pdp)
{
- struct in46_addr ia46;
+ char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN];
+ struct ippoolm_t *peer;
struct in_addr ia;
- in46a_from_eua(&pdp->eua, &ia46);
+ buf4[0] = '\0';
+ if ((peer = pdp_get_peer_ipv(pdp, false)))
+ in46a_ntop(&peer->addr, buf4, sizeof(buf4));
+ buf6[0] = '\0';
+ if ((peer = pdp_get_peer_ipv(pdp, true)))
+ in46a_ntop(&peer->addr, buf6, sizeof(buf6));
+
gsna2in_addr(&ia, &pdp->gsnrc);
- LOGPDPX(DGGSN, LOGL_DEBUG, pdp, "%s %s v%u TEID %"PRIx64" EUA=%s SGSN=%s\n", prefix,
+ LOGPDPX(DGGSN, LOGL_DEBUG, pdp, "%s %s v%u TEID %"PRIx64" EUA=(%s,%s) SGSN=%s\n", prefix,
devname, pdp->version,
pdp->version == 0 ? pdp_gettid(pdp->imsi, pdp->nsapi) : pdp->teid_gn,
- in46a_ntoa(&ia46), inet_ntoa(ia));
+ buf4, buf6, inet_ntoa(ia));
}
static struct {
diff --git a/lib/icmpv6.c b/lib/icmpv6.c
new file mode 100644
index 0000000..ac1474d
--- /dev/null
+++ b/lib/icmpv6.c
@@ -0,0 +1,242 @@
+/* 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 "checksum.h"
+
+#include "../gtp/gtp.h"
+#include "../gtp/pdp.h"
+#include "ippool.h"
+#include "syserr.h"
+#include "icmpv6.h"
+#include "config.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;
+}
+/*! construct a 3GPP 29.061 compliant router advertisement for a given prefix
+ * \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 */
+static struct msgb *icmpv6_construct_ra(const struct in6_addr *saddr,
+ const struct in6_addr *daddr,
+ const struct in6_addr *prefix)
+{
+ struct msgb *msg = msgb_alloc_headroom(512,128, "IPv6 RA");
+ struct icmpv6_radv_hdr *ra;
+ struct icmpv6_opt_prefix *ra_opt_pref;
+
+ OSMO_ASSERT(msg);
+
+ ra = (struct icmpv6_radv_hdr *) msgb_put(msg, sizeof(*ra));
+ ra->hdr.type = 134; /* see RFC4861 4.2 */
+ ra->hdr.code = 0; /* see RFC4861 4.2 */
+ ra->hdr.csum = 0; /* updated below */
+ ra->cur_ho_limit = 64; /* seems reasonable? */
+ /* the GGSN shall leave the M-flag cleared in the Router
+ * Advertisement messages */
+ ra->m = 0;
+ /* The GGSN may set the O-flag if there are additional
+ * configuration parameters that need to be fetched by the MS */
+ ra->o = 0; /* no DHCPv6 */
+ ra->res = 0;
+ /* RFC4861 Default: 3 * MaxRtrAdvInterval */
+ ra->router_lifetime = htons(3*GGSN_MaxRtrAdvInterval);
+ ra->reachable_time = 0; /* Unspecified */
+
+ /* RFC4861 Section 4.6.2 */
+ ra_opt_pref = (struct icmpv6_opt_prefix *) msgb_put(msg, sizeof(*ra_opt_pref));
+ ra_opt_pref->hdr.type = 3; /* RFC4861 4.6.2 */
+ ra_opt_pref->hdr.len = 4; /* RFC4861 4.6.2 */
+ ra_opt_pref->prefix_len = 64; /* only prefix length as per 3GPP */
+ /* The Prefix is contained in the Prefix Information Option of
+ * the Router Advertisements and shall have the A-flag set
+ * and the L-flag cleared */
+ ra_opt_pref->a = 1;
+ ra_opt_pref->l = 0;
+ ra_opt_pref->res = 0;
+ /* The lifetime of the prefix shall be set to infinity */
+ ra_opt_pref->valid_lifetime = htonl(GGSN_AdvValidLifetime);
+ ra_opt_pref->preferred_lifetime = htonl(GGSN_AdvPreferredLifetime);
+ ra_opt_pref->res2 = 0;
+ memcpy(ra_opt_pref->prefix, prefix, sizeof(ra_opt_pref->prefix));
+
+ /* checksum */
+ ra->hdr.csum = icmpv6_prepend_ip6hdr(msg, saddr, daddr);
+
+ return msg;
+}
+
+/* Validate an ICMPv6 router solicitation according to RFC4861 6.1.1 */
+static bool icmpv6_validate_router_solicit(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));
+
+ /* Hop limit field must have 255 */
+ if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_hlim != 255)
+ return false;
+ /* FIXME: ICMP checksum is valid */
+ /* ICMP length (derived from IP length) is 8 or more octets */
+ if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_plen < 8)
+ return false;
+ /* FIXME: All included options have a length > 0 */
+ /* FIXME: If IP source is unspecified, no source link-layer addr option */
+ return true;
+}
+
+/* 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;
+}
+
+/* handle incoming packets to the all-routers multicast address */
+int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp,
+ const struct in6_addr *pdp_prefix,
+ const struct in6_addr *own_ll_addr,
+ 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));
+ struct msgb *msg;
+
+ if (len < sizeof(*ip6h)) {
+ LOGP(DICMP6, LOGL_NOTICE, "Packet too short: %u bytes\n", len);
+ return -1;
+ }
+
+ /* we only treat ICMPv6 here */
+ if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_ICMPV6) {
+ LOGP(DICMP6, LOGL_DEBUG, "Ignoring non-ICMP to all-routers mcast\n");
+ return 0;
+ }
+
+ if (len < sizeof(*ip6h) + sizeof(*ic6h)) {
+ LOGP(DICMP6, LOGL_NOTICE, "Short ICMPv6 packet: %s\n", osmo_hexdump(pack, len));
+ return -1;
+ }
+
+ switch (ic6h->type) {
+ case 133: /* router solicitation */
+ if (ic6h->code != 0) {
+ LOGP(DICMP6, LOGL_NOTICE, "ICMPv6 type 133 but code %d\n", ic6h->code);
+ return -1;
+ }
+ if (!icmpv6_validate_router_solicit(pack, len)) {
+ LOGP(DICMP6, LOGL_NOTICE, "Invalid Router Solicitation: %s\n",
+ osmo_hexdump(pack, len));
+ return -1;
+ }
+ /* Send router advertisement from GGSN link-local
+ * address to MS link-local address, including prefix
+ * allocated to this PDP context */
+ msg = icmpv6_construct_ra(own_ll_addr, &ip6h->ip6_src, pdp_prefix);
+ /* Send the constructed RA to the MS */
+ gtp_data_req(gsn, pdp, msgb_data(msg), msgb_length(msg));
+ msgb_free(msg);
+ break;
+ default:
+ LOGP(DICMP6, LOGL_DEBUG, "Unknown ICMPv6 type %u\n", ic6h->type);
+ break;
+ }
+ return 0;
+}
diff --git a/lib/icmpv6.h b/lib/icmpv6.h
new file mode 100644
index 0000000..4b4dc37
--- /dev/null
+++ b/lib/icmpv6.h
@@ -0,0 +1,100 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/endian.h>
+
+#include "../gtp/gtp.h"
+#include "../gtp/pdp.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;
+#else
+ uint8_t m:1,
+ o: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;
+#else
+ 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));
+
+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);
+
+int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp,
+ const struct in6_addr *pdp_prefix,
+ const struct in6_addr *own_ll_addr,
+ const uint8_t *pack, unsigned len);
+
+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/lib/in46_addr.c b/lib/in46_addr.c
index f4bb8a2..2562c71 100644
--- a/lib/in46_addr.c
+++ b/lib/in46_addr.c
@@ -60,7 +60,11 @@ int in46a_to_sas(struct sockaddr_storage *out, const struct in46_addr *in)
return 0;
}
-/*! Convenience wrapper around inet_ntop() for \ref in46_addr */
+/*! Convenience wrapper around inet_ntop() for in46_addr.
+ * \param[in] in the in46_addr to print
+ * \param[out] dst destination buffer where string representation of the address is stored
+ * \param[out] dst_size size dst. Usually it should be at least INET6_ADDRSTRLEN.
+ * \return address of dst on success, NULL on error */
const char *in46a_ntop(const struct in46_addr *in, char *dst, socklen_t dst_size)
{
int af;
diff --git a/lib/in46_addr.h b/lib/in46_addr.h
index e4654cc..153df00 100644
--- a/lib/in46_addr.h
+++ b/lib/in46_addr.h
@@ -31,3 +31,11 @@ unsigned int in46a_netmasklen(const struct in46_addr *netmask);
int in46a_to_eua(const struct in46_addr *src, unsigned int size, struct ul66_t *eua);
int in46a_from_eua(const struct ul66_t *eua, struct in46_addr *dst);
+
+static inline bool in46a_is_v6(const struct in46_addr *addr) {
+ return addr->len == 8 || addr->len == 16;
+}
+
+static inline bool in46a_is_v4(const struct in46_addr *addr) {
+ return addr->len == sizeof(struct in_addr);
+}
diff --git a/lib/netdev.c b/lib/netdev.c
index 0011588..fd3caff 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -176,7 +176,7 @@ int netdev_setaddr4(const char *devname, struct in_addr *addr,
/* On linux the route to the interface is set automatically
on FreeBSD we have to do this manually */
#if defined(__FreeBSD__) || defined (__APPLE__)
- netdev_addroute(dstaddr, addr, &this->netmask);
+ netdev_addroute4(dstaddr, addr, &this->netmask);
#endif
return 0;
@@ -433,7 +433,7 @@ int netdev_addaddr6(const char *devname, struct in6_addr *addr,
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
req.n.nlmsg_type = RTM_NEWADDR;
req.i.ifa_family = AF_INET6;
- req.i.ifa_prefixlen = 64; /* 64 FOR IPv6 */
+ req.i.ifa_prefixlen = prefixlen; /* 64 FOR IPv6 */
req.i.ifa_flags = 0;
req.i.ifa_scope = RT_SCOPE_HOST; /* TODO or 0 */
req.i.ifa_index = if_nametoindex(devname);
@@ -553,7 +553,7 @@ int netdev_addaddr6(const char *devname, struct in6_addr *addr,
return 0;
}
-static int netdev_route(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask, int delete)
+static int netdev_route4(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask, int delete)
{
int fd;
#if defined(__linux__)
@@ -643,16 +643,81 @@ static int netdev_route(struct in_addr *dst, struct in_addr *gateway, struct in_
return 0;
}
-int netdev_addroute(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask)
+static int netdev_route6(struct in6_addr *dst, struct in6_addr *gateway, int prefixlen, const char *gw_iface, int delete)
{
- return netdev_route(dst, gateway, mask, 0);
+ int fd;
+#if defined(__linux__)
+ struct in6_rtmsg r;
+ struct ifreq ifr;
+
+ memset(&r, 0, sizeof(r));
+ r.rtmsg_flags = RTF_UP | RTF_GATEWAY; /* RTF_HOST not set */
+ r.rtmsg_metric = 1;
+
+ /* Create a channel to the NET kernel. */
+ if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+ SYS_ERR(DTUN, LOGL_ERROR, errno, "socket() failed");
+ return -1;
+ }
+
+ if (gw_iface) {
+ strncpy(ifr.ifr_name, gw_iface, IFNAMSIZ);
+ ifr.ifr_name[IFNAMSIZ - 1] = 0; /* Make sure to terminate */
+ if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
+ SYS_ERR(DTUN, LOGL_ERROR, errno,
+ "ioctl(SIOCGIFINDEX) failed");
+ close(fd);
+ return -1;
+ }
+ r.rtmsg_ifindex = ifr.ifr_ifindex;
+ }
+
+ memcpy(&r.rtmsg_dst, dst->s6_addr, sizeof(struct in6_addr));
+ memcpy(&r.rtmsg_gateway, gateway->s6_addr, sizeof(struct in6_addr));
+ r.rtmsg_dst_len = prefixlen;
+
+ if (delete) {
+ if (ioctl(fd, SIOCDELRT, (void *)&r) < 0) {
+ SYS_ERR(DTUN, LOGL_ERROR, errno,
+ "ioctl(SIOCDELRT) failed");
+ close(fd);
+ return -1;
+ }
+ } else {
+ if (ioctl(fd, SIOCADDRT, (void *)&r) < 0) {
+ SYS_ERR(DTUN, LOGL_ERROR, errno,
+ "ioctl(SIOCADDRT) failed");
+ close(fd);
+ return -1;
+ }
+ }
+ close(fd);
+#endif
+ return 0;
}
-int netdev_delroute(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask)
+int netdev_addroute4(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask)
{
- return netdev_route(dst, gateway, mask, 1);
+ return netdev_route4(dst, gateway, mask, 0);
}
+int netdev_delroute4(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask)
+{
+ return netdev_route4(dst, gateway, mask, 1);
+}
+
+int netdev_addroute6(struct in6_addr *dst, struct in6_addr *gateway, int prefixlen, const char *gw_iface)
+{
+ return netdev_route6(dst, gateway, prefixlen, gw_iface, 0);
+}
+
+int netdev_delroute6(struct in6_addr *dst, struct in6_addr *gateway, int prefixlen, const char *gw_iface)
+{
+ return netdev_route6(dst, gateway, prefixlen, gw_iface, 1);
+}
+
+
+
#include <ifaddrs.h>
/*! Obtain the local address of a network device
diff --git a/lib/netdev.h b/lib/netdev.h
index 74c42da..1bce814 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -65,8 +65,10 @@ extern int netdev_addaddr4(const char *devname, struct in_addr *addr,
extern int netdev_addaddr6(const char *devname, struct in6_addr *addr,
struct in6_addr *dstaddr, int prefixlen);
-extern int netdev_addroute(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask);
-extern int netdev_delroute(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask);
+extern int netdev_addroute4(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask);
+extern int netdev_delroute4(struct in_addr *dst, struct in_addr *gateway, struct in_addr *mask);
+extern int netdev_addroute6(struct in6_addr *dst, struct in6_addr *gateway, int prefixlen, const char *gw_iface);
+extern int netdev_delroute6(struct in6_addr *dst, struct in6_addr *gateway, int prefixlen, const char *gw_iface);
extern int netdev_ip_local_get(const char *devname, struct in46_prefix *prefix_list,
size_t prefix_size, int flags);
diff --git a/lib/netns.c b/lib/netns.c
new file mode 100644
index 0000000..b16a42b
--- /dev/null
+++ b/lib/netns.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2014-2017, Travelping GmbH <info@travelping.com>
+ * Copyright (C) 2020, Harald Welte <laforge@gnumonks.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if defined(__linux__)
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include "netns.h"
+
+#define NETNS_PATH "/var/run/netns"
+
+/*! default namespace of the GGSN process */
+static int default_nsfd = -1;
+
+/*! switch to a (non-default) namespace, store existing signal mask in oldmask.
+ * \param[in] nsfd file descriptor representing the namespace to whch we shall switch
+ * \param[out] oldmask caller-provided memory location to which old signal mask is stored
+ * \ returns 0 on success or negative (errno) in case of error */
+int switch_ns(int nsfd, sigset_t *oldmask)
+{
+ sigset_t intmask;
+ int rc;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, oldmask)) != 0)
+ return -rc;
+
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ /* restore old mask if we couldn't switch the netns */
+ sigprocmask(SIG_SETMASK, oldmask, NULL);
+ return -errno;
+ }
+ return 0;
+}
+
+/*! switch back to the default namespace, restoring signal mask.
+ * \param[in] oldmask signal mask to restore after returning to default namespace
+ * \returns 0 on successs; negative errno value in case of error */
+int restore_ns(sigset_t *oldmask)
+{
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ int rc;
+ if (setns(default_nsfd, CLONE_NEWNET) < 0)
+ return -errno;
+
+ if ((rc = sigprocmask(SIG_SETMASK, oldmask, NULL)) != 0)
+ return -rc;
+ return 0;
+}
+
+/*! open a file from within specified network namespace */
+int open_ns(int nsfd, const char *pathname, int flags)
+{
+ sigset_t intmask, oldmask;
+ int ret;
+ int fd = -1;
+ int rc;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ /* associate the calling thread with namespace file descriptor */
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ ret = -errno;
+ goto restore_sigmask;
+ }
+ /* open the requested file/path */
+ if ((fd = open(pathname, flags)) < 0) {
+ ret = -errno;
+ goto restore_defaultns;
+ }
+ ret = fd;
+
+restore_defaultns:
+ /* return back to default namespace */
+ if (setns(default_nsfd, CLONE_NEWNET) < 0) {
+ if (fd >= 0)
+ close(fd);
+ return -errno;
+ }
+
+restore_sigmask:
+ /* restore process mask */
+ if ((rc = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0) {
+ if (fd >= 0)
+ close(fd);
+ return -rc;
+ }
+
+ return ret;
+}
+
+/*! create a socket in another namespace.
+ * Switches temporarily to namespace indicated by nsfd, creates a socket in
+ * that namespace and then returns to the default namespace.
+ * \param[in] nsfd File descriptor of the namspace in which to create socket
+ * \param[in] domain Domain of the socket (AF_INET, ...)
+ * \param[in] type Type of the socket (SOCK_STREAM, ...)
+ * \param[in] protocol Protocol of the socket (IPPROTO_TCP, ...)
+ * \returns 0 on success; negative errno in case of error */
+int socket_ns(int nsfd, int domain, int type, int protocol)
+{
+ sigset_t intmask, oldmask;
+ int ret;
+ int sk = -1;
+ int rc;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ /* associate the calling thread with namespace file descriptor */
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ ret = -errno;
+ goto restore_sigmask;
+ }
+
+ /* create socket of requested domain/type/proto */
+ if ((sk = socket(domain, type, protocol)) < 0) {
+ ret = -errno;
+ goto restore_defaultns;
+ }
+ ret = sk;
+
+restore_defaultns:
+ /* return back to default namespace */
+ if (setns(default_nsfd, CLONE_NEWNET) < 0) {
+ if (sk >= 0)
+ close(sk);
+ return -errno;
+ }
+
+restore_sigmask:
+ /* restore process mask */
+ if ((rc = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0) {
+ if (sk >= 0)
+ close(sk);
+ return -rc;
+ }
+ return ret;
+}
+
+/*! initialize this network namespace helper module.
+ * Must be called before using any other functions of this file.
+ * \returns 0 on success; negative errno in case of error */
+int init_netns()
+{
+ /* store the default namespace for later reference */
+ if ((default_nsfd = open("/proc/self/ns/net", O_RDONLY)) < 0)
+ return -errno;
+ return 0;
+}
+
+/*! create obtain file descriptor for network namespace of give name.
+ * Creates /var/run/netns if it doesn't exist already.
+ * \param[in] name Name of the network namespace (in /var/run/netns/)
+ * \returns File descriptor of network namespace; negative errno in case of error */
+int get_nsfd(const char *name)
+{
+ int ret = 0;
+ int rc;
+ int fd;
+ sigset_t intmask, oldmask;
+ char path[MAXPATHLEN] = NETNS_PATH;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ /* create /var/run/netns, if it doesn't exist already */
+ rc = mkdir(path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
+ if (rc < 0 && errno != EEXIST)
+ return rc;
+
+ /* create /var/run/netns/[name], if it doesn't exist already */
+ snprintf(path, sizeof(path), "%s/%s", NETNS_PATH, name);
+ fd = open(path, O_RDONLY|O_CREAT|O_EXCL, 0);
+ if (fd < 0) {
+ if (errno == EEXIST) {
+ if ((fd = open(path, O_RDONLY)) < 0)
+ return -errno;
+ return fd;
+ }
+ return -errno;
+ }
+ if (close(fd) < 0)
+ return -errno;
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ /* create a new network namespace */
+ if (unshare(CLONE_NEWNET) < 0) {
+ ret = -errno;
+ goto restore_sigmask;
+ }
+ if (mount("/proc/self/ns/net", path, "none", MS_BIND, NULL) < 0)
+ ret = -errno;
+
+ /* switch back to default namespace */
+ if (setns(default_nsfd, CLONE_NEWNET) < 0)
+ return -errno;
+
+restore_sigmask:
+ /* restore process mask */
+ if ((rc = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0)
+ return -rc;
+
+ /* might have been set above in case mount fails */
+ if (ret < 0)
+ return ret;
+
+ /* finally, open the created namespace file descriptor from default ns */
+ if ((fd = open(path, O_RDONLY)) < 0)
+ return -errno;
+
+ return fd;
+}
+
+#endif
diff --git a/lib/netns.h b/lib/netns.h
new file mode 100644
index 0000000..3b91ba3
--- /dev/null
+++ b/lib/netns.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014-2017, Travelping GmbH <info@travelping.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __NETNS_H
+#define __NETNS_H
+
+#if defined(__linux__)
+
+int init_netns(void);
+
+int switch_ns(int nsfd, sigset_t *oldmask);
+int restore_ns(sigset_t *oldmask);
+
+int open_ns(int nsfd, const char *pathname, int flags);
+int socket_ns(int nsfd, int domain, int type, int protocol);
+int get_nsfd(const char *name);
+
+#endif
+
+#endif
diff --git a/lib/tun.c b/lib/tun.c
index 1aeed55..c771b92 100644
--- a/lib/tun.c
+++ b/lib/tun.c
@@ -276,7 +276,7 @@ int tun_free(struct tun_t *tun)
{
if (tun->routes) {
- netdev_delroute(&tun->dstaddr.v4, &tun->addr.v4, &tun->netmask);
+ netdev_delroute4(&tun->dstaddr.v4, &tun->addr.v4, &tun->netmask);
}
if (tun->fd >= 0) {
diff --git a/lib/util.c b/lib/util.c
new file mode 100644
index 0000000..6bb0d85
--- /dev/null
+++ b/lib/util.c
@@ -0,0 +1,35 @@
+/*
+ * misc helpers
+ * Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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 "../gtp/pdp.h"
+
+#include "ippool.h"
+#include "in46_addr.h"
+
+/*! Get the peer of pdp based on IP version used.
+* \param[in] pdp PDP context to select the peer from.
+* \param[in] v4v6 IP version to select. Valid values are 4 and 6.
+* \returns The selected peer matching the given IP version. NULL if not present.
+*/
+struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6) {
+ uint8_t i;
+
+ for (i = 0; i < 2; i++) {
+ struct ippoolm_t * ippool = pdp->peer[i];
+ if (!ippool)
+ continue;
+ if (is_ipv6 && in46a_is_v6(&ippool->addr))
+ return ippool;
+ else if (!is_ipv6 && in46a_is_v4(&ippool->addr))
+ return ippool;
+ }
+ return NULL;
+}
diff --git a/lib/util.h b/lib/util.h
new file mode 100644
index 0000000..bc9674d
--- /dev/null
+++ b/lib/util.h
@@ -0,0 +1,18 @@
+#pragma once
+/*
+ * misc helpers
+ * Copyright 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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 <stdbool.h>
+
+struct ippoolm_t;
+struct pdp_t;
+
+struct ippoolm_t *pdp_get_peer_ipv(struct pdp_t *pdp, bool is_ipv6);