Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 1 | /* Minimal ICMPv6 code for generating router advertisements as required by |
| 2 | * relevant 3GPP specs for a GGSN with IPv6 PDP contexts */ |
| 3 | |
| 4 | /* (C) 2017 by Harald Welte <laforge@gnumonks.org> |
| 5 | * |
| 6 | * The contents of this file may be used under the terms of the GNU |
| 7 | * General Public License Version 2, provided that the above copyright |
| 8 | * notice and this permission notice is included in all copies or |
| 9 | * substantial portions of the software. |
| 10 | */ |
| 11 | |
| 12 | #include <stdint.h> |
| 13 | #include <stdbool.h> |
| 14 | #include <string.h> |
| 15 | #include <netinet/in.h> |
| 16 | #if defined(__FreeBSD__) |
| 17 | #include <sys/types.h> /* FreeBSD 10.x needs this before ip6.h */ |
| 18 | #include <sys/endian.h> |
| 19 | #endif |
| 20 | #include <netinet/ip6.h> |
| 21 | |
| 22 | #include <osmocom/core/msgb.h> |
| 23 | #include <osmocom/core/utils.h> |
| 24 | #include "checksum.h" |
| 25 | |
| 26 | #include "../gtp/gtp.h" |
| 27 | #include "../gtp/pdp.h" |
| 28 | #include "../lib/ippool.h" |
| 29 | #include "../lib/syserr.h" |
| 30 | #include "config.h" |
| 31 | |
| 32 | /* 29.061 11.2.1.3.4 IPv6 Router Configuration Variables in GGSN */ |
| 33 | #define GGSN_MaxRtrAdvInterval 21600 /* 6 hours */ |
| 34 | #define GGSN_MinRtrAdvInterval 16200 /* 4.5 hours */ |
| 35 | #define GGSN_AdvValidLifetime 0xffffffff /* infinite */ |
| 36 | #define GGSN_AdvPreferredLifetime 0xffffffff /* infinite */ |
| 37 | |
| 38 | struct icmpv6_hdr { |
| 39 | uint8_t type; |
| 40 | uint8_t code; |
| 41 | uint16_t csum; |
| 42 | } __attribute__ ((packed)); |
| 43 | |
| 44 | /* RFC4861 Section 4.2 */ |
| 45 | struct icmpv6_radv_hdr { |
| 46 | struct icmpv6_hdr hdr; |
| 47 | uint8_t cur_ho_limit; |
| 48 | #if BYTE_ORDER == LITTLE_ENDIAN |
| 49 | uint8_t res:6, |
| 50 | m:1, |
| 51 | o:1; |
| 52 | #elif BYTE_ORDER == BIG_ENDIAN |
| 53 | uint8_t m:1, |
| 54 | o:1, |
| 55 | res:6; |
| 56 | #else |
| 57 | # error "Please fix <bits/endian.h>" |
| 58 | #endif |
| 59 | uint16_t router_lifetime; |
| 60 | uint32_t reachable_time; |
| 61 | uint32_t retrans_timer; |
| 62 | uint8_t options[0]; |
| 63 | } __attribute__ ((packed)); |
| 64 | |
| 65 | /* RFC4861 Section 4.6 */ |
| 66 | struct icmpv6_opt_hdr { |
| 67 | uint8_t type; |
| 68 | /* length in units of 8 octets, including type+len! */ |
| 69 | uint8_t len; |
| 70 | uint8_t data[0]; |
| 71 | } __attribute__ ((packed)); |
| 72 | |
| 73 | /* RFC4861 Section 4.6.2 */ |
| 74 | struct icmpv6_opt_prefix { |
| 75 | struct icmpv6_opt_hdr hdr; |
| 76 | uint8_t prefix_len; |
| 77 | #if BYTE_ORDER == LITTLE_ENDIAN |
| 78 | uint8_t res:6, |
| 79 | a:1, |
| 80 | l:1; |
| 81 | #elif BYTE_ORDER == BIG_ENDIAN |
| 82 | uint8_t l:1, |
| 83 | a:1, |
| 84 | res:6; |
| 85 | #else |
| 86 | # error "Please fix <bits/endian.h>" |
| 87 | #endif |
| 88 | uint32_t valid_lifetime; |
| 89 | uint32_t preferred_lifetime; |
| 90 | uint32_t res2; |
| 91 | uint8_t prefix[16]; |
| 92 | } __attribute__ ((packed)); |
| 93 | |
| 94 | |
| 95 | /*! construct a 3GPP 29.061 compliant router advertisement for a given prefix |
| 96 | * \param[in] saddr Source IPv6 address for router advertisement |
| 97 | * \param[in] daddr Destination IPv6 address for router advertisement IPv6 header |
| 98 | * \param[in] prefix The single prefix to be advertised (/64 implied!)i |
| 99 | * \returns callee-allocated message buffer containing router advertisement */ |
| 100 | struct msgb *icmpv6_construct_ra(const struct in6_addr *saddr, |
| 101 | const struct in6_addr *daddr, |
| 102 | const struct in6_addr *prefix) |
| 103 | { |
| 104 | struct msgb *msg = msgb_alloc_headroom(512,128, "IPv6 RA"); |
| 105 | struct icmpv6_radv_hdr *ra; |
| 106 | struct icmpv6_opt_prefix *ra_opt_pref; |
| 107 | struct ip6_hdr *i6h; |
| 108 | uint32_t len; |
| 109 | uint16_t skb_csum; |
| 110 | |
| 111 | OSMO_ASSERT(msg); |
| 112 | |
| 113 | ra = (struct icmpv6_radv_hdr *) msgb_put(msg, sizeof(*ra)); |
| 114 | ra->hdr.type = 134; /* see RFC4861 4.2 */ |
| 115 | ra->hdr.code = 0; /* see RFC4861 4.2 */ |
| 116 | ra->hdr.csum = 0; /* updated below */ |
| 117 | ra->cur_ho_limit = 64; /* seems reasonable? */ |
| 118 | /* the GGSN shall leave the M-flag cleared in the Router |
| 119 | * Advertisement messages */ |
| 120 | ra->m = 0; |
| 121 | /* The GGSN may set the O-flag if there are additional |
| 122 | * configuration parameters that need to be fetched by the MS */ |
| 123 | ra->o = 0; /* no DHCPv6 */ |
| 124 | ra->res = 0; |
| 125 | /* RFC4861 Default: 3 * MaxRtrAdvInterval */ |
| 126 | ra->router_lifetime = htons(3*GGSN_MaxRtrAdvInterval); |
| 127 | ra->reachable_time = 0; /* Unspecified */ |
| 128 | |
| 129 | /* RFC4861 Section 4.6.2 */ |
| 130 | ra_opt_pref = (struct icmpv6_opt_prefix *) msgb_put(msg, sizeof(*ra_opt_pref)); |
| 131 | ra_opt_pref->hdr.type = 3; /* RFC4861 4.6.2 */ |
| 132 | ra_opt_pref->hdr.len = 4; /* RFC4861 4.6.2 */ |
| 133 | ra_opt_pref->prefix_len = 64; /* only prefix length as per 3GPP */ |
| 134 | /* The Prefix is contained in the Prefix Information Option of |
| 135 | * the Router Advertisements and shall have the A-flag set |
| 136 | * and the L-flag cleared */ |
| 137 | ra_opt_pref->a = 1; |
| 138 | ra_opt_pref->l = 0; |
| 139 | ra_opt_pref->res = 0; |
| 140 | /* The lifetime of the prefix shall be set to infinity */ |
| 141 | ra_opt_pref->valid_lifetime = htonl(GGSN_AdvValidLifetime); |
| 142 | ra_opt_pref->preferred_lifetime = htonl(GGSN_AdvPreferredLifetime); |
| 143 | ra_opt_pref->res2 = 0; |
| 144 | memcpy(ra_opt_pref->prefix, prefix, sizeof(ra_opt_pref->prefix)); |
| 145 | |
| 146 | /* checksum */ |
| 147 | skb_csum = csum_partial(msgb_data(msg), msgb_length(msg), 0); |
| 148 | len = msgb_length(msg); |
| 149 | ra->hdr.csum = csum_ipv6_magic(saddr, daddr, len, IPPROTO_ICMPV6, skb_csum); |
| 150 | |
| 151 | /* Push IPv6 header in front of ICMPv6 packet */ |
| 152 | i6h = (struct ip6_hdr *) msgb_push(msg, sizeof(*i6h)); |
| 153 | /* 4 bits version, 8 bits TC, 20 bits flow-ID */ |
| 154 | i6h->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000); |
| 155 | i6h->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len); |
| 156 | i6h->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6; |
| 157 | i6h->ip6_ctlun.ip6_un1.ip6_un1_hlim = 255; |
| 158 | i6h->ip6_src = *saddr; |
| 159 | i6h->ip6_dst = *daddr; |
| 160 | |
| 161 | return msg; |
| 162 | } |
| 163 | |
| 164 | /* Walidate an ICMPv6 router solicitation according to RFC4861 6.1.1 */ |
| 165 | static bool icmpv6_validate_router_solicit(const uint8_t *pack, unsigned len) |
| 166 | { |
| 167 | const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack; |
| 168 | //const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h)); |
| 169 | |
| 170 | /* Hop limit field must have 255 */ |
| 171 | if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_hlim != 255) |
| 172 | return false; |
| 173 | /* FIXME: ICMP checksum is valid */ |
| 174 | /* ICMP length (derived from IP length) is 8 or more octets */ |
| 175 | if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_plen < 8) |
| 176 | return false; |
| 177 | /* FIXME: All included options have a length > 0 */ |
| 178 | /* FIXME: If IP source is unspecified, no source link-layer addr option */ |
| 179 | return true; |
| 180 | } |
| 181 | |
Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 182 | /* handle incoming packets to the all-routers multicast address */ |
Pau Espin Pedrol | 7d54ed4 | 2018-01-25 20:09:16 +0100 | [diff] [blame] | 183 | int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp, |
| 184 | const struct in6_addr *pdp_prefix, |
| 185 | const struct in6_addr *own_ll_addr, |
Harald Welte | f85fe97 | 2017-09-24 20:00:34 +0800 | [diff] [blame] | 186 | const uint8_t *pack, unsigned len) |
Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 187 | { |
Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 188 | const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack; |
| 189 | const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h)); |
| 190 | struct msgb *msg; |
| 191 | |
Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 192 | if (len < sizeof(*ip6h)) { |
| 193 | LOGP(DICMP6, LOGL_NOTICE, "Packet too short: %u bytes\n", len); |
| 194 | return -1; |
| 195 | } |
| 196 | |
| 197 | /* we only treat ICMPv6 here */ |
| 198 | if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_ICMPV6) { |
| 199 | LOGP(DICMP6, LOGL_DEBUG, "Ignoring non-ICMP to all-routers mcast\n"); |
| 200 | return 0; |
| 201 | } |
| 202 | |
| 203 | if (len < sizeof(*ip6h) + sizeof(*ic6h)) { |
| 204 | LOGP(DICMP6, LOGL_NOTICE, "Short ICMPv6 packet: %s\n", osmo_hexdump(pack, len)); |
| 205 | return -1; |
| 206 | } |
| 207 | |
| 208 | switch (ic6h->type) { |
| 209 | case 133: /* router solicitation */ |
| 210 | if (ic6h->code != 0) { |
| 211 | LOGP(DICMP6, LOGL_NOTICE, "ICMPv6 type 133 but code %d\n", ic6h->code); |
| 212 | return -1; |
| 213 | } |
| 214 | if (!icmpv6_validate_router_solicit(pack, len)) { |
| 215 | LOGP(DICMP6, LOGL_NOTICE, "Invalid Router Solicitation: %s\n", |
| 216 | osmo_hexdump(pack, len)); |
| 217 | return -1; |
| 218 | } |
Harald Welte | f85fe97 | 2017-09-24 20:00:34 +0800 | [diff] [blame] | 219 | /* Send router advertisement from GGSN link-local |
Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 220 | * address to MS link-local address, including prefix |
| 221 | * allocated to this PDP context */ |
Pau Espin Pedrol | 7d54ed4 | 2018-01-25 20:09:16 +0100 | [diff] [blame] | 222 | msg = icmpv6_construct_ra(own_ll_addr, &ip6h->ip6_src, pdp_prefix); |
Harald Welte | d46bcd2 | 2017-08-08 23:27:22 +0200 | [diff] [blame] | 223 | /* Send the constructed RA to the MS */ |
| 224 | gtp_data_req(gsn, pdp, msgb_data(msg), msgb_length(msg)); |
| 225 | msgb_free(msg); |
| 226 | break; |
| 227 | default: |
| 228 | LOGP(DICMP6, LOGL_DEBUG, "Unknown ICMPv6 type %u\n", ic6h->type); |
| 229 | break; |
| 230 | } |
| 231 | return 0; |
| 232 | } |