1 | /* $Id: proxy_rtadvd.c 106061 2024-09-16 14:03:52Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * NAT Network - IPv6 router advertisement daemon.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2013-2024 Oracle and/or its affiliates.
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox base platform packages, as
|
---|
10 | * available from https://www.virtualbox.org.
|
---|
11 | *
|
---|
12 | * This program is free software; you can redistribute it and/or
|
---|
13 | * modify it under the terms of the GNU General Public License
|
---|
14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
15 | * License.
|
---|
16 | *
|
---|
17 | * This program is distributed in the hope that it will be useful, but
|
---|
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
20 | * General Public License for more details.
|
---|
21 | *
|
---|
22 | * You should have received a copy of the GNU General Public License
|
---|
23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
24 | *
|
---|
25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
26 | */
|
---|
27 |
|
---|
28 | #define LOG_GROUP LOG_GROUP_NAT_SERVICE
|
---|
29 |
|
---|
30 | #include "winutils.h"
|
---|
31 |
|
---|
32 | #include "proxy.h"
|
---|
33 |
|
---|
34 | #include "lwip/opt.h"
|
---|
35 | #include "lwip/sys.h"
|
---|
36 | #include "lwip/stats.h"
|
---|
37 | #include "lwip/timers.h"
|
---|
38 |
|
---|
39 | #include "lwip/inet_chksum.h"
|
---|
40 | #include "lwip/icmp6.h"
|
---|
41 | #include "lwip/nd6.h"
|
---|
42 |
|
---|
43 | #include "lwip/raw.h"
|
---|
44 |
|
---|
45 | #include <string.h>
|
---|
46 |
|
---|
47 |
|
---|
48 | static void proxy_rtadvd_timer(void *);
|
---|
49 | static void proxy_rtadvd_send_multicast(struct netif *);
|
---|
50 | static void proxy_rtadvd_fill_payload(struct netif *, int);
|
---|
51 |
|
---|
52 | static u8_t rtadvd_recv(void *, struct raw_pcb *, struct pbuf *, ip6_addr_t *);
|
---|
53 |
|
---|
54 |
|
---|
55 | /* ff02::1 - link-local all nodes multicast address */
|
---|
56 | static ip6_addr_t allnodes_linklocal = {
|
---|
57 | { PP_HTONL(0xff020000UL), 0, 0, PP_HTONL(0x00000001UL) }
|
---|
58 | };
|
---|
59 |
|
---|
60 |
|
---|
61 | /*
|
---|
62 | * Unsolicited Router Advertisement payload.
|
---|
63 | *
|
---|
64 | * NB: Since ICMP checksum covers pseudo-header with destination
|
---|
65 | * address (link-local allnodes multicast in this case) this payload
|
---|
66 | * cannot be used for solicited replies to unicast addresses.
|
---|
67 | */
|
---|
68 | static unsigned int unsolicited_ra_payload_length;
|
---|
69 | static u8_t unsolicited_ra_payload[
|
---|
70 | sizeof(struct ra_header)
|
---|
71 | /* reserves enough space for NETIF_MAX_HWADDR_LEN */
|
---|
72 | + sizeof(struct lladdr_option)
|
---|
73 | /* we only announce one prefix */
|
---|
74 | + sizeof(struct prefix_option) * 1
|
---|
75 | ];
|
---|
76 |
|
---|
77 |
|
---|
78 | static int ndefaults = 0;
|
---|
79 |
|
---|
80 | static struct raw_pcb *rtadvd_pcb;
|
---|
81 |
|
---|
82 |
|
---|
83 | void
|
---|
84 | proxy_rtadvd_start(struct netif *proxy_netif)
|
---|
85 | {
|
---|
86 | #if 0 /* XXX */
|
---|
87 | ndefaults = rtmon_get_defaults();
|
---|
88 | #else
|
---|
89 | ndefaults = g_proxy_options->ipv6_defroute;
|
---|
90 | #endif
|
---|
91 | if (ndefaults < 0) {
|
---|
92 | DPRINTF0(("rtadvd: failed to read IPv6 routing table, aborting\n"));
|
---|
93 | return;
|
---|
94 | }
|
---|
95 |
|
---|
96 | proxy_rtadvd_fill_payload(proxy_netif, ndefaults > 0);
|
---|
97 |
|
---|
98 | rtadvd_pcb = raw_new_ip6(IP6_NEXTH_ICMP6);
|
---|
99 | if (rtadvd_pcb == NULL) {
|
---|
100 | DPRINTF0(("rtadvd: failed to allocate pcb, aborting\n"));
|
---|
101 | return;
|
---|
102 | }
|
---|
103 |
|
---|
104 | /*
|
---|
105 | * We cannot use raw_bind_ip6() since raw_input() doesn't grok
|
---|
106 | * multicasts. We are going to use ip6_output_if() directly.
|
---|
107 | */
|
---|
108 | raw_recv_ip6(rtadvd_pcb, rtadvd_recv, proxy_netif);
|
---|
109 |
|
---|
110 | sys_timeout(3 * 1000, proxy_rtadvd_timer, proxy_netif);
|
---|
111 | }
|
---|
112 |
|
---|
113 |
|
---|
114 | static int quick_ras = 2;
|
---|
115 |
|
---|
116 |
|
---|
117 | /**
|
---|
118 | * lwIP thread callback invoked when we start/stop advertising default
|
---|
119 | * route.
|
---|
120 | */
|
---|
121 | void
|
---|
122 | proxy_rtadvd_do_quick(void *arg)
|
---|
123 | {
|
---|
124 | struct netif *proxy_netif = (struct netif *)arg;
|
---|
125 |
|
---|
126 | quick_ras = 2;
|
---|
127 | sys_untimeout(proxy_rtadvd_timer, proxy_netif);
|
---|
128 | proxy_rtadvd_timer(proxy_netif); /* sends and re-arms */
|
---|
129 | }
|
---|
130 |
|
---|
131 |
|
---|
132 | static void
|
---|
133 | proxy_rtadvd_timer(void *arg)
|
---|
134 | {
|
---|
135 | struct netif *proxy_netif = (struct netif *)arg;
|
---|
136 | int newdefs;
|
---|
137 | u32_t delay;
|
---|
138 |
|
---|
139 | #if 0 /* XXX */
|
---|
140 | newdefs = rtmon_get_defaults();
|
---|
141 | #else
|
---|
142 | newdefs = g_proxy_options->ipv6_defroute;
|
---|
143 | #endif
|
---|
144 | if (newdefs != ndefaults && newdefs != -1) {
|
---|
145 | ndefaults = newdefs;
|
---|
146 | proxy_rtadvd_fill_payload(proxy_netif, ndefaults > 0);
|
---|
147 | }
|
---|
148 |
|
---|
149 | proxy_rtadvd_send_multicast(proxy_netif);
|
---|
150 |
|
---|
151 | if (quick_ras > 0) {
|
---|
152 | --quick_ras;
|
---|
153 | delay = 16 * 1000;
|
---|
154 | }
|
---|
155 | else {
|
---|
156 | delay = 600 * 1000;
|
---|
157 | }
|
---|
158 |
|
---|
159 | sys_timeout(delay, proxy_rtadvd_timer, proxy_netif);
|
---|
160 | }
|
---|
161 |
|
---|
162 |
|
---|
163 | /*
|
---|
164 | * This should be folded into icmp6/nd6 input, but I don't want to
|
---|
165 | * solve this in general, making it configurable, etc.
|
---|
166 | *
|
---|
167 | * Cf. RFC 4861:
|
---|
168 | * 6.1.1. Validation of Router Solicitation Messages
|
---|
169 | */
|
---|
170 | static u8_t
|
---|
171 | rtadvd_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, ip6_addr_t *addr)
|
---|
172 | {
|
---|
173 | enum raw_recv_status { RAW_RECV_CONTINUE = 0, RAW_RECV_CONSUMED = 1 };
|
---|
174 |
|
---|
175 | struct netif *proxy_netif = (struct netif *)arg;
|
---|
176 | struct ip6_hdr *ip6_hdr;
|
---|
177 | struct icmp6_hdr *icmp6_hdr;
|
---|
178 | struct lladdr_option *lladdr_opt;
|
---|
179 | void *option;
|
---|
180 | u8_t opttype, optlen8;
|
---|
181 |
|
---|
182 | LWIP_UNUSED_ARG(pcb);
|
---|
183 | LWIP_UNUSED_ARG(addr);
|
---|
184 |
|
---|
185 | /* save a pointer to IP6 header and skip to ICMP6 payload */
|
---|
186 | ip6_hdr = (struct ip6_hdr *)p->payload;
|
---|
187 | pbuf_header(p, -ip_current_header_tot_len());
|
---|
188 |
|
---|
189 | if (p->len < sizeof(struct icmp6_hdr)) {
|
---|
190 | ICMP6_STATS_INC(icmp6.lenerr);
|
---|
191 | goto drop;
|
---|
192 | }
|
---|
193 |
|
---|
194 | if (ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len,
|
---|
195 | ip6_current_src_addr(),
|
---|
196 | ip6_current_dest_addr()) != 0)
|
---|
197 | {
|
---|
198 | ICMP6_STATS_INC(icmp6.chkerr);
|
---|
199 | goto drop;
|
---|
200 | }
|
---|
201 |
|
---|
202 | icmp6_hdr = (struct icmp6_hdr *)p->payload;
|
---|
203 | if (icmp6_hdr->type != ICMP6_TYPE_RS) {
|
---|
204 | pbuf_header(p, ip_current_header_tot_len()); /* restore payload ptr */
|
---|
205 | return RAW_RECV_CONTINUE; /* not interested */
|
---|
206 | }
|
---|
207 |
|
---|
208 | /* only now that we know it's ICMP6_TYPE_RS we can check IP6 hop limit */
|
---|
209 | if (IP6H_HOPLIM(ip6_hdr) != 255) {
|
---|
210 | ICMP6_STATS_INC(icmp6.proterr);
|
---|
211 | goto drop;
|
---|
212 | }
|
---|
213 |
|
---|
214 | /* future, backward-incompatible changes may use different Code values. */
|
---|
215 | if (icmp6_hdr->code != 0) {
|
---|
216 | ICMP6_STATS_INC(icmp6.proterr);
|
---|
217 | goto drop;
|
---|
218 | }
|
---|
219 |
|
---|
220 | /* skip past rs_header, nothing interesting in it */
|
---|
221 | if (p->len < sizeof(struct rs_header)) {
|
---|
222 | ICMP6_STATS_INC(icmp6.lenerr);
|
---|
223 | goto drop;
|
---|
224 | }
|
---|
225 | pbuf_header(p, -(s16_t)sizeof(struct rs_header));
|
---|
226 |
|
---|
227 | lladdr_opt = NULL;
|
---|
228 | while (p->len > 0) {
|
---|
229 | int optlen;
|
---|
230 |
|
---|
231 | if (p->len < 8) {
|
---|
232 | ICMP6_STATS_INC(icmp6.lenerr);
|
---|
233 | goto drop;
|
---|
234 | }
|
---|
235 |
|
---|
236 | option = p->payload;
|
---|
237 | opttype = ((u8_t *)option)[0];
|
---|
238 | optlen8 = ((u8_t *)option)[1]; /* in units of 8 octets */
|
---|
239 |
|
---|
240 | if (optlen8 == 0) {
|
---|
241 | ICMP6_STATS_INC(icmp6.proterr);
|
---|
242 | goto drop;
|
---|
243 | }
|
---|
244 |
|
---|
245 | optlen = (unsigned int)optlen8 << 3;
|
---|
246 | if (p->len < optlen) {
|
---|
247 | ICMP6_STATS_INC(icmp6.lenerr);
|
---|
248 | goto drop;
|
---|
249 | }
|
---|
250 |
|
---|
251 | if (opttype == ND6_OPTION_TYPE_SOURCE_LLADDR) {
|
---|
252 | if (lladdr_opt != NULL) { /* duplicate */
|
---|
253 | ICMP6_STATS_INC(icmp6.proterr);
|
---|
254 | goto drop;
|
---|
255 | }
|
---|
256 | lladdr_opt = (struct lladdr_option *)option;
|
---|
257 | }
|
---|
258 |
|
---|
259 | pbuf_header(p, -optlen);
|
---|
260 | }
|
---|
261 |
|
---|
262 | if (ip6_addr_isany(ip6_current_src_addr())) {
|
---|
263 | if (lladdr_opt != NULL) {
|
---|
264 | ICMP6_STATS_INC(icmp6.proterr);
|
---|
265 | goto drop;
|
---|
266 | }
|
---|
267 |
|
---|
268 | /* reply with multicast RA */
|
---|
269 | }
|
---|
270 | else {
|
---|
271 | /*
|
---|
272 | * XXX: Router is supposed to update its Neighbor Cache (6.2.6),
|
---|
273 | * but it's hidden inside nd6.c.
|
---|
274 | */
|
---|
275 |
|
---|
276 | /* may reply with either unicast or multicast RA */
|
---|
277 | }
|
---|
278 | /* we just always reply with multicast RA */
|
---|
279 |
|
---|
280 | pbuf_free(p); /* NB: this invalidates lladdr_option */
|
---|
281 |
|
---|
282 | sys_untimeout(proxy_rtadvd_timer, proxy_netif);
|
---|
283 | proxy_rtadvd_timer(proxy_netif); /* sends and re-arms */
|
---|
284 |
|
---|
285 | return RAW_RECV_CONSUMED;
|
---|
286 |
|
---|
287 | drop:
|
---|
288 | pbuf_free(p);
|
---|
289 | ICMP6_STATS_INC(icmp6.drop);
|
---|
290 | return RAW_RECV_CONSUMED;
|
---|
291 | }
|
---|
292 |
|
---|
293 |
|
---|
294 | static void
|
---|
295 | proxy_rtadvd_send_multicast(struct netif *proxy_netif)
|
---|
296 | {
|
---|
297 | struct pbuf *ph, *pp;
|
---|
298 | err_t error;
|
---|
299 |
|
---|
300 | ph = pbuf_alloc(PBUF_IP, 0, PBUF_RAM);
|
---|
301 | if (ph == NULL) {
|
---|
302 | DPRINTF0(("%s: failed to allocate RA header pbuf\n", __func__));
|
---|
303 | return;
|
---|
304 | }
|
---|
305 |
|
---|
306 | pp = pbuf_alloc(PBUF_RAW, unsolicited_ra_payload_length, PBUF_ROM);
|
---|
307 | if (pp == NULL) {
|
---|
308 | DPRINTF0(("%s: failed to allocate RA payload pbuf\n", __func__));
|
---|
309 | pbuf_free(ph);
|
---|
310 | return;
|
---|
311 | }
|
---|
312 | pp->payload = unsolicited_ra_payload;
|
---|
313 | pbuf_chain(ph, pp);
|
---|
314 |
|
---|
315 | error = ip6_output_if(ph,
|
---|
316 | netif_ip6_addr(proxy_netif, 0), /* src: link-local */
|
---|
317 | &allnodes_linklocal, /* dst */
|
---|
318 | 255, /* hop limit */
|
---|
319 | 0, /* traffic class */
|
---|
320 | IP6_NEXTH_ICMP6,
|
---|
321 | proxy_netif);
|
---|
322 | if (error != ERR_OK) {
|
---|
323 | DPRINTF0(("%s: failed to send RA (err=%d)\n", __func__, error));
|
---|
324 | }
|
---|
325 |
|
---|
326 | pbuf_free(pp);
|
---|
327 | pbuf_free(ph);
|
---|
328 | }
|
---|
329 |
|
---|
330 |
|
---|
331 | /*
|
---|
332 | * XXX: TODO: Only ra_header::router_lifetime (and hence
|
---|
333 | * ra_header::chksum) need to be changed, so we can precompute it once
|
---|
334 | * and then only update these two fields.
|
---|
335 | */
|
---|
336 | static void
|
---|
337 | proxy_rtadvd_fill_payload(struct netif *proxy_netif, int is_default)
|
---|
338 | {
|
---|
339 | struct pbuf *p;
|
---|
340 | struct ra_header *ra_hdr;
|
---|
341 | struct lladdr_option *lladdr_opt;
|
---|
342 | struct prefix_option *pfx_opt;
|
---|
343 | unsigned int lladdr_optlen;
|
---|
344 |
|
---|
345 | LWIP_ASSERT("netif hwaddr too long",
|
---|
346 | proxy_netif->hwaddr_len <= NETIF_MAX_HWADDR_LEN);
|
---|
347 |
|
---|
348 | /* type + length + ll addr + round up to 8 octets */
|
---|
349 | lladdr_optlen = (2 + proxy_netif->hwaddr_len + 7) & ~0x7;
|
---|
350 |
|
---|
351 | /* actual payload length */
|
---|
352 | unsolicited_ra_payload_length =
|
---|
353 | sizeof(struct ra_header)
|
---|
354 | + lladdr_optlen
|
---|
355 | + sizeof(struct prefix_option) * 1;
|
---|
356 |
|
---|
357 | /* Set fields. */
|
---|
358 | ra_hdr = (struct ra_header *)unsolicited_ra_payload;
|
---|
359 | lladdr_opt = (struct lladdr_option *)((u8_t *)ra_hdr + sizeof(struct ra_header));
|
---|
360 | pfx_opt = (struct prefix_option *)((u8_t *)lladdr_opt + lladdr_optlen);
|
---|
361 |
|
---|
362 | memset(unsolicited_ra_payload, 0, sizeof(unsolicited_ra_payload));
|
---|
363 |
|
---|
364 | ra_hdr->type = ICMP6_TYPE_RA;
|
---|
365 |
|
---|
366 | #if 0
|
---|
367 | /*
|
---|
368 | * "M" flag. Tell guests to use stateful DHCP6. Disabled here
|
---|
369 | * since we don't provide stateful server.
|
---|
370 | */
|
---|
371 | ra_hdr->flags |= ND6_RA_FLAG_MANAGED_ADDR_CONFIG;
|
---|
372 | #endif
|
---|
373 | /*
|
---|
374 | * XXX: TODO: Disable "O" flag for now to match disabled stateless
|
---|
375 | * server. We don't yet get IPv6 nameserver addresses from
|
---|
376 | * HostDnsService, so we have nothing to say, don't tell guests to
|
---|
377 | * come asking.
|
---|
378 | */
|
---|
379 | #if 0
|
---|
380 | /*
|
---|
381 | * "O" flag. Tell guests to use DHCP6 for DNS and the like. This
|
---|
382 | * is served by simple stateless server (RFC 3736).
|
---|
383 | *
|
---|
384 | * XXX: "STATEFUL" in the flag name was probably a bug in RFC2461.
|
---|
385 | * It's present in the text, but not in the router configuration
|
---|
386 | * variable name. It's dropped in the text in RFC4861.
|
---|
387 | */
|
---|
388 | ra_hdr->flags |= ND6_RA_FLAG_OTHER_STATEFUL_CONFIG;
|
---|
389 | #endif
|
---|
390 |
|
---|
391 | if (is_default) {
|
---|
392 | ra_hdr->router_lifetime = PP_HTONS(1200); /* seconds */
|
---|
393 | }
|
---|
394 | else {
|
---|
395 | ra_hdr->router_lifetime = 0;
|
---|
396 | }
|
---|
397 |
|
---|
398 | lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
|
---|
399 | lladdr_opt->length = lladdr_optlen >> 3; /* in units of 8 octets */
|
---|
400 | memcpy(lladdr_opt->addr, proxy_netif->hwaddr, proxy_netif->hwaddr_len);
|
---|
401 |
|
---|
402 | pfx_opt->type = ND6_OPTION_TYPE_PREFIX_INFO;
|
---|
403 | pfx_opt->length = 4;
|
---|
404 | pfx_opt->prefix_length = 64;
|
---|
405 | pfx_opt->flags = ND6_PREFIX_FLAG_ON_LINK
|
---|
406 | | ND6_PREFIX_FLAG_AUTONOMOUS;
|
---|
407 | pfx_opt->valid_lifetime = ~0U; /* infinite */
|
---|
408 | pfx_opt->preferred_lifetime = ~0U; /* infinite */
|
---|
409 | pfx_opt->prefix.addr[0] = netif_ip6_addr(proxy_netif, 1)->addr[0];
|
---|
410 | pfx_opt->prefix.addr[1] = netif_ip6_addr(proxy_netif, 1)->addr[1];
|
---|
411 |
|
---|
412 |
|
---|
413 | /* we need a temp pbuf to calculate the checksum */
|
---|
414 | p = pbuf_alloc(PBUF_IP, unsolicited_ra_payload_length, PBUF_ROM);
|
---|
415 | if (p == NULL) {
|
---|
416 | DPRINTF0(("rtadvd: failed to allocate RA pbuf\n"));
|
---|
417 | return;
|
---|
418 | }
|
---|
419 | p->payload = unsolicited_ra_payload;
|
---|
420 |
|
---|
421 | ra_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len,
|
---|
422 | /* src addr: netif's link-local */
|
---|
423 | netif_ip6_addr(proxy_netif, 0),
|
---|
424 | /* dst addr */
|
---|
425 | &allnodes_linklocal);
|
---|
426 | pbuf_free(p);
|
---|
427 | }
|
---|