blob: ac1474d54d617ef4e3adf826d722cb5b5fb736a0 [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"
Pau Espin Pedrole2b09612020-04-15 15:09:30 +020030#include "icmpv6.h"
Harald Welted46bcd22017-08-08 23:27:22 +020031#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 Pedrole2b09612020-04-15 15:09:30 +020039/* RFC3307 link-local scope multicast address */
40const 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 Welted46bcd22017-08-08 23:27:22 +020043
Pau Espin Pedrolc43e8872020-04-15 15:03:50 +020044/* Prepends the ipv6 header and returns checksum content */
Pau Espin Pedrol96214602020-04-14 19:39:09 +020045uint16_t icmpv6_prepend_ip6hdr(struct msgb *msg, const struct in6_addr *saddr,
Pau Espin Pedrolc43e8872020-04-15 15:03:50 +020046 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 Welted46bcd22017-08-08 23:27:22 +020068
Pau Espin Pedrole2b09612020-04-15 15:09:30 +020069/*! 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 */
74struct 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 Welted46bcd22017-08-08 23:27:22 +020084
Pau Espin Pedrole2b09612020-04-15 15:09:30 +020085 rs->hdr.csum = icmpv6_prepend_ip6hdr(msg, saddr, &all_router_mcast_addr);
86
87 return msg;
88}
Harald Welted46bcd22017-08-08 23:27:22 +020089/*! 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 Pedrolb283c322020-02-25 14:13:09 +010092 * \param[in] prefix The single prefix to be advertised (/64 implied!)
Harald Welted46bcd22017-08-08 23:27:22 +020093 * \returns callee-allocated message buffer containing router advertisement */
Pau Espin Pedrolee1529e2020-04-15 15:02:00 +020094static struct msgb *icmpv6_construct_ra(const struct in6_addr *saddr,
Harald Welted46bcd22017-08-08 23:27:22 +020095 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 Welted46bcd22017-08-08 23:27:22 +0200101
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 Pedrolc43e8872020-04-15 15:03:50 +0200138 ra->hdr.csum = icmpv6_prepend_ip6hdr(msg, saddr, daddr);
Harald Welted46bcd22017-08-08 23:27:22 +0200139
140 return msg;
141}
142
Pau Espin Pedrol29e7bd02020-04-15 15:00:46 +0200143/* Validate an ICMPv6 router solicitation according to RFC4861 6.1.1 */
Harald Welted46bcd22017-08-08 23:27:22 +0200144static 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 Pedrole2b09612020-04-15 15:09:30 +0200161/* Validate an ICMPv6 router advertisement according to RFC4861 6.1.2.
162 Returns pointer packet header on success, NULL otherwise. */
163struct 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 Welted46bcd22017-08-08 23:27:22 +0200192/* handle incoming packets to the all-routers multicast address */
Pau Espin Pedrol7d54ed42018-01-25 20:09:16 +0100193int 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 Weltef85fe972017-09-24 20:00:34 +0800196 const uint8_t *pack, unsigned len)
Harald Welted46bcd22017-08-08 23:27:22 +0200197{
Harald Welted46bcd22017-08-08 23:27:22 +0200198 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 Welted46bcd22017-08-08 23:27:22 +0200202 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 Weltef85fe972017-09-24 20:00:34 +0800229 /* Send router advertisement from GGSN link-local
Harald Welted46bcd22017-08-08 23:27:22 +0200230 * address to MS link-local address, including prefix
231 * allocated to this PDP context */
Pau Espin Pedrol7d54ed42018-01-25 20:09:16 +0100232 msg = icmpv6_construct_ra(own_ll_addr, &ip6h->ip6_src, pdp_prefix);
Harald Welted46bcd22017-08-08 23:27:22 +0200233 /* 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}