diff options
Diffstat (limited to 'src/VBox/NetworkServices/NAT/pxping_win.c')
-rw-r--r-- | src/VBox/NetworkServices/NAT/pxping_win.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/NAT/pxping_win.c b/src/VBox/NetworkServices/NAT/pxping_win.c new file mode 100644 index 00000000..47316513 --- /dev/null +++ b/src/VBox/NetworkServices/NAT/pxping_win.c @@ -0,0 +1,672 @@ +/* $Id: pxping_win.c $ */ +/** @file + * NAT Network - ping proxy, Windows ICMP API version. + */ + +/* + * Copyright (C) 2013-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_NAT_SERVICE + +#include "winutils.h" +#include "proxy.h" +#include "pxremap.h" + +#include "lwip/ip.h" +#include "lwip/icmp.h" +#include "lwip/inet_chksum.h" + +/* XXX: lwIP names conflict with winsock <iphlpapi.h> */ +#undef IP_STATS +#undef ICMP_STATS +#undef TCP_STATS +#undef UDP_STATS +#undef IP6_STATS + +#include <winternl.h> /* for PIO_APC_ROUTINE &c */ +#ifndef PIO_APC_ROUTINE_DEFINED +# define PIO_APC_ROUTINE_DEFINED 1 +#endif +#include <iprt/win/iphlpapi.h> +#include <icmpapi.h> + +#include <stdio.h> + + +struct pxping { + /* + * We use single ICMP handle for all pings. This means that all + * proxied pings will have the same id and share single sequence + * of sequence numbers. + */ + HANDLE hdl4; + HANDLE hdl6; + + struct netif *netif; + + /* + * On Windows XP and Windows Server 2003 IcmpSendEcho2() callback + * is FARPROC, but starting from Vista it's PIO_APC_ROUTINE with + * two extra arguments. Callbacks use WINAPI (stdcall) calling + * convention with callee responsible for popping the arguments, + * so to avoid stack corruption we check windows version at run + * time and provide correct callback. + */ + PIO_APC_ROUTINE pfnCallback4; + PIO_APC_ROUTINE pfnCallback6; +}; + + +struct pong4 { + struct netif *netif; + + struct ip_hdr reqiph; + struct icmp_echo_hdr reqicmph; + + size_t bufsize; + u8_t buf[1]; +}; + + +struct pong6 { + struct netif *netif; + + ip6_addr_t reqsrc; + struct icmp6_echo_hdr reqicmph; + size_t reqsize; + + size_t bufsize; + u8_t buf[1]; +}; + + +static void pxping_recv4(void *arg, struct pbuf *p); +static void pxping_recv6(void *arg, struct pbuf *p); + +static VOID WINAPI pxping_icmp4_callback_old(void *); +static VOID WINAPI pxping_icmp4_callback_apc(void *, PIO_STATUS_BLOCK, ULONG); +static void pxping_icmp4_callback(struct pong4 *pong); + +static VOID WINAPI pxping_icmp6_callback_old(void *); +static VOID WINAPI pxping_icmp6_callback_apc(void *, PIO_STATUS_BLOCK, ULONG); +static void pxping_icmp6_callback(struct pong6 *pong); + + +struct pxping g_pxping; + + +err_t +pxping_init(struct netif *netif, SOCKET sock4, SOCKET sock6) +{ + OSVERSIONINFO osvi; + int status; + + LWIP_UNUSED_ARG(sock4); + LWIP_UNUSED_ARG(sock6); + + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + status = GetVersionEx(&osvi); + if (status == 0) { + return ERR_ARG; + } + + if (osvi.dwMajorVersion >= 6) { + g_pxping.pfnCallback4 = pxping_icmp4_callback_apc; + g_pxping.pfnCallback6 = pxping_icmp6_callback_apc; + } + else { + g_pxping.pfnCallback4 = (PIO_APC_ROUTINE)pxping_icmp4_callback_old; + g_pxping.pfnCallback6 = (PIO_APC_ROUTINE)pxping_icmp6_callback_old; + } + + + g_pxping.hdl4 = IcmpCreateFile(); + if (g_pxping.hdl4 != INVALID_HANDLE_VALUE) { + ping_proxy_accept(pxping_recv4, &g_pxping); + } + else { + DPRINTF(("IcmpCreateFile: error %d\n", GetLastError())); + } + + g_pxping.hdl6 = Icmp6CreateFile(); + if (g_pxping.hdl6 != INVALID_HANDLE_VALUE) { + ping6_proxy_accept(pxping_recv6, &g_pxping); + } + else { + DPRINTF(("Icmp6CreateFile: error %d\n", GetLastError())); + } + + if (g_pxping.hdl4 == INVALID_HANDLE_VALUE + && g_pxping.hdl6 == INVALID_HANDLE_VALUE) + { + return ERR_ARG; + } + + g_pxping.netif = netif; + + return ERR_OK; +} + + +/** + * ICMP Echo Request in pbuf "p" is to be proxied. + */ +static void +pxping_recv4(void *arg, struct pbuf *p) +{ + struct pxping *pxping = (struct pxping *)arg; + const struct ip_hdr *iph; + const struct icmp_echo_hdr *icmph; + u16_t iphlen; + size_t bufsize; + struct pong4 *pong; + IPAddr dst; + int mapped; + int ttl; + IP_OPTION_INFORMATION opts; + void *reqdata; + size_t reqsize; + int status; + + pong = NULL; + + iphlen = ip_current_header_tot_len(); + if (RT_UNLIKELY(iphlen != IP_HLEN)) { /* we don't do options */ + goto out; + } + + iph = (const struct ip_hdr *)ip_current_header(); + icmph = (const struct icmp_echo_hdr *)p->payload; + + mapped = pxremap_outbound_ip4((ip_addr_t *)&dst, (ip_addr_t *)&iph->dest); + if (RT_UNLIKELY(mapped == PXREMAP_FAILED)) { + goto out; + } + + ttl = IPH_TTL(iph); + if (mapped == PXREMAP_ASIS) { + if (RT_UNLIKELY(ttl == 1)) { + status = pbuf_header(p, iphlen); /* back to IP header */ + if (RT_LIKELY(status == 0)) { + icmp_time_exceeded(p, ICMP_TE_TTL); + } + goto out; + } + --ttl; + } + + status = pbuf_header(p, -(u16_t)sizeof(*icmph)); /* to ping payload */ + if (RT_UNLIKELY(status != 0)) { + goto out; + } + + bufsize = sizeof(ICMP_ECHO_REPLY); + if (p->tot_len < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo_hdr)) + bufsize += sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp_echo_hdr); + else + bufsize += p->tot_len; + bufsize += 16; /* whatever that is; empirically at least XP needs it */ + + pong = (struct pong4 *)malloc(RT_UOFFSETOF(struct pong4, buf) + bufsize); + if (RT_UNLIKELY(pong == NULL)) { + goto out; + } + pong->bufsize = bufsize; + pong->netif = pxping->netif; + + memcpy(&pong->reqiph, iph, sizeof(*iph)); + memcpy(&pong->reqicmph, icmph, sizeof(*icmph)); + + reqsize = p->tot_len; + if (p->next == NULL) { + /* single pbuf can be directly used as request data source */ + reqdata = p->payload; + } + else { + /* data from pbuf chain must be concatenated */ + pbuf_copy_partial(p, pong->buf, p->tot_len, 0); + reqdata = pong->buf; + } + + opts.Ttl = ttl; + opts.Tos = IPH_TOS(iph); /* affected by DisableUserTOSSetting key */ + opts.Flags = (IPH_OFFSET(iph) & PP_HTONS(IP_DF)) != 0 ? IP_FLAG_DF : 0; + opts.OptionsSize = 0; + opts.OptionsData = 0; + + status = IcmpSendEcho2(pxping->hdl4, NULL, + pxping->pfnCallback4, pong, + dst, reqdata, (WORD)reqsize, &opts, + pong->buf, (DWORD)pong->bufsize, + 5 * 1000 /* ms */); + + if (RT_UNLIKELY(status != 0)) { + DPRINTF(("IcmpSendEcho2: unexpected status %d\n", status)); + goto out; + } + if ((status = GetLastError()) != ERROR_IO_PENDING) { + int code; + + DPRINTF(("IcmpSendEcho2: error %d\n", status)); + switch (status) { + case ERROR_NETWORK_UNREACHABLE: + code = ICMP_DUR_NET; + break; + case ERROR_HOST_UNREACHABLE: + code = ICMP_DUR_HOST; + break; + default: + code = -1; + break; + } + + if (code != -1) { + /* move payload back to IP header */ + status = pbuf_header(p, (u16_t)(sizeof(*icmph) + iphlen)); + if (RT_LIKELY(status == 0)) { + icmp_dest_unreach(p, code); + } + } + goto out; + } + + pong = NULL; /* callback owns it now */ + out: + if (pong != NULL) { + free(pong); + } + pbuf_free(p); +} + + +static VOID WINAPI +pxping_icmp4_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved) +{ + struct pong4 *pong = (struct pong4 *)ctx; + LWIP_UNUSED_ARG(iob); + LWIP_UNUSED_ARG(reserved); + + if (pong != NULL) { + pxping_icmp4_callback(pong); + free(pong); + } +} + + +static VOID WINAPI +pxping_icmp4_callback_old(void *ctx) +{ + struct pong4 *pong = (struct pong4 *)ctx; + + if (pong != NULL) { + pxping_icmp4_callback(pong); + free(pong); + } +} + + +static void +pxping_icmp4_callback(struct pong4 *pong) +{ + ICMP_ECHO_REPLY *reply; + DWORD nreplies; + size_t icmplen; + struct pbuf *p; + struct icmp_echo_hdr *icmph; + ip_addr_t src; + int mapped; + + nreplies = IcmpParseReplies(pong->buf, (DWORD)pong->bufsize); + if (nreplies == 0) { + DWORD error = GetLastError(); + if (error == IP_REQ_TIMED_OUT) { + DPRINTF2(("pong4: %p timed out\n", (void *)pong)); + } + else { + DPRINTF(("pong4: %p: IcmpParseReplies: error %d\n", + (void *)pong, error)); + } + return; + } + + reply = (ICMP_ECHO_REPLY *)pong->buf; + + if (reply->Options.OptionsSize != 0) { /* don't do options */ + return; + } + + mapped = pxremap_inbound_ip4(&src, (ip_addr_t *)&reply->Address); + if (mapped == PXREMAP_FAILED) { + return; + } + if (mapped == PXREMAP_ASIS) { + if (reply->Options.Ttl == 1) { + return; + } + --reply->Options.Ttl; + } + + if (reply->Status == IP_SUCCESS) { + icmplen = sizeof(struct icmp_echo_hdr) + reply->DataSize; + if ((reply->Options.Flags & IP_FLAG_DF) != 0 + && IP_HLEN + icmplen > pong->netif->mtu) + { + return; + } + + p = pbuf_alloc(PBUF_IP, (u16_t)icmplen, PBUF_RAM); + if (RT_UNLIKELY(p == NULL)) { + return; + } + + icmph = (struct icmp_echo_hdr *)p->payload; + icmph->type = ICMP_ER; + icmph->code = 0; + icmph->chksum = 0; + icmph->id = pong->reqicmph.id; + icmph->seqno = pong->reqicmph.seqno; + + memcpy((u8_t *)p->payload + sizeof(*icmph), + reply->Data, reply->DataSize); + } + else { + u8_t type, code; + + switch (reply->Status) { + case IP_DEST_NET_UNREACHABLE: + type = ICMP_DUR; code = ICMP_DUR_NET; + break; + case IP_DEST_HOST_UNREACHABLE: + type = ICMP_DUR; code = ICMP_DUR_HOST; + break; + case IP_DEST_PROT_UNREACHABLE: + type = ICMP_DUR; code = ICMP_DUR_PROTO; + break; + case IP_PACKET_TOO_BIG: + type = ICMP_DUR; code = ICMP_DUR_FRAG; + break; + case IP_SOURCE_QUENCH: + type = ICMP_SQ; code = 0; + break; + case IP_TTL_EXPIRED_TRANSIT: + type = ICMP_TE; code = ICMP_TE_TTL; + break; + case IP_TTL_EXPIRED_REASSEM: + type = ICMP_TE; code = ICMP_TE_FRAG; + break; + default: + DPRINTF(("pong4: reply status %d, dropped\n", reply->Status)); + return; + } + + DPRINTF(("pong4: reply status %d -> type %d/code %d\n", + reply->Status, type, code)); + + icmplen = sizeof(*icmph) + sizeof(pong->reqiph) + sizeof(pong->reqicmph); + + p = pbuf_alloc(PBUF_IP, (u16_t)icmplen, PBUF_RAM); + if (RT_UNLIKELY(p == NULL)) { + return; + } + + icmph = (struct icmp_echo_hdr *)p->payload; + icmph->type = type; + icmph->code = code; + icmph->chksum = 0; + icmph->id = 0; + icmph->seqno = 0; + + /* + * XXX: we don't know the TTL of the request at the time this + * ICMP error was generated (we can guess it was 1 for ttl + * exceeded, but don't bother faking it). + */ + memcpy((u8_t *)p->payload + sizeof(*icmph), + &pong->reqiph, sizeof(pong->reqiph)); + + memcpy((u8_t *)p->payload + sizeof(*icmph) + sizeof(pong->reqiph), + &pong->reqicmph, sizeof(pong->reqicmph)); + } + + icmph->chksum = inet_chksum(p->payload, (u16_t)icmplen); + ip_output_if(p, &src, + (ip_addr_t *)&pong->reqiph.src, /* dst */ + reply->Options.Ttl, + reply->Options.Tos, + IPPROTO_ICMP, + pong->netif); + pbuf_free(p); +} + + +static void +pxping_recv6(void *arg, struct pbuf *p) +{ + struct pxping *pxping = (struct pxping *)arg; + struct icmp6_echo_hdr *icmph; + size_t bufsize; + struct pong6 *pong; + int mapped; + void *reqdata; + size_t reqsize; + struct sockaddr_in6 src, dst; + int hopl; + IP_OPTION_INFORMATION opts; + int status; + + pong = NULL; + + icmph = (struct icmp6_echo_hdr *)p->payload; + + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; + mapped = pxremap_outbound_ip6((ip6_addr_t *)&dst.sin6_addr, + ip6_current_dest_addr()); + if (RT_UNLIKELY(mapped == PXREMAP_FAILED)) { + goto out; + } + + hopl = IP6H_HOPLIM(ip6_current_header()); + if (mapped == PXREMAP_ASIS) { + if (RT_UNLIKELY(hopl == 1)) { + status = pbuf_header(p, ip_current_header_tot_len()); + if (RT_LIKELY(status == 0)) { + icmp6_time_exceeded(p, ICMP6_TE_HL); + } + goto out; + } + --hopl; + } + + status = pbuf_header(p, -(u16_t)sizeof(*icmph)); /* to ping payload */ + if (RT_UNLIKELY(status != 0)) { + goto out; + } + + /* XXX: parrotted from IPv4 version, not tested all os version/bitness */ + bufsize = sizeof(ICMPV6_ECHO_REPLY); + if (p->tot_len < sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp6_echo_hdr)) + bufsize += sizeof(IO_STATUS_BLOCK) + sizeof(struct icmp6_echo_hdr); + else + bufsize += p->tot_len; + bufsize += 16; + + pong = (struct pong6 *)malloc(RT_UOFFSETOF(struct pong6, buf) + bufsize); + if (RT_UNLIKELY(pong == NULL)) { + goto out; + } + pong->bufsize = bufsize; + pong->netif = pxping->netif; + + ip6_addr_copy(pong->reqsrc, *ip6_current_src_addr()); + memcpy(&pong->reqicmph, icmph, sizeof(*icmph)); + + memset(pong->buf, 0xa5, pong->bufsize); + + pong->reqsize = reqsize = p->tot_len; + if (p->next == NULL) { + /* single pbuf can be directly used as request data source */ + reqdata = p->payload; + } + else { + /* data from pbuf chain must be concatenated */ + pbuf_copy_partial(p, pong->buf, p->tot_len, 0); + reqdata = pong->buf; + } + + memset(&src, 0, sizeof(src)); + src.sin6_family = AF_INET6; + src.sin6_addr = in6addr_any; /* let the OS select host source address */ + + memset(&opts, 0, sizeof(opts)); + opts.Ttl = hopl; + + status = Icmp6SendEcho2(pxping->hdl6, NULL, + pxping->pfnCallback6, pong, + &src, &dst, reqdata, (WORD)reqsize, &opts, + pong->buf, (DWORD)pong->bufsize, + 5 * 1000 /* ms */); + + if (RT_UNLIKELY(status != 0)) { + DPRINTF(("Icmp6SendEcho2: unexpected status %d\n", status)); + goto out; + } + if ((status = GetLastError()) != ERROR_IO_PENDING) { + int code; + + DPRINTF(("Icmp6SendEcho2: error %d\n", status)); + switch (status) { + case ERROR_NETWORK_UNREACHABLE: + case ERROR_HOST_UNREACHABLE: + code = ICMP6_DUR_NO_ROUTE; + break; + default: + code = -1; + break; + } + + if (code != -1) { + /* move payload back to IP header */ + status = pbuf_header(p, (u16_t)(sizeof(*icmph) + + ip_current_header_tot_len())); + if (RT_LIKELY(status == 0)) { + icmp6_dest_unreach(p, code); + } + } + goto out; + } + + pong = NULL; /* callback owns it now */ + out: + if (pong != NULL) { + free(pong); + } + pbuf_free(p); +} + + +static VOID WINAPI +pxping_icmp6_callback_apc(void *ctx, PIO_STATUS_BLOCK iob, ULONG reserved) +{ + struct pong6 *pong = (struct pong6 *)ctx; + LWIP_UNUSED_ARG(iob); + LWIP_UNUSED_ARG(reserved); + + if (pong != NULL) { + pxping_icmp6_callback(pong); + free(pong); + } +} + + +static VOID WINAPI +pxping_icmp6_callback_old(void *ctx) +{ + struct pong6 *pong = (struct pong6 *)ctx; + + if (pong != NULL) { + pxping_icmp6_callback(pong); + free(pong); + } +} + + +static void +pxping_icmp6_callback(struct pong6 *pong) +{ + DWORD nreplies; + ICMPV6_ECHO_REPLY *reply; + struct pbuf *p; + struct icmp6_echo_hdr *icmph; + size_t icmplen; + ip6_addr_t src; + int mapped; + + nreplies = Icmp6ParseReplies(pong->buf, (DWORD)pong->bufsize); + if (nreplies == 0) { + DWORD error = GetLastError(); + if (error == IP_REQ_TIMED_OUT) { + DPRINTF2(("pong6: %p timed out\n", (void *)pong)); + } + else { + DPRINTF(("pong6: %p: Icmp6ParseReplies: error %d\n", + (void *)pong, error)); + } + return; + } + + reply = (ICMPV6_ECHO_REPLY *)pong->buf; + + mapped = pxremap_inbound_ip6(&src, (ip6_addr_t *)reply->Address.sin6_addr); + if (mapped == PXREMAP_FAILED) { + return; + } + + /* + * Reply data follows ICMPV6_ECHO_REPLY structure in memory, but + * it doesn't tell us its size. Assume it's equal the size of the + * request. + */ + icmplen = sizeof(*icmph) + pong->reqsize; + p = pbuf_alloc(PBUF_IP, (u16_t)icmplen, PBUF_RAM); + if (RT_UNLIKELY(p == NULL)) { + return; + } + + icmph = (struct icmp6_echo_hdr *)p->payload; + icmph->type = ICMP6_TYPE_EREP; + icmph->code = 0; + icmph->chksum = 0; + icmph->id = pong->reqicmph.id; + icmph->seqno = pong->reqicmph.seqno; + + memcpy((u8_t *)p->payload + sizeof(*icmph), + pong->buf + sizeof(*reply), pong->reqsize); + + icmph->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len, + &src, &pong->reqsrc); + ip6_output_if(p, /* :src */ &src, /* :dst */ &pong->reqsrc, + LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, + pong->netif); + pbuf_free(p); +} |