VirtualBox

source: vbox/trunk/src/VBox/Devices/Network/slirp/ip_icmpwin.c@ 59202

Last change on this file since 59202 was 56292, checked in by vboxsync, 9 years ago

Devices: Updated (C) year.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Data Id Revision
File size: 14.3 KB
Line 
1/* $Id: ip_icmpwin.c 56292 2015-06-09 14:20:46Z vboxsync $ */
2/** @file
3 * NAT - Windows ICMP API based ping proxy.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include "slirp.h"
19#include "ip_icmp.h"
20
21#include <winternl.h> /* for PIO_APC_ROUTINE &c */
22#include <iphlpapi.h>
23#include <icmpapi.h>
24
25/*
26 * A header of ICMP ECHO. Intended for storage, unlike struct icmp
27 * which is intended to be overlayed onto a buffer.
28 */
29struct icmp_echo {
30 uint8_t icmp_type;
31 uint8_t icmp_code;
32 uint16_t icmp_cksum;
33 uint16_t icmp_echo_id;
34 uint16_t icmp_echo_seq;
35};
36
37AssertCompileSize(struct icmp_echo, 8);
38
39
40struct pong {
41 PNATState pData;
42
43 TAILQ_ENTRY(pong) queue_entry;
44
45 struct ip reqiph;
46 struct icmp_echo reqicmph;
47
48 size_t bufsize;
49 uint8_t buf[1];
50};
51
52
53static VOID WINAPI icmpwin_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved);
54static VOID WINAPI icmpwin_callback_old(void *ctx);
55
56static void icmpwin_callback(struct pong *pong);
57static void icmpwin_pong(struct pong *pong);
58
59static struct mbuf *icmpwin_get_error(struct pong *pong, int type, int code);
60static struct mbuf *icmpwin_get_mbuf(PNATState pData, size_t reqsize);
61
62
63/*
64 * On Windows XP and Windows Server 2003 IcmpSendEcho2() callback
65 * is FARPROC, but starting from Vista it's PIO_APC_ROUTINE with
66 * two extra arguments. Callbacks use WINAPI (stdcall) calling
67 * convention with callee responsible for popping the arguments,
68 * so to avoid stack corruption we check windows version at run
69 * time and provide correct callback.
70 *
71 * XXX: this is system-wide, but what about multiple NAT threads?
72 */
73static void *pfIcmpCallback;
74
75
76int
77icmpwin_init(PNATState pData)
78{
79 if (pfIcmpCallback == NULL)
80 {
81 OSVERSIONINFO osvi;
82 int status;
83
84 ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
85 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
86 status = GetVersionEx(&osvi);
87 if (status == 0)
88 return 1;
89
90 if (osvi.dwMajorVersion >= 6)
91 pfIcmpCallback = icmpwin_callback_apc;
92 else
93 pfIcmpCallback = icmpwin_callback_old;
94 }
95
96 TAILQ_INIT(&pData->pongs_expected);
97 TAILQ_INIT(&pData->pongs_received);
98
99 pData->icmp_socket.sh = IcmpCreateFile();
100 pData->phEvents[VBOX_ICMP_EVENT_INDEX] = CreateEvent(NULL, FALSE, FALSE, NULL);
101
102 return 0;
103}
104
105
106void
107icmpwin_finit(PNATState pData)
108{
109 IcmpCloseHandle(pData->icmp_socket.sh);
110
111 while (!TAILQ_EMPTY(&pData->pongs_received)) {
112 struct pong *pong = TAILQ_FIRST(&pData->pongs_received);
113 TAILQ_REMOVE(&pData->pongs_received, pong, queue_entry);
114 RTMemFree(pong);
115 }
116
117 /* this should be empty */
118 while (!TAILQ_EMPTY(&pData->pongs_expected)) {
119 struct pong *pong = TAILQ_FIRST(&pData->pongs_expected);
120 TAILQ_REMOVE(&pData->pongs_expected, pong, queue_entry);
121 pong->pData = NULL;
122 }
123}
124
125
126/*
127 * Outgoing ping from guest.
128 */
129void
130icmpwin_ping(PNATState pData, struct mbuf *m, int hlen)
131{
132 struct ip *ip = mtod(m, struct ip *);
133 size_t reqsize, pongsize;
134 uint8_t ttl;
135 size_t bufsize;
136 struct pong *pong;
137 IPAddr dst;
138 IP_OPTION_INFORMATION opts;
139 void *reqdata;
140 int status;
141
142 ttl = ip->ip_ttl;
143 AssertReturnVoid(ttl > 0);
144
145 reqsize = ip->ip_len - hlen - sizeof(struct icmp_echo);
146
147 bufsize = sizeof(ICMP_ECHO_REPLY);
148 if (reqsize < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo))
149 bufsize += sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo);
150 else
151 bufsize += reqsize;
152 bufsize += 16; /* whatever that is; empirically at least XP needs it */
153
154 pongsize = RT_OFFSETOF(struct pong, buf) + bufsize;
155 if (pData->cbIcmpPending + pongsize > 1024 * 1024)
156 return;
157
158 pong = RTMemAlloc(pongsize);
159 if (RT_UNLIKELY(pong == NULL))
160 return;
161
162 pong->pData = pData;
163 pong->bufsize = bufsize;
164 m_copydata(m, 0, hlen, (caddr_t)&pong->reqiph);
165 m_copydata(m, hlen, sizeof(struct icmp_echo), (caddr_t)&pong->reqicmph);
166 AssertReturnVoid(pong->reqicmph.icmp_type == ICMP_ECHO);
167
168 if (m->m_next == NULL)
169 {
170 /* already in single contiguous buffer */
171 reqdata = mtod(m, char *) + sizeof(struct ip) + sizeof(struct icmp_echo);
172 }
173 else
174 {
175 /* use reply buffer as temporary storage */
176 reqdata = pong->buf;
177 m_copydata(m, sizeof(struct ip) + sizeof(struct icmp_echo),
178 reqsize, reqdata);
179 }
180
181 dst = ip->ip_dst.s_addr;
182
183 opts.Ttl = ttl;
184 opts.Tos = ip->ip_tos; /* affected by DisableUserTOSSetting key */
185 opts.Flags = (ip->ip_off & IP_DF) != 0 ? IP_FLAG_DF : 0;
186 opts.OptionsSize = 0;
187 opts.OptionsData = 0;
188
189
190 status = IcmpSendEcho2(pData->icmp_socket.sh, NULL,
191 pfIcmpCallback, pong,
192 dst, reqdata, (WORD)reqsize, &opts,
193 pong->buf, (DWORD)pong->bufsize,
194 5 * 1000 /* ms */);
195
196 if (RT_UNLIKELY(status != 0))
197 {
198 Log2(("NAT: IcmpSendEcho2: unexpected status %d\n", status));
199 }
200 else if ((status = GetLastError()) != ERROR_IO_PENDING)
201 {
202 int code;
203
204 Log2(("NAT: IcmpSendEcho2: error %d\n", status));
205 switch (status) {
206 case ERROR_NETWORK_UNREACHABLE:
207 code = ICMP_UNREACH_NET;
208 break;
209 case ERROR_HOST_UNREACHABLE:
210 code = ICMP_UNREACH_HOST;
211 break;
212 default:
213 code = -1;
214 break;
215 }
216
217 if (code != -1) /* send icmp error */
218 {
219 struct mbuf *em = icmpwin_get_error(pong, ICMP_UNREACH, code);
220 if (em != NULL)
221 {
222 struct ip *eip = mtod(em, struct ip *);
223 eip->ip_src = alias_addr;
224 ip_output(pData, NULL, em);
225 }
226 }
227 }
228 else /* success */
229 {
230 Log2(("NAT: pong %p for ping %RTnaipv4 id 0x%04x seq %d len %zu (%zu)\n",
231 pong, dst,
232 RT_N2H_U16(pong->reqicmph.icmp_echo_id),
233 RT_N2H_U16(pong->reqicmph.icmp_echo_seq),
234 pongsize, reqsize));
235
236 pData->cbIcmpPending += pongsize;
237 TAILQ_INSERT_TAIL(&pData->pongs_expected, pong, queue_entry);
238 pong = NULL; /* callback owns it now */
239 }
240
241 if (pong != NULL)
242 RTMemFree(pong);
243}
244
245
246static VOID WINAPI
247icmpwin_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved)
248{
249 struct pong *pong = (struct pong *)ctx;
250 if (pong != NULL)
251 icmpwin_callback(pong);
252}
253
254
255static VOID WINAPI
256icmpwin_callback_old(void *ctx)
257{
258 struct pong *pong = (struct pong *)ctx;
259 if (pong != NULL)
260 icmpwin_callback(pong);
261}
262
263
264/*
265 * Actual callback code for IcmpSendEcho2(). OS version specific
266 * trampoline will free "pong" argument for us.
267 *
268 * Since async callback can be called anytime the thread is alertable,
269 * it's not safe to do any processing here. Instead queue it and
270 * notify the main loop.
271 */
272static void
273icmpwin_callback(struct pong *pong)
274{
275 PNATState pData = pong->pData;
276
277 if (pData == NULL)
278 {
279 RTMemFree(pong);
280 return;
281 }
282
283#ifdef DEBUG
284 {
285 struct pong *expected, *already;
286
287 TAILQ_FOREACH(expected, &pData->pongs_expected, queue_entry)
288 {
289 if (expected == pong)
290 break;
291 }
292 Assert(expected);
293
294 TAILQ_FOREACH(already, &pData->pongs_received, queue_entry)
295 {
296 if (already == pong)
297 break;
298 }
299 Assert(!already);
300 }
301#endif
302
303 TAILQ_REMOVE(&pData->pongs_expected, pong, queue_entry);
304 TAILQ_INSERT_TAIL(&pData->pongs_received, pong, queue_entry);
305
306 WSASetEvent(pData->phEvents[VBOX_ICMP_EVENT_INDEX]);
307}
308
309
310void
311icmpwin_process(PNATState pData)
312{
313 struct pong_tailq pongs;
314
315 if (TAILQ_EMPTY(&pData->pongs_received))
316 return;
317
318 TAILQ_INIT(&pongs);
319 TAILQ_CONCAT(&pongs, &pData->pongs_received, queue_entry);
320
321 while (!TAILQ_EMPTY(&pongs)) {
322 struct pong *pong = TAILQ_FIRST(&pongs);
323 size_t sz;
324
325 sz = RT_OFFSETOF(struct pong, buf) + pong->bufsize;
326 Assert(pData->cbIcmpPending >= sz);
327 pData->cbIcmpPending -= sz;
328
329 icmpwin_pong(pong);
330
331 TAILQ_REMOVE(&pongs, pong, queue_entry);
332 RTMemFree(pong);
333 }
334}
335
336
337void
338icmpwin_pong(struct pong *pong)
339{
340 PNATState pData;
341 DWORD nreplies;
342 ICMP_ECHO_REPLY *reply;
343 struct mbuf *m;
344 struct ip *ip;
345 struct icmp_echo *icmp;
346 size_t reqsize;
347
348 pData = pong->pData; /* to make slirp_state.h macro hackery work */
349
350 nreplies = IcmpParseReplies(pong->buf, (DWORD)pong->bufsize);
351 if (nreplies == 0)
352 {
353 DWORD error = GetLastError();
354 if (error == IP_REQ_TIMED_OUT)
355 Log2(("NAT: ping %p timed out\n", (void *)pong));
356 else
357 Log2(("NAT: ping %p: IcmpParseReplies: error %d\n",
358 (void *)pong, error));
359 return;
360 }
361
362 reply = (ICMP_ECHO_REPLY *)pong->buf;
363
364 if (reply->Status == IP_SUCCESS)
365 {
366 if (reply->Options.OptionsSize != 0) /* don't do options */
367 return;
368
369 /* need to remap &reply->Address ? */
370 if (/* not a mapped loopback */ 1)
371 {
372 if (reply->Options.Ttl <= 1)
373 return;
374 --reply->Options.Ttl;
375 }
376
377 reqsize = reply->DataSize;
378 if ( (reply->Options.Flags & IP_FLAG_DF) != 0
379 && sizeof(struct ip) + sizeof(struct icmp_echo) + reqsize > if_mtu)
380 return;
381
382 m = icmpwin_get_mbuf(pData, reqsize);
383 if (m == NULL)
384 return;
385
386 ip = mtod(m, struct ip *);
387 icmp = (struct icmp_echo *)(mtod(m, char *) + sizeof(*ip));
388
389 /* fill in ip (ip_output0() does the boilerplate for us) */
390 ip->ip_tos = reply->Options.Tos;
391 ip->ip_len = sizeof(*ip) + sizeof(*icmp) + reqsize;
392 ip->ip_off = 0;
393 ip->ip_ttl = reply->Options.Ttl;
394 ip->ip_p = IPPROTO_ICMP;
395 ip->ip_src.s_addr = reply->Address;
396 ip->ip_dst = pong->reqiph.ip_src;
397
398 icmp->icmp_type = ICMP_ECHOREPLY;
399 icmp->icmp_code = 0;
400 icmp->icmp_cksum = 0;
401 icmp->icmp_echo_id = pong->reqicmph.icmp_echo_id;
402 icmp->icmp_echo_seq = pong->reqicmph.icmp_echo_seq;
403
404 m_append(pData, m, reqsize, reply->Data);
405
406 icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip));
407 }
408 else {
409 uint8_t type, code;
410
411 switch (reply->Status) {
412 case IP_DEST_NET_UNREACHABLE:
413 type = ICMP_UNREACH; code = ICMP_UNREACH_NET;
414 break;
415 case IP_DEST_HOST_UNREACHABLE:
416 type = ICMP_UNREACH; code = ICMP_UNREACH_HOST;
417 break;
418 case IP_DEST_PROT_UNREACHABLE:
419 type = ICMP_UNREACH; code = ICMP_UNREACH_PROTOCOL;
420 break;
421 case IP_PACKET_TOO_BIG:
422 type = ICMP_UNREACH; code = ICMP_UNREACH_NEEDFRAG;
423 break;
424 case IP_SOURCE_QUENCH:
425 type = ICMP_SOURCEQUENCH; code = 0;
426 break;
427 case IP_TTL_EXPIRED_TRANSIT:
428 type = ICMP_TIMXCEED; code = ICMP_TIMXCEED_INTRANS;
429 break;
430 case IP_TTL_EXPIRED_REASSEM:
431 type = ICMP_TIMXCEED; code = ICMP_TIMXCEED_REASS;
432 break;
433 default:
434 Log2(("NAT: ping reply status %d, dropped\n", reply->Status));
435 return;
436 }
437
438 Log2(("NAT: ping status %d -> type %d/code %d\n",
439 reply->Status, type, code));
440
441 /*
442 * XXX: we don't know the TTL of the request at the time this
443 * ICMP error was generated (we can guess it was 1 for ttl
444 * exceeded, but don't bother faking it).
445 */
446 m = icmpwin_get_error(pong, type, code);
447 if (m == NULL)
448 return;
449
450 ip = mtod(m, struct ip *);
451
452 ip->ip_tos = reply->Options.Tos;
453 ip->ip_ttl = reply->Options.Ttl; /* XXX: decrement */
454 ip->ip_src.s_addr = reply->Address;
455 }
456
457 Assert(ip->ip_len == m_length(m, NULL));
458 ip_output(pData, NULL, m);
459}
460
461
462/*
463 * Prepare mbuf with ICMP error type/code.
464 * IP source must be filled by the caller.
465 */
466static struct mbuf *
467icmpwin_get_error(struct pong *pong, int type, int code)
468{
469 PNATState pData = pong->pData;
470 struct mbuf *m;
471 struct ip *ip;
472 struct icmp_echo *icmp;
473 size_t reqsize;
474
475 Log2(("NAT: ping error type %d/code %d\n", type, code));
476
477 reqsize = sizeof(pong->reqiph) + sizeof(pong->reqicmph);
478
479 m = icmpwin_get_mbuf(pData, reqsize);
480 if (m == NULL)
481 return NULL;
482
483 ip = mtod(m, struct ip *);
484 icmp = (struct icmp_echo *)(mtod(m, char *) + sizeof(*ip));
485
486 ip->ip_tos = 0;
487 ip->ip_len = sizeof(*ip) + sizeof(*icmp) + reqsize;
488 ip->ip_off = 0;
489 ip->ip_ttl = IPDEFTTL;
490 ip->ip_p = IPPROTO_ICMP;
491 ip->ip_src.s_addr = 0; /* NB */
492 ip->ip_dst = pong->reqiph.ip_src;
493
494 icmp->icmp_type = type;
495 icmp->icmp_code = code;
496 icmp->icmp_cksum = 0;
497 icmp->icmp_echo_id = 0;
498 icmp->icmp_echo_seq = 0;
499
500 m_append(pData, m, sizeof(pong->reqiph), (caddr_t)&pong->reqiph);
501 m_append(pData, m, sizeof(pong->reqicmph), (caddr_t)&pong->reqicmph);
502
503 icmp->icmp_cksum = in_cksum_skip(m, ip->ip_len, sizeof(*ip));
504
505 return m;
506}
507
508
509/*
510 * Replacing original simple slirp mbufs with real mbufs from freebsd
511 * was a bit messy since assumption are different. This leads to
512 * rather ugly code at times. Hide the gore here.
513 */
514static struct mbuf *
515icmpwin_get_mbuf(PNATState pData, size_t reqsize)
516{
517 struct mbuf *m;
518
519 reqsize += if_maxlinkhdr;
520 reqsize += sizeof(struct ip) + sizeof(struct icmp_echo);
521
522 if (reqsize <= MHLEN)
523 /* good pings come in small packets */
524 m = m_gethdr(pData, M_NOWAIT, MT_HEADER);
525 else
526 m = m_getjcl(pData, M_NOWAIT, MT_HEADER, M_PKTHDR, slirp_size(pData));
527
528 if (m == NULL)
529 return NULL;
530
531 m->m_flags |= M_SKIP_FIREWALL;
532 m->m_data += if_maxlinkhdr; /* reserve leading space for ethernet header */
533
534 m->m_pkthdr.header = mtod(m, void *);
535 m->m_len = sizeof(struct ip) + sizeof(struct icmp_echo);
536
537 return m;
538}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use