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