pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmo-netif/+/42464?usp=email )
Change subject: Introduce API osmo_icmpv6 ......................................................................
Introduce API osmo_icmpv6
icmpv6.* files are copied from osmo-ggsn.git 1.14.0 (ebd1bf8adbc8d5bec9e36d70e3a78e8838226e7a).
Change-Id: I78ec7270c717af0a1b8ffd9398cd69ea7a0dbee2 --- M TODO-RELEASE M include/osmocom/netif/Makefile.am A include/osmocom/netif/icmpv6.h M src/Makefile.am A src/icmpv6.c 5 files changed, 323 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmo-netif refs/changes/64/42464/1
diff --git a/TODO-RELEASE b/TODO-RELEASE index 7aebf84..89a240e 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -8,3 +8,4 @@ # If any interfaces have been removed or changed since the last public release: c:r:0. #library what description / commit summary line netif add osmo_ip_checksum API +netif add osmo_icmpv6 API diff --git a/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am index f509221..1a0cce4 100644 --- a/include/osmocom/netif/Makefile.am +++ b/include/osmocom/netif/Makefile.am @@ -20,6 +20,7 @@ osmonetif_HEADERS = \ amr.h \ datagram.h \ + icmpv6.h \ ip_checksum.h \ ipa.h \ ipa_unit.h \ diff --git a/include/osmocom/netif/icmpv6.h b/include/osmocom/netif/icmpv6.h new file mode 100644 index 0000000..94d97fc --- /dev/null +++ b/include/osmocom/netif/icmpv6.h @@ -0,0 +1,105 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include <netinet/in.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/endian.h> + +#define ICMPv6_OPT_TYPE_PREFIX_INFO 0x03 + +struct osmo_icmpv6_hdr { + uint8_t type; + uint8_t code; + uint16_t csum; +} __attribute__ ((packed)); + +struct osmo_icmpv6_echo_hdr { + struct osmo_icmpv6_hdr hdr; + uint16_t ident; /* Identifier */ + uint16_t seq; /* Sequence number */ + uint8_t data[0]; /* Data */ +} __attribute__ ((packed)); + +/* RFC4861 Section 4.1 */ +struct osmo_icmpv6_rsol_hdr { + struct osmo_icmpv6_hdr hdr; + uint32_t reserved; + uint8_t options[0]; +} __attribute__ ((packed)); + +/* RFC4861 Section 4.2 */ +struct osmo_icmpv6_radv_hdr { + struct osmo_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 osmo_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 osmo_icmpv6_opt_prefix { + struct osmo_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 osmo_icmpv6_opt_mtu { + struct osmo_icmpv6_opt_hdr hdr; + uint16_t reserved; + uint32_t mtu; +} __attribute__ ((packed)); + +uint16_t osmo_icmpv6_prepend_ip6hdr(struct msgb *msg, const struct in6_addr *saddr, + const struct in6_addr *daddr); + +struct msgb *osmo_icmpv6_construct_rs(const struct in6_addr *saddr); +struct msgb *osmo_icmpv6_construct_ra(const struct in6_addr *saddr, + const struct in6_addr *daddr, + const struct in6_addr *prefix, + uint32_t mtu); + +bool osmo_icmpv6_validate_router_solicit(const uint8_t *pack, unsigned len); +struct osmo_icmpv6_radv_hdr *osmo_icmpv6_validate_router_adv(const uint8_t *pack, unsigned len); + +#define foreach_icmpv6_opt(icmpv6_pkt, icmpv6_len, opt_hdr) \ + for (opt_hdr = (struct osmo_icmpv6_opt_hdr *)(icmpv6_pkt)->options; \ + (uint8_t *)(opt_hdr) + sizeof(struct osmo_icmpv6_opt_hdr) <= (((uint8_t *)(icmpv6_pkt)) + (icmpv6_len)); \ + opt_hdr = (struct osmo_icmpv6_opt_hdr *)((uint8_t *)(opt_hdr) + (opt_hdr)->len) \ + ) + +/* RFC3307 link-local scope multicast address */ +extern const struct in6_addr osmo_icmpv6_all_router_mcast_addr; + +extern const uint8_t osmo_icmpv6_solicited_node_mcast_addr_prefix[13]; diff --git a/src/Makefile.am b/src/Makefile.am index 4484e02..8c2b95b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,6 +15,7 @@
libosmonetif_la_SOURCES = amr.c \ datagram.c \ + icmpv6.c \ ip_checksum.c \ ipa.c \ ipa_keepalive.c \ diff --git a/src/icmpv6.c b/src/icmpv6.c new file mode 100644 index 0000000..69e03dd --- /dev/null +++ b/src/icmpv6.c @@ -0,0 +1,215 @@ +/* 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/netif/ip_checksum.h> +#include <osmocom/netif/icmpv6.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 osmo_icmpv6_all_router_mcast_addr = { + .s6_addr = { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } +}; + + +/* RFC4291 link-local solicited-node multicast address, FF02:0:0:0:0:1:FF, 104 bits = 13 bytes */ +const uint8_t osmo_icmpv6_solicited_node_mcast_addr_prefix[13] = { + 0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0xFF +}; + +/* 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 = osmo_ip_checksum_csum_partial(msgb_data(msg), msgb_length(msg), 0); + len = msgb_length(msg); + skb_csum = osmo_ip_checksum_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 *osmo_icmpv6_construct_rs(const struct in6_addr *saddr) +{ + struct msgb *msg = msgb_alloc_headroom(512, 128, "IPv6 RS"); + struct osmo_icmpv6_rsol_hdr *rs; + OSMO_ASSERT(msg); + rs = (struct osmo_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, &osmo_icmpv6_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 */ +struct msgb *osmo_icmpv6_construct_ra(const struct in6_addr *saddr, + const struct in6_addr *daddr, + const struct in6_addr *prefix, + uint32_t mtu) +{ + struct msgb *msg = msgb_alloc_headroom(512, 128, "IPv6 RA"); + struct osmo_icmpv6_radv_hdr *ra; + struct osmo_icmpv6_opt_prefix *ra_opt_pref; + struct osmo_icmpv6_opt_mtu *ra_opt_mtu; + + OSMO_ASSERT(msg); + + ra = (struct osmo_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 osmo_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)); + + /* RFC4861 Section 4.6.4, MTU */ + ra_opt_mtu = (struct osmo_icmpv6_opt_mtu *) msgb_put(msg, sizeof(*ra_opt_mtu)); + ra_opt_mtu->hdr.type = 5; /* RFC4861 4.6.4 */ + ra_opt_mtu->hdr.len = 1; /* RFC4861 4.6.4 */ + ra_opt_mtu->reserved = 0; + ra_opt_mtu->mtu = htonl(mtu); + + /* 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 */ +bool osmo_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 osmo_icmpv6_radv_hdr *osmo_icmpv6_validate_router_adv(const uint8_t *pack, unsigned len) +{ + const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack; + const struct osmo_icmpv6_hdr *ic6h = (struct osmo_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 osmo_icmpv6_radv_hdr *)ic6h; +}