VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/NAT/proxy_rtadvd.c

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 11.8 KB
Line 
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
48static void proxy_rtadvd_timer(void *);
49static void proxy_rtadvd_send_multicast(struct netif *);
50static void proxy_rtadvd_fill_payload(struct netif *, int);
51
52static u8_t rtadvd_recv(void *, struct raw_pcb *, struct pbuf *, ip6_addr_t *);
53
54
55/* ff02::1 - link-local all nodes multicast address */
56static 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 */
68static unsigned int unsolicited_ra_payload_length;
69static 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
78static int ndefaults = 0;
79
80static struct raw_pcb *rtadvd_pcb;
81
82
83void
84proxy_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
114static int quick_ras = 2;
115
116
117/**
118 * lwIP thread callback invoked when we start/stop advertising default
119 * route.
120 */
121void
122proxy_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
132static void
133proxy_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 */
170static u8_t
171rtadvd_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
294static void
295proxy_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 */
336static void
337proxy_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}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette