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