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