VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

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