blob: b7b97eb7b5f23dd48f1452cc63d5aa9e1ad190b1 [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
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
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));
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 */
100struct 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 */
165static 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 Welted46bcd22017-08-08 23:27:22 +0200182/* handle incoming packets to the all-routers multicast address */
Pau Espin Pedrol7d54ed42018-01-25 20:09:16 +0100183int 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 Weltef85fe972017-09-24 20:00:34 +0800186 const uint8_t *pack, unsigned len)
Harald Welted46bcd22017-08-08 23:27:22 +0200187{
Harald Welted46bcd22017-08-08 23:27:22 +0200188 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 Welted46bcd22017-08-08 23:27:22 +0200192 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 Weltef85fe972017-09-24 20:00:34 +0800219 /* Send router advertisement from GGSN link-local
Harald Welted46bcd22017-08-08 23:27:22 +0200220 * address to MS link-local address, including prefix
221 * allocated to this PDP context */
Pau Espin Pedrol7d54ed42018-01-25 20:09:16 +0100222 msg = icmpv6_construct_ra(own_ll_addr, &ip6h->ip6_src, pdp_prefix);
Harald Welted46bcd22017-08-08 23:27:22 +0200223 /* 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}