From 1c8ae666548d350701c3645a97792776203ff200 Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Thu, 9 Apr 2020 18:51:05 +0200 Subject: Move icmpv6 and checksum files from ggsn/ dir to lib/ They will be required by sgsnemu to implement ICMPv6 Router Soliciations. Change-Id: Ie878604f0fc0169cc98a1e9eee64b14d76be2c45 --- ggsn/Makefile.am | 2 +- ggsn/checksum.c | 211 -------------------------------------------------- ggsn/checksum.h | 13 ---- ggsn/ggsn.c | 2 +- ggsn/icmpv6.c | 232 ------------------------------------------------------- ggsn/icmpv6.h | 9 --- lib/Makefile.am | 4 +- lib/checksum.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/checksum.h | 13 ++++ lib/icmpv6.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/icmpv6.h | 9 +++ 11 files changed, 469 insertions(+), 469 deletions(-) delete mode 100644 ggsn/checksum.c delete mode 100644 ggsn/checksum.h delete mode 100644 ggsn/icmpv6.c delete mode 100644 ggsn/icmpv6.h create mode 100644 lib/checksum.c create mode 100644 lib/checksum.h create mode 100644 lib/icmpv6.c create mode 100644 lib/icmpv6.h diff --git a/ggsn/Makefile.am b/ggsn/Makefile.am index ca389f0..eea7c6e 100644 --- a/ggsn/Makefile.am +++ b/ggsn/Makefile.am @@ -12,4 +12,4 @@ osmo_ggsn_LDADD += $(LIBGTPNL_LIBS) endif osmo_ggsn_DEPENDENCIES = ../gtp/libgtp.la ../lib/libmisc.a -osmo_ggsn_SOURCES = ggsn_main.c ggsn_vty.c ggsn.c ggsn.h sgsn.c sgsn.h icmpv6.c icmpv6.h checksum.c checksum.h pco.c pco.h +osmo_ggsn_SOURCES = ggsn_main.c ggsn_vty.c ggsn.c ggsn.h sgsn.c sgsn.h pco.c pco.h diff --git a/ggsn/checksum.c b/ggsn/checksum.c deleted file mode 100644 index 4b23897..0000000 --- a/ggsn/checksum.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * - * 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, - * Arnt Gulbrandsen, - * Tom May, - * Andreas Schwab, - * 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 -#include -#include -#endif - -#include "checksum.h" -#include - -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/ggsn/checksum.h b/ggsn/checksum.h deleted file mode 100644 index 4b22431..0000000 --- a/ggsn/checksum.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include -#include - -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/ggsn/ggsn.c b/ggsn/ggsn.c index c710984..3b10f70 100644 --- a/ggsn/ggsn.c +++ b/ggsn/ggsn.c @@ -54,7 +54,7 @@ #include "../lib/util.h" #include "../gtp/pdp.h" #include "../gtp/gtp.h" -#include "icmpv6.h" +#include "../lib/icmpv6.h" #include "pco.h" #include "ggsn.h" diff --git a/ggsn/icmpv6.c b/ggsn/icmpv6.c deleted file mode 100644 index 12119b8..0000000 --- a/ggsn/icmpv6.c +++ /dev/null @@ -1,232 +0,0 @@ -/* 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 - * - * 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 -#include -#include -#include -#if defined(__FreeBSD__) -#include /* FreeBSD 10.x needs this before ip6.h */ -#include -#endif -#include - -#include -#include -#include "checksum.h" - -#include "../gtp/gtp.h" -#include "../gtp/pdp.h" -#include "../lib/ippool.h" -#include "../lib/syserr.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 */ - -struct icmpv6_hdr { - uint8_t type; - uint8_t code; - uint16_t csum; -} __attribute__ ((packed)); - -/* RFC4861 Section 4.2 */ -struct icmpv6_radv_hdr { - struct icmpv6_hdr hdr; - uint8_t cur_ho_limit; -#if BYTE_ORDER == LITTLE_ENDIAN - uint8_t res:6, - m:1, - o:1; -#elif BYTE_ORDER == BIG_ENDIAN - uint8_t m:1, - o:1, - res:6; -#else -# error "Please fix " -#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 BYTE_ORDER == LITTLE_ENDIAN - uint8_t res:6, - a:1, - l:1; -#elif BYTE_ORDER == BIG_ENDIAN - uint8_t l:1, - a:1, - res:6; -#else -# error "Please fix " -#endif - uint32_t valid_lifetime; - uint32_t preferred_lifetime; - uint32_t res2; - uint8_t prefix[16]; -} __attribute__ ((packed)); - - -/*! 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 */ -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; - struct ip6_hdr *i6h; - uint32_t len; - uint16_t skb_csum; - - 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 */ - skb_csum = csum_partial(msgb_data(msg), msgb_length(msg), 0); - len = msgb_length(msg); - ra->hdr.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 msg; -} - -/* Walidate 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; -} - -/* 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/ggsn/icmpv6.h b/ggsn/icmpv6.h deleted file mode 100644 index bf91e27..0000000 --- a/ggsn/icmpv6.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../gtp/gtp.h" -#include "../gtp/pdp.h" - -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); diff --git a/lib/Makefile.am b/lib/Makefile.am index f2c5dc9..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 netns.h util.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 netns.c util.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, + * Arnt Gulbrandsen, + * Tom May, + * Andreas Schwab, + * 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 +#include +#include +#endif + +#include "checksum.h" +#include + +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 +#include + +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/icmpv6.c b/lib/icmpv6.c new file mode 100644 index 0000000..a6545fd --- /dev/null +++ b/lib/icmpv6.c @@ -0,0 +1,232 @@ +/* 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 + * + * 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 +#include +#include +#include +#if defined(__FreeBSD__) +#include /* FreeBSD 10.x needs this before ip6.h */ +#include +#endif +#include + +#include +#include +#include "checksum.h" + +#include "../gtp/gtp.h" +#include "../gtp/pdp.h" +#include "ippool.h" +#include "syserr.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 */ + +struct icmpv6_hdr { + uint8_t type; + uint8_t code; + uint16_t csum; +} __attribute__ ((packed)); + +/* RFC4861 Section 4.2 */ +struct icmpv6_radv_hdr { + struct icmpv6_hdr hdr; + uint8_t cur_ho_limit; +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t res:6, + m:1, + o:1; +#elif BYTE_ORDER == BIG_ENDIAN + uint8_t m:1, + o:1, + res:6; +#else +# error "Please fix " +#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 BYTE_ORDER == LITTLE_ENDIAN + uint8_t res:6, + a:1, + l:1; +#elif BYTE_ORDER == BIG_ENDIAN + uint8_t l:1, + a:1, + res:6; +#else +# error "Please fix " +#endif + uint32_t valid_lifetime; + uint32_t preferred_lifetime; + uint32_t res2; + uint8_t prefix[16]; +} __attribute__ ((packed)); + + +/*! 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 */ +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; + struct ip6_hdr *i6h; + uint32_t len; + uint16_t skb_csum; + + 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 */ + skb_csum = csum_partial(msgb_data(msg), msgb_length(msg), 0); + len = msgb_length(msg); + ra->hdr.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 msg; +} + +/* Walidate 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; +} + +/* 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..bf91e27 --- /dev/null +++ b/lib/icmpv6.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../gtp/gtp.h" +#include "../gtp/pdp.h" + +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); -- cgit v1.2.3