VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/NAT/pxping_win.c@ 60066

Last change on this file since 60066 was 56300, checked in by vboxsync, 9 years ago

NetworkServices: Updated (C) year.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.4 KB
Line 
1/* $Id: pxping_win.c 56300 2015-06-09 14:36:22Z vboxsync $ */
2/** @file
3 * NAT Network - ping proxy, Windows ICMP API version.
4 */
5
6/*
7 * Copyright (C) 2013-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#define LOG_GROUP LOG_GROUP_NAT_SERVICE
19
20#include "winutils.h"
21#include "proxy.h"
22#include "pxremap.h"
23
24#include "lwip/ip.h"
25#include "lwip/icmp.h"
26#include "lwip/inet_chksum.h"
27
28/* XXX: lwIP names conflict with winsock <iphlpapi.h> */
29#undef IP_STATS
30#undef ICMP_STATS
31#undef TCP_STATS
32#undef UDP_STATS
33#undef IP6_STATS
34
35#include <winternl.h> /* for PIO_APC_ROUTINE &c */
36#include <iphlpapi.h>
37#include <icmpapi.h>
38
39#include <stdio.h>
40
41
42struct pxping {
43 /*
44 * We use single ICMP handle for all pings. This means that all
45 * proxied pings will have the same id and share single sequence
46 * of sequence numbers.
47 */
48 HANDLE hdl4;
49 HANDLE hdl6;
50
51 struct netif *netif;
52
53 /*
54 * On Windows XP and Windows Server 2003 IcmpSendEcho2() callback
55 * is FARPROC, but starting from Vista it's PIO_APC_ROUTINE with
56 * two extra arguments. Callbacks use WINAPI (stdcall) calling
57 * convention with callee responsible for popping the arguments,
58 * so to avoid stack corruption we check windows version at run
59 * time and provide correct callback.
60 */
61 void *callback4;
62 void *callback6;
63};
64
65
66struct pong4 {
67 struct netif *netif;
68
69 struct ip_hdr reqiph;
70 struct icmp_echo_hdr reqicmph;
71
72 size_t bufsize;
73 u8_t buf[1];
74};
75
76
77struct pong6 {
78 struct netif *netif;
79
80 ip6_addr_t reqsrc;
81 struct icmp6_echo_hdr reqicmph;
82 size_t reqsize;
83
84 size_t bufsize;
85 u8_t buf[1];
86};
87
88
89static void pxping_recv4(void *arg, struct pbuf *p);
90static void pxping_recv6(void *arg, struct pbuf *p);
91
92static VOID WINAPI pxping_icmp4_callback_old(void *);
93static VOID WINAPI pxping_icmp4_callback_apc(void *, PIO_STATUS_BLOCK, ULONG);
94static void pxping_icmp4_callback(struct pong4 *pong);
95
96static VOID WINAPI pxping_icmp6_callback_old(void *);
97static VOID WINAPI pxping_icmp6_callback_apc(void *, PIO_STATUS_BLOCK, ULONG);
98static void pxping_icmp6_callback(struct pong6 *pong);
99
100
101struct pxping g_pxping;
102
103
104err_t
105pxping_init(struct netif *netif, SOCKET sock4, SOCKET sock6)
106{
107 OSVERSIONINFO osvi;
108 int status;
109
110 LWIP_UNUSED_ARG(sock4);
111 LWIP_UNUSED_ARG(sock6);
112
113 ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
114 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
115 status = GetVersionEx(&osvi);
116 if (status == 0) {
117 return ERR_ARG;
118 }
119
120 if (osvi.dwMajorVersion >= 6) {
121 g_pxping.callback4 = (void *)pxping_icmp4_callback_apc;
122 g_pxping.callback6 = (void *)pxping_icmp6_callback_apc;
123 }
124 else {
125 g_pxping.callback4 = (void *)pxping_icmp4_callback_old;
126 g_pxping.callback6 = (void *)pxping_icmp6_callback_old;
127 }
128
129
130 g_pxping.hdl4 = IcmpCreateFile();
131 if (g_pxping.hdl4 != INVALID_HANDLE_VALUE) {
132 ping_proxy_accept(pxping_recv4, &g_pxping);
133 }
134 else {
135 DPRINTF(("IcmpCreateFile: error %d\n", GetLastError()));
136 }
137
138 g_pxping.hdl6 = Icmp6CreateFile();
139 if (g_pxping.hdl6 != INVALID_HANDLE_VALUE) {
140 ping6_proxy_accept(pxping_recv6, &g_pxping);
141 }
142 else {
143 DPRINTF(("Icmp6CreateFile: error %d\n", GetLastError()));
144 }
145
146 if (g_pxping.hdl4 == INVALID_HANDLE_VALUE
147 && g_pxping.hdl6 == INVALID_HANDLE_VALUE)
148 {
149 return ERR_ARG;
150 }
151
152 g_pxping.netif = netif;
153
154 return ERR_OK;
155}
156
157
158/**
159 * ICMP Echo Request in pbuf "p" is to be proxied.
160 */
161static void
162pxping_recv4(void *arg, struct pbuf *p)
163{
164 struct pxping *pxping = (struct pxping *)arg;
165 const struct ip_hdr *iph;
166 const struct icmp_echo_hdr *icmph;
167 u16_t iphlen;
168 size_t bufsize;
169 struct pong4 *pong;
170 IPAddr dst;
171 int mapped;
172 int ttl;
173 IP_OPTION_INFORMATION opts;
174 void *reqdata;
175 size_t reqsize;
176 int status;
177
178 pong = NULL;
179
180 iphlen = ip_current_header_tot_len();
181 if (RT_UNLIKELY(iphlen != IP_HLEN)) { /* we don't do options */
182 goto out;
183 }
184
185 iph = (const struct ip_hdr *)ip_current_header();
186 icmph = (const struct icmp_echo_hdr *)p->payload;
187
188 mapped = pxremap_outbound_ip4((ip_addr_t *)&dst, (ip_addr_t *)&iph->dest);
189 if (RT_UNLIKELY(mapped == PXREMAP_FAILED)) {
190 goto out;
191 }
192
193 ttl = IPH_TTL(iph);
194 if (mapped == PXREMAP_ASIS) {
195 if (RT_UNLIKELY(ttl == 1)) {
196 status = pbuf_header(p, iphlen); /* back to IP header */
197 if (RT_LIKELY(status == 0)) {
198 icmp_time_exceeded(p, ICMP_TE_TTL);
199 }
200 goto out;
201 }
202 --ttl;
203 }
204
205 status = pbuf_header(p, -(u16_t)sizeof(*icmph)); /* to ping payload */
206 if (RT_UNLIKELY(status != 0)) {
207 goto out;
208 }
209
210 bufsize = sizeof(ICMP_ECHO_REPLY);
211 if (p->tot_len < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo_hdr))
212 bufsize += sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo_hdr);
213 else
214 bufsize += p->tot_len;
215 bufsize += 16; /* whatever that is; empirically at least XP needs it */
216
217 pong = (struct pong4 *)malloc(RT_OFFSETOF(struct pong4, buf) + bufsize);
218 if (RT_UNLIKELY(pong == NULL)) {
219 goto out;
220 }
221 pong->bufsize = bufsize;
222 pong->netif = pxping->netif;
223
224 memcpy(&pong->reqiph, iph, sizeof(*iph));
225 memcpy(&pong->reqicmph, icmph, sizeof(*icmph));
226
227 reqsize = p->tot_len;
228 if (p->next == NULL) {
229 /* single pbuf can be directly used as request data source */
230 reqdata = p->payload;
231 }
232 else {
233 /* data from pbuf chain must be concatenated */
234 pbuf_copy_partial(p, pong->buf, p->tot_len, 0);
235 reqdata = pong->buf;
236 }
237
238 opts.Ttl = ttl;
239 opts.Tos = IPH_TOS(iph); /* affected by DisableUserTOSSetting key */
240 opts.Flags = (IPH_OFFSET(iph) & PP_HTONS(IP_DF)) != 0 ? IP_FLAG_DF : 0;
241 opts.OptionsSize = 0;
242 opts.OptionsData = 0;
243
244 status = IcmpSendEcho2(pxping->hdl4, NULL,
245 pxping->callback4, pong,
246 dst, reqdata, (WORD)reqsize, &opts,
247 pong->buf, (DWORD)pong->bufsize,
248 5 * 1000 /* ms */);
249
250 if (RT_UNLIKELY(status != 0)) {
251 DPRINTF(("IcmpSendEcho2: unexpected status %d\n", status));
252 goto out;
253 }
254 else if ((status = GetLastError()) != ERROR_IO_PENDING) {
255 int code;
256
257 DPRINTF(("IcmpSendEcho2: error %d\n", status));
258 switch (status) {
259 case ERROR_NETWORK_UNREACHABLE:
260 code = ICMP_DUR_NET;
261 break;
262 case ERROR_HOST_UNREACHABLE:
263 code = ICMP_DUR_HOST;
264 break;
265 default:
266 code = -1;
267 break;
268 }
269
270 if (code != -1) {
271 /* move payload back to IP header */
272 status = pbuf_header(p, (u16_t)(sizeof(*icmph) + iphlen));
273 if (RT_LIKELY(status == 0)) {
274 icmp_dest_unreach(p, code);
275 }
276 }
277 goto out;
278 }
279
280 pong = NULL; /* callback owns it now */
281 out:
282 if (pong != NULL) {
283 free(pong);
284 }
285 pbuf_free(p);
286}
287
288
289static VOID WINAPI
290pxping_icmp4_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved)
291{
292 struct pong4 *pong = (struct pong4 *)ctx;
293 LWIP_UNUSED_ARG(iob);
294 LWIP_UNUSED_ARG(reserved);
295
296 if (pong != NULL) {
297 pxping_icmp4_callback(pong);
298 free(pong);
299 }
300}
301
302
303static VOID WINAPI
304pxping_icmp4_callback_old(void *ctx)
305{
306 struct pong4 *pong = (struct pong4 *)ctx;
307
308 if (pong != NULL) {
309 pxping_icmp4_callback(pong);
310 free(pong);
311 }
312}
313
314
315static void
316pxping_icmp4_callback(struct pong4 *pong)
317{
318 ICMP_ECHO_REPLY *reply;
319 DWORD nreplies;
320 size_t icmplen;
321 struct pbuf *p;
322 struct icmp_echo_hdr *icmph;
323 ip_addr_t src;
324 int mapped;
325
326 nreplies = IcmpParseReplies(pong->buf, (DWORD)pong->bufsize);
327 if (nreplies == 0) {
328 DWORD error = GetLastError();
329 if (error == IP_REQ_TIMED_OUT) {
330 DPRINTF2(("pong4: %p timed out\n", (void *)pong));
331 }
332 else {
333 DPRINTF(("pong4: %p: IcmpParseReplies: error %d\n",
334 (void *)pong, error));
335 }
336 return;
337 }
338
339 reply = (ICMP_ECHO_REPLY *)pong->buf;
340
341 if (reply->Options.OptionsSize != 0) { /* don't do options */
342 return;
343 }
344
345 mapped = pxremap_inbound_ip4(&src, (ip_addr_t *)&reply->Address);
346 if (mapped == PXREMAP_FAILED) {
347 return;
348 }
349 if (mapped == PXREMAP_ASIS) {
350 if (reply->Options.Ttl == 1) {
351 return;
352 }
353 --reply->Options.Ttl;
354 }
355
356 if (reply->Status == IP_SUCCESS) {
357 icmplen = sizeof(struct icmp_echo_hdr) + reply->DataSize;
358 if ((reply->Options.Flags & IP_FLAG_DF) != 0
359 && IP_HLEN + icmplen > pong->netif->mtu)
360 {
361 return;
362 }
363
364 p = pbuf_alloc(PBUF_IP, (u16_t)icmplen, PBUF_RAM);
365 if (RT_UNLIKELY(p == NULL)) {
366 return;
367 }
368
369 icmph = (struct icmp_echo_hdr *)p->payload;
370 icmph->type = ICMP_ER;
371 icmph->code = 0;
372 icmph->chksum = 0;
373 icmph->id = pong->reqicmph.id;
374 icmph->seqno = pong->reqicmph.seqno;
375
376 memcpy((u8_t *)p->payload + sizeof(*icmph),
377 reply->Data, reply->DataSize);
378 }
379 else {
380 u8_t type, code;
381
382 switch (reply->Status) {
383 case IP_DEST_NET_UNREACHABLE:
384 type = ICMP_DUR; code = ICMP_DUR_NET;
385 break;
386 case IP_DEST_HOST_UNREACHABLE:
387 type = ICMP_DUR; code = ICMP_DUR_HOST;
388 break;
389 case IP_DEST_PROT_UNREACHABLE:
390 type = ICMP_DUR; code = ICMP_DUR_PROTO;
391 break;
392 case IP_PACKET_TOO_BIG:
393 type = ICMP_DUR; code = ICMP_DUR_FRAG;
394 break;
395 case IP_SOURCE_QUENCH:
396 type = ICMP_SQ; code = 0;
397 break;
398 case IP_TTL_EXPIRED_TRANSIT:
399 type = ICMP_TE; code = ICMP_TE_TTL;
400 break;
401 case IP_TTL_EXPIRED_REASSEM:
402 type = ICMP_TE; code = ICMP_TE_FRAG;
403 break;
404 default:
405 DPRINTF(("pong4: reply status %d, dropped\n", reply->Status));
406 return;
407 }
408
409 DPRINTF(("pong4: reply status %d -> type %d/code %d\n",
410 reply->Status, type, code));
411
412 icmplen = sizeof(*icmph) + sizeof(pong->reqiph) + sizeof(pong->reqicmph);
413
414 p = pbuf_alloc(PBUF_IP, (u16_t)icmplen, PBUF_RAM);
415 if (RT_UNLIKELY(p == NULL)) {
416 return;
417 }
418
419 icmph = (struct icmp_echo_hdr *)p->payload;
420 icmph->type = type;
421 icmph->code = code;
422 icmph->chksum = 0;
423 icmph->id = 0;
424 icmph->seqno = 0;
425
426 /*
427 * XXX: we don't know the TTL of the request at the time this
428 * ICMP error was generated (we can guess it was 1 for ttl
429 * exceeded, but don't bother faking it).
430 */
431 memcpy((u8_t *)p->payload + sizeof(*icmph),
432 &pong->reqiph, sizeof(pong->reqiph));
433
434 memcpy((u8_t *)p->payload + sizeof(*icmph) + sizeof(pong->reqiph),
435 &pong->reqicmph, sizeof(pong->reqicmph));
436 }
437
438 icmph->chksum = inet_chksum(p->payload, (u16_t)icmplen);
439 ip_output_if(p, &src,
440 (ip_addr_t *)&pong->reqiph.src, /* dst */
441 reply->Options.Ttl,
442 reply->Options.Tos,
443 IPPROTO_ICMP,
444 pong->netif);
445 pbuf_free(p);
446}
447
448
449static void
450pxping_recv6(void *arg, struct pbuf *p)
451{
452 struct pxping *pxping = (struct pxping *)arg;
453 struct icmp6_echo_hdr *icmph;
454 size_t bufsize;
455 struct pong6 *pong;
456 int mapped;
457 void *reqdata;
458 size_t reqsize;
459 struct sockaddr_in6 src, dst;
460 int hopl;
461 IP_OPTION_INFORMATION opts;
462 int status;
463
464 pong = NULL;
465
466 icmph = (struct icmp6_echo_hdr *)p->payload;
467
468 memset(&dst, 0, sizeof(dst));
469 dst.sin6_family = AF_INET6;
470 mapped = pxremap_outbound_ip6((ip6_addr_t *)&dst.sin6_addr,
471 ip6_current_dest_addr());
472 if (RT_UNLIKELY(mapped == PXREMAP_FAILED)) {
473 goto out;
474 }
475
476 hopl = IP6H_HOPLIM(ip6_current_header());
477 if (mapped == PXREMAP_ASIS) {
478 if (RT_UNLIKELY(hopl == 1)) {
479 status = pbuf_header(p, ip_current_header_tot_len());
480 if (RT_LIKELY(status == 0)) {
481 icmp6_time_exceeded(p, ICMP6_TE_HL);
482 }
483 goto out;
484 }
485 --hopl;
486 }
487
488 status = pbuf_header(p, -(u16_t)sizeof(*icmph)); /* to ping payload */
489 if (RT_UNLIKELY(status != 0)) {
490 goto out;
491 }
492
493 /* XXX: parrotted from IPv4 version, not tested all os version/bitness */
494 bufsize = sizeof(ICMPV6_ECHO_REPLY);
495 if (p->tot_len < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp6_echo_hdr))
496 bufsize += sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp6_echo_hdr);
497 else
498 bufsize += p->tot_len;
499 bufsize += 16;
500
501 pong = (struct pong6 *)malloc(RT_OFFSETOF(struct pong6, buf) + bufsize);
502 if (RT_UNLIKELY(pong == NULL)) {
503 goto out;
504 }
505 pong->bufsize = bufsize;
506 pong->netif = pxping->netif;
507
508 ip6_addr_copy(pong->reqsrc, *ip6_current_src_addr());
509 memcpy(&pong->reqicmph, icmph, sizeof(*icmph));
510
511 memset(pong->buf, 0xa5, pong->bufsize);
512
513 pong->reqsize = reqsize = p->tot_len;
514 if (p->next == NULL) {
515 /* single pbuf can be directly used as request data source */
516 reqdata = p->payload;
517 }
518 else {
519 /* data from pbuf chain must be concatenated */
520 pbuf_copy_partial(p, pong->buf, p->tot_len, 0);
521 reqdata = pong->buf;
522 }
523
524 memset(&src, 0, sizeof(src));
525 src.sin6_family = AF_INET6;
526 src.sin6_addr = in6addr_any; /* let the OS select host source address */
527
528 memset(&opts, 0, sizeof(opts));
529 opts.Ttl = hopl;
530
531 status = Icmp6SendEcho2(pxping->hdl6, NULL,
532 pxping->callback6, pong,
533 &src, &dst, reqdata, (WORD)reqsize, &opts,
534 pong->buf, (DWORD)pong->bufsize,
535 5 * 1000 /* ms */);
536
537 if (RT_UNLIKELY(status != 0)) {
538 DPRINTF(("Icmp6SendEcho2: unexpected status %d\n", status));
539 goto out;
540 }
541 else if ((status = GetLastError()) != ERROR_IO_PENDING) {
542 int code;
543
544 DPRINTF(("Icmp6SendEcho2: error %d\n", status));
545 switch (status) {
546 case ERROR_NETWORK_UNREACHABLE:
547 case ERROR_HOST_UNREACHABLE:
548 code = ICMP6_DUR_NO_ROUTE;
549 break;
550 default:
551 code = -1;
552 break;
553 }
554
555 if (code != -1) {
556 /* move payload back to IP header */
557 status = pbuf_header(p, (u16_t)(sizeof(*icmph)
558 + ip_current_header_tot_len()));
559 if (RT_LIKELY(status == 0)) {
560 icmp6_dest_unreach(p, code);
561 }
562 }
563 goto out;
564 }
565
566 pong = NULL; /* callback owns it now */
567 out:
568 if (pong != NULL) {
569 free(pong);
570 }
571 pbuf_free(p);
572}
573
574
575static VOID WINAPI
576pxping_icmp6_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved)
577{
578 struct pong6 *pong = (struct pong6 *)ctx;
579 LWIP_UNUSED_ARG(iob);
580 LWIP_UNUSED_ARG(reserved);
581
582 if (pong != NULL) {
583 pxping_icmp6_callback(pong);
584 free(pong);
585 }
586}
587
588
589static VOID WINAPI
590pxping_icmp6_callback_old(void *ctx)
591{
592 struct pong6 *pong = (struct pong6 *)ctx;
593
594 if (pong != NULL) {
595 pxping_icmp6_callback(pong);
596 free(pong);
597 }
598}
599
600
601static void
602pxping_icmp6_callback(struct pong6 *pong)
603{
604 DWORD nreplies;
605 ICMPV6_ECHO_REPLY *reply;
606 struct pbuf *p;
607 struct icmp6_echo_hdr *icmph;
608 size_t icmplen;
609 ip6_addr_t src;
610 int mapped;
611
612 nreplies = Icmp6ParseReplies(pong->buf, (DWORD)pong->bufsize);
613 if (nreplies == 0) {
614 DWORD error = GetLastError();
615 if (error == IP_REQ_TIMED_OUT) {
616 DPRINTF2(("pong6: %p timed out\n", (void *)pong));
617 }
618 else {
619 DPRINTF(("pong6: %p: Icmp6ParseReplies: error %d\n",
620 (void *)pong, error));
621 }
622 return;
623 }
624
625 reply = (ICMPV6_ECHO_REPLY *)pong->buf;
626
627 mapped = pxremap_inbound_ip6(&src, (ip6_addr_t *)reply->Address.sin6_addr);
628 if (mapped == PXREMAP_FAILED) {
629 return;
630 }
631
632 /*
633 * Reply data follows ICMPV6_ECHO_REPLY structure in memory, but
634 * it doesn't tell us its size. Assume it's equal the size of the
635 * request.
636 */
637 icmplen = sizeof(*icmph) + pong->reqsize;
638 p = pbuf_alloc(PBUF_IP, (u16_t)icmplen, PBUF_RAM);
639 if (RT_UNLIKELY(p == NULL)) {
640 return;
641 }
642
643 icmph = (struct icmp6_echo_hdr *)p->payload;
644 icmph->type = ICMP6_TYPE_EREP;
645 icmph->code = 0;
646 icmph->chksum = 0;
647 icmph->id = pong->reqicmph.id;
648 icmph->seqno = pong->reqicmph.seqno;
649
650 memcpy((u8_t *)p->payload + sizeof(*icmph),
651 pong->buf + sizeof(*reply), pong->reqsize);
652
653 icmph->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len,
654 &src, &pong->reqsrc);
655 ip6_output_if(p, /* :src */ &src, /* :dst */ &pong->reqsrc,
656 LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6,
657 pong->netif);
658 pbuf_free(p);
659}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette