1 | /* SPDX-License-Identifier: BSD-3-Clause */ |
2 | /* |
3 | * Copyright (c) 2013 |
4 | * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. |
5 | */ |
6 | |
7 | #include "slirp.h" |
8 | #include "ip6_icmp.h" |
9 | |
10 | #define NDP_Interval \ |
11 | g_rand_int_range(slirp->grand, NDP_MinRtrAdvInterval, NDP_MaxRtrAdvInterval) |
12 | |
13 | static void ra_timer_handler(void *opaque) |
14 | { |
15 | Slirp *slirp = opaque; |
16 | |
17 | slirp->cb->timer_mod(slirp->ra_timer, |
18 | slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS + |
19 | NDP_Interval, |
20 | slirp->opaque); |
21 | ndp_send_ra(slirp); |
22 | } |
23 | |
24 | void icmp6_init(Slirp *slirp) |
25 | { |
26 | if (!slirp->in6_enabled) { |
27 | return; |
28 | } |
29 | |
30 | slirp->ra_timer = |
31 | slirp->cb->timer_new(ra_timer_handler, slirp, slirp->opaque); |
32 | slirp->cb->timer_mod(slirp->ra_timer, |
33 | slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS + |
34 | NDP_Interval, |
35 | slirp->opaque); |
36 | } |
37 | |
38 | void icmp6_cleanup(Slirp *slirp) |
39 | { |
40 | if (!slirp->in6_enabled) { |
41 | return; |
42 | } |
43 | |
44 | slirp->cb->timer_free(slirp->ra_timer, slirp->opaque); |
45 | } |
46 | |
47 | static void icmp6_send_echoreply(struct mbuf *m, Slirp *slirp, struct ip6 *ip, |
48 | struct icmp6 *icmp) |
49 | { |
50 | struct mbuf *t = m_get(slirp); |
51 | t->m_len = sizeof(struct ip6) + ntohs(ip->ip_pl); |
52 | memcpy(t->m_data, m->m_data, t->m_len); |
53 | |
54 | /* IPv6 Packet */ |
55 | struct ip6 *rip = mtod(t, struct ip6 *); |
56 | rip->ip_dst = ip->ip_src; |
57 | rip->ip_src = ip->ip_dst; |
58 | |
59 | /* ICMPv6 packet */ |
60 | t->m_data += sizeof(struct ip6); |
61 | struct icmp6 *ricmp = mtod(t, struct icmp6 *); |
62 | ricmp->icmp6_type = ICMP6_ECHO_REPLY; |
63 | ricmp->icmp6_cksum = 0; |
64 | |
65 | /* Checksum */ |
66 | t->m_data -= sizeof(struct ip6); |
67 | ricmp->icmp6_cksum = ip6_cksum(t); |
68 | |
69 | ip6_output(NULL, t, 0); |
70 | } |
71 | |
72 | void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code) |
73 | { |
74 | Slirp *slirp = m->slirp; |
75 | struct mbuf *t; |
76 | struct ip6 *ip = mtod(m, struct ip6 *); |
77 | char addrstr[INET6_ADDRSTRLEN]; |
78 | |
79 | DEBUG_CALL("icmp6_send_error" ); |
80 | DEBUG_ARG("type = %d, code = %d" , type, code); |
81 | |
82 | if (IN6_IS_ADDR_MULTICAST(&ip->ip_src) || in6_zero(&ip->ip_src)) { |
83 | /* TODO icmp error? */ |
84 | return; |
85 | } |
86 | |
87 | t = m_get(slirp); |
88 | |
89 | /* IPv6 packet */ |
90 | struct ip6 *rip = mtod(t, struct ip6 *); |
91 | rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR; |
92 | rip->ip_dst = ip->ip_src; |
93 | inet_ntop(AF_INET6, &rip->ip_dst, addrstr, INET6_ADDRSTRLEN); |
94 | DEBUG_ARG("target = %s" , addrstr); |
95 | |
96 | rip->ip_nh = IPPROTO_ICMPV6; |
97 | const int error_data_len = |
98 | MIN(m->m_len, IF_MTU - (sizeof(struct ip6) + ICMP6_ERROR_MINLEN)); |
99 | rip->ip_pl = htons(ICMP6_ERROR_MINLEN + error_data_len); |
100 | t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); |
101 | |
102 | /* ICMPv6 packet */ |
103 | t->m_data += sizeof(struct ip6); |
104 | struct icmp6 *ricmp = mtod(t, struct icmp6 *); |
105 | ricmp->icmp6_type = type; |
106 | ricmp->icmp6_code = code; |
107 | ricmp->icmp6_cksum = 0; |
108 | |
109 | switch (type) { |
110 | case ICMP6_UNREACH: |
111 | case ICMP6_TIMXCEED: |
112 | ricmp->icmp6_err.unused = 0; |
113 | break; |
114 | case ICMP6_TOOBIG: |
115 | ricmp->icmp6_err.mtu = htonl(IF_MTU); |
116 | break; |
117 | case ICMP6_PARAMPROB: |
118 | /* TODO: Handle this case */ |
119 | break; |
120 | default: |
121 | g_assert_not_reached(); |
122 | break; |
123 | } |
124 | t->m_data += ICMP6_ERROR_MINLEN; |
125 | memcpy(t->m_data, m->m_data, error_data_len); |
126 | |
127 | /* Checksum */ |
128 | t->m_data -= ICMP6_ERROR_MINLEN; |
129 | t->m_data -= sizeof(struct ip6); |
130 | ricmp->icmp6_cksum = ip6_cksum(t); |
131 | |
132 | ip6_output(NULL, t, 0); |
133 | } |
134 | |
135 | /* |
136 | * Send NDP Router Advertisement |
137 | */ |
138 | void ndp_send_ra(Slirp *slirp) |
139 | { |
140 | DEBUG_CALL("ndp_send_ra" ); |
141 | |
142 | /* Build IPv6 packet */ |
143 | struct mbuf *t = m_get(slirp); |
144 | struct ip6 *rip = mtod(t, struct ip6 *); |
145 | size_t pl_size = 0; |
146 | struct in6_addr addr; |
147 | uint32_t scope_id; |
148 | |
149 | rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR; |
150 | rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; |
151 | rip->ip_nh = IPPROTO_ICMPV6; |
152 | |
153 | /* Build ICMPv6 packet */ |
154 | t->m_data += sizeof(struct ip6); |
155 | struct icmp6 *ricmp = mtod(t, struct icmp6 *); |
156 | ricmp->icmp6_type = ICMP6_NDP_RA; |
157 | ricmp->icmp6_code = 0; |
158 | ricmp->icmp6_cksum = 0; |
159 | |
160 | /* NDP */ |
161 | ricmp->icmp6_nra.chl = NDP_AdvCurHopLimit; |
162 | ricmp->icmp6_nra.M = NDP_AdvManagedFlag; |
163 | ricmp->icmp6_nra.O = NDP_AdvOtherConfigFlag; |
164 | ricmp->icmp6_nra.reserved = 0; |
165 | ricmp->icmp6_nra.lifetime = htons(NDP_AdvDefaultLifetime); |
166 | ricmp->icmp6_nra.reach_time = htonl(NDP_AdvReachableTime); |
167 | ricmp->icmp6_nra.retrans_time = htonl(NDP_AdvRetransTime); |
168 | t->m_data += ICMP6_NDP_RA_MINLEN; |
169 | pl_size += ICMP6_NDP_RA_MINLEN; |
170 | |
171 | /* Source link-layer address (NDP option) */ |
172 | struct ndpopt *opt = mtod(t, struct ndpopt *); |
173 | opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; |
174 | opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; |
175 | in6_compute_ethaddr(rip->ip_src, opt->ndpopt_linklayer); |
176 | t->m_data += NDPOPT_LINKLAYER_LEN; |
177 | pl_size += NDPOPT_LINKLAYER_LEN; |
178 | |
179 | /* Prefix information (NDP option) */ |
180 | struct ndpopt *opt2 = mtod(t, struct ndpopt *); |
181 | opt2->ndpopt_type = NDPOPT_PREFIX_INFO; |
182 | opt2->ndpopt_len = NDPOPT_PREFIXINFO_LEN / 8; |
183 | opt2->ndpopt_prefixinfo.prefix_length = slirp->vprefix_len; |
184 | opt2->ndpopt_prefixinfo.L = 1; |
185 | opt2->ndpopt_prefixinfo.A = 1; |
186 | opt2->ndpopt_prefixinfo.reserved1 = 0; |
187 | opt2->ndpopt_prefixinfo.valid_lt = htonl(NDP_AdvValidLifetime); |
188 | opt2->ndpopt_prefixinfo.pref_lt = htonl(NDP_AdvPrefLifetime); |
189 | opt2->ndpopt_prefixinfo.reserved2 = 0; |
190 | opt2->ndpopt_prefixinfo.prefix = slirp->vprefix_addr6; |
191 | t->m_data += NDPOPT_PREFIXINFO_LEN; |
192 | pl_size += NDPOPT_PREFIXINFO_LEN; |
193 | |
194 | /* Prefix information (NDP option) */ |
195 | if (get_dns6_addr(&addr, &scope_id) >= 0) { |
196 | /* Host system does have an IPv6 DNS server, announce our proxy. */ |
197 | struct ndpopt *opt3 = mtod(t, struct ndpopt *); |
198 | opt3->ndpopt_type = NDPOPT_RDNSS; |
199 | opt3->ndpopt_len = NDPOPT_RDNSS_LEN / 8; |
200 | opt3->ndpopt_rdnss.reserved = 0; |
201 | opt3->ndpopt_rdnss.lifetime = htonl(2 * NDP_MaxRtrAdvInterval); |
202 | opt3->ndpopt_rdnss.addr = slirp->vnameserver_addr6; |
203 | t->m_data += NDPOPT_RDNSS_LEN; |
204 | pl_size += NDPOPT_RDNSS_LEN; |
205 | } |
206 | |
207 | rip->ip_pl = htons(pl_size); |
208 | t->m_data -= sizeof(struct ip6) + pl_size; |
209 | t->m_len = sizeof(struct ip6) + pl_size; |
210 | |
211 | /* ICMPv6 Checksum */ |
212 | ricmp->icmp6_cksum = ip6_cksum(t); |
213 | |
214 | ip6_output(NULL, t, 0); |
215 | } |
216 | |
217 | /* |
218 | * Send NDP Neighbor Solitication |
219 | */ |
220 | void ndp_send_ns(Slirp *slirp, struct in6_addr addr) |
221 | { |
222 | char addrstr[INET6_ADDRSTRLEN]; |
223 | |
224 | inet_ntop(AF_INET6, &addr, addrstr, INET6_ADDRSTRLEN); |
225 | |
226 | DEBUG_CALL("ndp_send_ns" ); |
227 | DEBUG_ARG("target = %s" , addrstr); |
228 | |
229 | /* Build IPv6 packet */ |
230 | struct mbuf *t = m_get(slirp); |
231 | struct ip6 *rip = mtod(t, struct ip6 *); |
232 | rip->ip_src = slirp->vhost_addr6; |
233 | rip->ip_dst = (struct in6_addr)SOLICITED_NODE_PREFIX; |
234 | memcpy(&rip->ip_dst.s6_addr[13], &addr.s6_addr[13], 3); |
235 | rip->ip_nh = IPPROTO_ICMPV6; |
236 | rip->ip_pl = htons(ICMP6_NDP_NS_MINLEN + NDPOPT_LINKLAYER_LEN); |
237 | t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); |
238 | |
239 | /* Build ICMPv6 packet */ |
240 | t->m_data += sizeof(struct ip6); |
241 | struct icmp6 *ricmp = mtod(t, struct icmp6 *); |
242 | ricmp->icmp6_type = ICMP6_NDP_NS; |
243 | ricmp->icmp6_code = 0; |
244 | ricmp->icmp6_cksum = 0; |
245 | |
246 | /* NDP */ |
247 | ricmp->icmp6_nns.reserved = 0; |
248 | ricmp->icmp6_nns.target = addr; |
249 | |
250 | /* Build NDP option */ |
251 | t->m_data += ICMP6_NDP_NS_MINLEN; |
252 | struct ndpopt *opt = mtod(t, struct ndpopt *); |
253 | opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; |
254 | opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; |
255 | in6_compute_ethaddr(slirp->vhost_addr6, opt->ndpopt_linklayer); |
256 | |
257 | /* ICMPv6 Checksum */ |
258 | t->m_data -= ICMP6_NDP_NA_MINLEN; |
259 | t->m_data -= sizeof(struct ip6); |
260 | ricmp->icmp6_cksum = ip6_cksum(t); |
261 | |
262 | ip6_output(NULL, t, 1); |
263 | } |
264 | |
265 | /* |
266 | * Send NDP Neighbor Advertisement |
267 | */ |
268 | static void ndp_send_na(Slirp *slirp, struct ip6 *ip, struct icmp6 *icmp) |
269 | { |
270 | /* Build IPv6 packet */ |
271 | struct mbuf *t = m_get(slirp); |
272 | struct ip6 *rip = mtod(t, struct ip6 *); |
273 | rip->ip_src = icmp->icmp6_nns.target; |
274 | if (in6_zero(&ip->ip_src)) { |
275 | rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; |
276 | } else { |
277 | rip->ip_dst = ip->ip_src; |
278 | } |
279 | rip->ip_nh = IPPROTO_ICMPV6; |
280 | rip->ip_pl = htons(ICMP6_NDP_NA_MINLEN + NDPOPT_LINKLAYER_LEN); |
281 | t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); |
282 | |
283 | /* Build ICMPv6 packet */ |
284 | t->m_data += sizeof(struct ip6); |
285 | struct icmp6 *ricmp = mtod(t, struct icmp6 *); |
286 | ricmp->icmp6_type = ICMP6_NDP_NA; |
287 | ricmp->icmp6_code = 0; |
288 | ricmp->icmp6_cksum = 0; |
289 | |
290 | /* NDP */ |
291 | ricmp->icmp6_nna.R = NDP_IsRouter; |
292 | ricmp->icmp6_nna.S = !IN6_IS_ADDR_MULTICAST(&rip->ip_dst); |
293 | ricmp->icmp6_nna.O = 1; |
294 | ricmp->icmp6_nna.reserved_hi = 0; |
295 | ricmp->icmp6_nna.reserved_lo = 0; |
296 | ricmp->icmp6_nna.target = icmp->icmp6_nns.target; |
297 | |
298 | /* Build NDP option */ |
299 | t->m_data += ICMP6_NDP_NA_MINLEN; |
300 | struct ndpopt *opt = mtod(t, struct ndpopt *); |
301 | opt->ndpopt_type = NDPOPT_LINKLAYER_TARGET; |
302 | opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; |
303 | in6_compute_ethaddr(ricmp->icmp6_nna.target, opt->ndpopt_linklayer); |
304 | |
305 | /* ICMPv6 Checksum */ |
306 | t->m_data -= ICMP6_NDP_NA_MINLEN; |
307 | t->m_data -= sizeof(struct ip6); |
308 | ricmp->icmp6_cksum = ip6_cksum(t); |
309 | |
310 | ip6_output(NULL, t, 0); |
311 | } |
312 | |
313 | /* |
314 | * Process a NDP message |
315 | */ |
316 | static void ndp_input(struct mbuf *m, Slirp *slirp, struct ip6 *ip, |
317 | struct icmp6 *icmp) |
318 | { |
319 | m->m_len += ETH_HLEN; |
320 | m->m_data -= ETH_HLEN; |
321 | struct ethhdr *eth = mtod(m, struct ethhdr *); |
322 | m->m_len -= ETH_HLEN; |
323 | m->m_data += ETH_HLEN; |
324 | |
325 | switch (icmp->icmp6_type) { |
326 | case ICMP6_NDP_RS: |
327 | DEBUG_CALL(" type = Router Solicitation" ); |
328 | if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && |
329 | ntohs(ip->ip_pl) >= ICMP6_NDP_RS_MINLEN) { |
330 | /* Gratuitous NDP */ |
331 | ndp_table_add(slirp, ip->ip_src, eth->h_source); |
332 | |
333 | ndp_send_ra(slirp); |
334 | } |
335 | break; |
336 | |
337 | case ICMP6_NDP_RA: |
338 | DEBUG_CALL(" type = Router Advertisement" ); |
339 | slirp->cb->guest_error("Warning: guest sent NDP RA, but shouldn't" , |
340 | slirp->opaque); |
341 | break; |
342 | |
343 | case ICMP6_NDP_NS: |
344 | DEBUG_CALL(" type = Neighbor Solicitation" ); |
345 | if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && |
346 | !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nns.target) && |
347 | ntohs(ip->ip_pl) >= ICMP6_NDP_NS_MINLEN && |
348 | (!in6_zero(&ip->ip_src) || |
349 | in6_solicitednode_multicast(&ip->ip_dst))) { |
350 | if (in6_equal_host(&icmp->icmp6_nns.target)) { |
351 | /* Gratuitous NDP */ |
352 | ndp_table_add(slirp, ip->ip_src, eth->h_source); |
353 | ndp_send_na(slirp, ip, icmp); |
354 | } |
355 | } |
356 | break; |
357 | |
358 | case ICMP6_NDP_NA: |
359 | DEBUG_CALL(" type = Neighbor Advertisement" ); |
360 | if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && |
361 | ntohs(ip->ip_pl) >= ICMP6_NDP_NA_MINLEN && |
362 | !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nna.target) && |
363 | (!IN6_IS_ADDR_MULTICAST(&ip->ip_dst) || icmp->icmp6_nna.S == 0)) { |
364 | ndp_table_add(slirp, ip->ip_src, eth->h_source); |
365 | } |
366 | break; |
367 | |
368 | case ICMP6_NDP_REDIRECT: |
369 | DEBUG_CALL(" type = Redirect" ); |
370 | slirp->cb->guest_error( |
371 | "Warning: guest sent NDP REDIRECT, but shouldn't" , slirp->opaque); |
372 | break; |
373 | } |
374 | } |
375 | |
376 | /* |
377 | * Process a received ICMPv6 message. |
378 | */ |
379 | void icmp6_input(struct mbuf *m) |
380 | { |
381 | struct icmp6 *icmp; |
382 | struct ip6 *ip = mtod(m, struct ip6 *); |
383 | Slirp *slirp = m->slirp; |
384 | int hlen = sizeof(struct ip6); |
385 | |
386 | DEBUG_CALL("icmp6_input" ); |
387 | DEBUG_ARG("m = %p" , m); |
388 | DEBUG_ARG("m_len = %d" , m->m_len); |
389 | |
390 | if (ntohs(ip->ip_pl) < ICMP6_MINLEN) { |
391 | goto end; |
392 | } |
393 | |
394 | if (ip6_cksum(m)) { |
395 | goto end; |
396 | } |
397 | |
398 | m->m_len -= hlen; |
399 | m->m_data += hlen; |
400 | icmp = mtod(m, struct icmp6 *); |
401 | m->m_len += hlen; |
402 | m->m_data -= hlen; |
403 | |
404 | DEBUG_ARG("icmp6_type = %d" , icmp->icmp6_type); |
405 | switch (icmp->icmp6_type) { |
406 | case ICMP6_ECHO_REQUEST: |
407 | if (in6_equal_host(&ip->ip_dst)) { |
408 | icmp6_send_echoreply(m, slirp, ip, icmp); |
409 | } else { |
410 | /* TODO */ |
411 | g_critical("external icmpv6 not supported yet" ); |
412 | } |
413 | break; |
414 | |
415 | case ICMP6_NDP_RS: |
416 | case ICMP6_NDP_RA: |
417 | case ICMP6_NDP_NS: |
418 | case ICMP6_NDP_NA: |
419 | case ICMP6_NDP_REDIRECT: |
420 | ndp_input(m, slirp, ip, icmp); |
421 | break; |
422 | |
423 | case ICMP6_UNREACH: |
424 | case ICMP6_TOOBIG: |
425 | case ICMP6_TIMXCEED: |
426 | case ICMP6_PARAMPROB: |
427 | /* XXX? report error? close socket? */ |
428 | default: |
429 | break; |
430 | } |
431 | |
432 | end: |
433 | m_free(m); |
434 | } |
435 | |