VirtualBox

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

Last change on this file since 51300 was 50004, checked in by vboxsync, 11 years ago

Simple ping proxy that uses rather limited but unprivileged Windows Icmp API.

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