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