VirtualBox

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

Last change on this file since 93902 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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