blob: ae72b4c55c794ea030ff2786b890561bd96c0836 [file] [log] [blame]
Harald Welted46bcd22017-08-08 23:27:22 +02001/* Minimal ICMPv6 code for generating router advertisements as required by
2 * relevant 3GPP specs for a GGSN with IPv6 PDP contexts */
3
Pau Espin Pedrolb283c322020-02-25 14:13:09 +01004/* (C) 2017 by Harald Welte <laforge@gnumonks.org>
Harald Welted46bcd22017-08-08 23:27:22 +02005 *
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 Pedrol1c8ae662020-04-09 18:51:05 +020028#include "ippool.h"
29#include "syserr.h"
Harald Welted46bcd22017-08-08 23:27:22 +020030#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
38struct icmpv6_hdr {
39 uint8_t type;
40 uint8_t code;
41 uint16_t csum;
42} __attribute__ ((packed));
43
44/* RFC4861 Section 4.2 */
45struct 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 */
66struct 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 */
74struct 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));
Pau Espin Pedrolc43e8872020-04-15 15:03:50 +020093/* Prepends the ipv6 header and returns checksum content */
94static uint16_t icmpv6_prepend_ip6hdr(struct msgb *msg, const struct in6_addr *saddr,
95 const struct in6_addr *daddr)
96{
97 uint32_t len;
98 uint16_t skb_csum;
99 struct ip6_hdr *i6h;
100
101 /* checksum */
102 skb_csum = csum_partial(msgb_data(msg), msgb_length(msg), 0);
103 len = msgb_length(msg);
104 skb_csum = csum_ipv6_magic(saddr, daddr, len, IPPROTO_ICMPV6, skb_csum);
105
106 /* Push IPv6 header in front of ICMPv6 packet */
107 i6h = (struct ip6_hdr *) msgb_push(msg, sizeof(*i6h));
108 /* 4 bits version, 8 bits TC, 20 bits flow-ID */
109 i6h->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000);
110 i6h->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len);
111 i6h->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
112 i6h->ip6_ctlun.ip6_un1.ip6_un1_hlim = 255;
113 i6h->ip6_src = *saddr;
114 i6h->ip6_dst = *daddr;
115 return skb_csum;
116}
Harald Welted46bcd22017-08-08 23:27:22 +0200117
118
119/*! construct a 3GPP 29.061 compliant router advertisement for a given prefix
120 * \param[in] saddr Source IPv6 address for router advertisement
121 * \param[in] daddr Destination IPv6 address for router advertisement IPv6 header
Pau Espin Pedrolb283c322020-02-25 14:13:09 +0100122 * \param[in] prefix The single prefix to be advertised (/64 implied!)
Harald Welted46bcd22017-08-08 23:27:22 +0200123 * \returns callee-allocated message buffer containing router advertisement */
Pau Espin Pedrolee1529e2020-04-15 15:02:00 +0200124static struct msgb *icmpv6_construct_ra(const struct in6_addr *saddr,
Harald Welted46bcd22017-08-08 23:27:22 +0200125 const struct in6_addr *daddr,
126 const struct in6_addr *prefix)
127{
128 struct msgb *msg = msgb_alloc_headroom(512,128, "IPv6 RA");
129 struct icmpv6_radv_hdr *ra;
130 struct icmpv6_opt_prefix *ra_opt_pref;
Harald Welted46bcd22017-08-08 23:27:22 +0200131
132 OSMO_ASSERT(msg);
133
134 ra = (struct icmpv6_radv_hdr *) msgb_put(msg, sizeof(*ra));
135 ra->hdr.type = 134; /* see RFC4861 4.2 */
136 ra->hdr.code = 0; /* see RFC4861 4.2 */
137 ra->hdr.csum = 0; /* updated below */
138 ra->cur_ho_limit = 64; /* seems reasonable? */
139 /* the GGSN shall leave the M-flag cleared in the Router
140 * Advertisement messages */
141 ra->m = 0;
142 /* The GGSN may set the O-flag if there are additional
143 * configuration parameters that need to be fetched by the MS */
144 ra->o = 0; /* no DHCPv6 */
145 ra->res = 0;
146 /* RFC4861 Default: 3 * MaxRtrAdvInterval */
147 ra->router_lifetime = htons(3*GGSN_MaxRtrAdvInterval);
148 ra->reachable_time = 0; /* Unspecified */
149
150 /* RFC4861 Section 4.6.2 */
151 ra_opt_pref = (struct icmpv6_opt_prefix *) msgb_put(msg, sizeof(*ra_opt_pref));
152 ra_opt_pref->hdr.type = 3; /* RFC4861 4.6.2 */
153 ra_opt_pref->hdr.len = 4; /* RFC4861 4.6.2 */
154 ra_opt_pref->prefix_len = 64; /* only prefix length as per 3GPP */
155 /* The Prefix is contained in the Prefix Information Option of
156 * the Router Advertisements and shall have the A-flag set
157 * and the L-flag cleared */
158 ra_opt_pref->a = 1;
159 ra_opt_pref->l = 0;
160 ra_opt_pref->res = 0;
161 /* The lifetime of the prefix shall be set to infinity */
162 ra_opt_pref->valid_lifetime = htonl(GGSN_AdvValidLifetime);
163 ra_opt_pref->preferred_lifetime = htonl(GGSN_AdvPreferredLifetime);
164 ra_opt_pref->res2 = 0;
165 memcpy(ra_opt_pref->prefix, prefix, sizeof(ra_opt_pref->prefix));
166
167 /* checksum */
Pau Espin Pedrolc43e8872020-04-15 15:03:50 +0200168 ra->hdr.csum = icmpv6_prepend_ip6hdr(msg, saddr, daddr);
Harald Welted46bcd22017-08-08 23:27:22 +0200169
170 return msg;
171}
172
Pau Espin Pedrol29e7bd02020-04-15 15:00:46 +0200173/* Validate an ICMPv6 router solicitation according to RFC4861 6.1.1 */
Harald Welted46bcd22017-08-08 23:27:22 +0200174static bool icmpv6_validate_router_solicit(const uint8_t *pack, unsigned len)
175{
176 const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
177 //const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h));
178
179 /* Hop limit field must have 255 */
180 if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_hlim != 255)
181 return false;
182 /* FIXME: ICMP checksum is valid */
183 /* ICMP length (derived from IP length) is 8 or more octets */
184 if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_plen < 8)
185 return false;
186 /* FIXME: All included options have a length > 0 */
187 /* FIXME: If IP source is unspecified, no source link-layer addr option */
188 return true;
189}
190
Harald Welted46bcd22017-08-08 23:27:22 +0200191/* handle incoming packets to the all-routers multicast address */
Pau Espin Pedrol7d54ed42018-01-25 20:09:16 +0100192int handle_router_mcast(struct gsn_t *gsn, struct pdp_t *pdp,
193 const struct in6_addr *pdp_prefix,
194 const struct in6_addr *own_ll_addr,
Harald Weltef85fe972017-09-24 20:00:34 +0800195 const uint8_t *pack, unsigned len)
Harald Welted46bcd22017-08-08 23:27:22 +0200196{
Harald Welted46bcd22017-08-08 23:27:22 +0200197 const struct ip6_hdr *ip6h = (struct ip6_hdr *)pack;
198 const struct icmpv6_hdr *ic6h = (struct icmpv6_hdr *) (pack + sizeof(*ip6h));
199 struct msgb *msg;
200
Harald Welted46bcd22017-08-08 23:27:22 +0200201 if (len < sizeof(*ip6h)) {
202 LOGP(DICMP6, LOGL_NOTICE, "Packet too short: %u bytes\n", len);
203 return -1;
204 }
205
206 /* we only treat ICMPv6 here */
207 if (ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_ICMPV6) {
208 LOGP(DICMP6, LOGL_DEBUG, "Ignoring non-ICMP to all-routers mcast\n");
209 return 0;
210 }
211
212 if (len < sizeof(*ip6h) + sizeof(*ic6h)) {
213 LOGP(DICMP6, LOGL_NOTICE, "Short ICMPv6 packet: %s\n", osmo_hexdump(pack, len));
214 return -1;
215 }
216
217 switch (ic6h->type) {
218 case 133: /* router solicitation */
219 if (ic6h->code != 0) {
220 LOGP(DICMP6, LOGL_NOTICE, "ICMPv6 type 133 but code %d\n", ic6h->code);
221 return -1;
222 }
223 if (!icmpv6_validate_router_solicit(pack, len)) {
224 LOGP(DICMP6, LOGL_NOTICE, "Invalid Router Solicitation: %s\n",
225 osmo_hexdump(pack, len));
226 return -1;
227 }
Harald Weltef85fe972017-09-24 20:00:34 +0800228 /* Send router advertisement from GGSN link-local
Harald Welted46bcd22017-08-08 23:27:22 +0200229 * address to MS link-local address, including prefix
230 * allocated to this PDP context */
Pau Espin Pedrol7d54ed42018-01-25 20:09:16 +0100231 msg = icmpv6_construct_ra(own_ll_addr, &ip6h->ip6_src, pdp_prefix);
Harald Welted46bcd22017-08-08 23:27:22 +0200232 /* Send the constructed RA to the MS */
233 gtp_data_req(gsn, pdp, msgb_data(msg), msgb_length(msg));
234 msgb_free(msg);
235 break;
236 default:
237 LOGP(DICMP6, LOGL_DEBUG, "Unknown ICMPv6 type %u\n", ic6h->type);
238 break;
239 }
240 return 0;
241}