However, don't yet wait for the reply.
Signed-off-by: Huw Davies huw@codeweavers.com --- dlls/nsiproxy.sys/Makefile.in | 1 + dlls/nsiproxy.sys/device.c | 37 ++- dlls/nsiproxy.sys/icmp_echo.c | 321 +++++++++++++++++++++++++++ dlls/nsiproxy.sys/nsi.c | 1 + dlls/nsiproxy.sys/nsiproxy_private.h | 28 +++ dlls/nsiproxy.sys/unix_private.h | 2 + 6 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 dlls/nsiproxy.sys/icmp_echo.c create mode 100644 dlls/nsiproxy.sys/nsiproxy_private.h
diff --git a/dlls/nsiproxy.sys/Makefile.in b/dlls/nsiproxy.sys/Makefile.in index 95f2a205816..87f7e165ef4 100644 --- a/dlls/nsiproxy.sys/Makefile.in +++ b/dlls/nsiproxy.sys/Makefile.in @@ -7,6 +7,7 @@ EXTRADLLFLAGS = -Wl,--subsystem,native
C_SRCS = \ device.c \ + icmp_echo.c \ ip.c \ ndis.c \ nsi.c \ diff --git a/dlls/nsiproxy.sys/device.c b/dlls/nsiproxy.sys/device.c index f4cefd4b794..5ae243a4111 100644 --- a/dlls/nsiproxy.sys/device.c +++ b/dlls/nsiproxy.sys/device.c @@ -33,6 +33,8 @@ #include "wine/debug.h" #include "wine/unixlib.h"
+#include "nsiproxy_private.h" + WINE_DEFAULT_DEBUG_CHANNEL(nsi);
static unixlib_handle_t nsiproxy_handle; @@ -56,6 +58,7 @@ static NTSTATUS nsiproxy_call( unsigned int code, void *args )
enum unix_calls { + icmp_send_echo, nsi_enumerate_all_ex, nsi_get_all_parameters_ex, nsi_get_parameter_ex, @@ -289,6 +292,36 @@ static int add_device( DRIVER_OBJECT *driver ) return 1; }
+static void handle_queued_send_echo( IRP *irp ) +{ + struct nsiproxy_icmp_echo *in = (struct nsiproxy_icmp_echo *)irp->AssociatedIrp.SystemBuffer; + struct nsiproxy_icmp_echo_reply *reply = (struct nsiproxy_icmp_echo_reply *)irp->AssociatedIrp.SystemBuffer; + struct icmp_send_echo_params params; + NTSTATUS status; + + TRACE( "\n" ); + params.request = in->data + ((in->opt_size + 3) & ~3); + params.request_size = in->req_size; + params.ttl = in->ttl; + params.tos = in->tos; + params.dst = &in->dst; + + status = nsiproxy_call( icmp_send_echo, ¶ms ); + TRACE( "icmp_send_echo rets %08x\n", status ); + + if (1) + { + irp->IoStatus.Status = status; + if (status == STATUS_SUCCESS) + { + memset( reply, 0, sizeof(*reply) ); + reply->status = params.ip_status; + irp->IoStatus.Information = sizeof(*reply); + } + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } +} + static DWORD WINAPI request_thread_proc( void *arg ) { LIST_ENTRY *entry; @@ -309,9 +342,7 @@ static DWORD WINAPI request_thread_proc( void *arg ) continue; }
- /* FIXME */ - irp->IoStatus.Status = STATUS_NOT_SUPPORTED; - IoCompleteRequest( irp, IO_NO_INCREMENT ); + handle_queued_send_echo( irp ); } LeaveCriticalSection( &nsiproxy_cs ); } diff --git a/dlls/nsiproxy.sys/icmp_echo.c b/dlls/nsiproxy.sys/icmp_echo.c new file mode 100644 index 00000000000..8ec506f823c --- /dev/null +++ b/dlls/nsiproxy.sys/icmp_echo.c @@ -0,0 +1,321 @@ +/* + * nsiproxy.sys icmp_echo implementation + * + * Copyright 2021 Huw Davies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#if 0 +#pragma makedep unix +#endif + +#define _NTSYSTEM_ + +#include "config.h" +#include <stdarg.h> + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <pthread.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winioctl.h" +#define USE_WS_PREFIX +#include "ddk/wdm.h" +#include "ifdef.h" +#include "netiodef.h" +#include "ipexport.h" +#include "ipmib.h" +#include "wine/nsi.h" +#include "wine/debug.h" + +#include "nsiproxy_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(nsi); + +static LONG icmp_sequence; + +struct ip_hdr +{ + uint8_t v_hl; /* version << 4 | hdr_len */ + uint8_t tos; + uint16_t tot_len; + uint16_t id; + uint16_t frag_off; + uint8_t ttl; + uint8_t protocol; + uint16_t checksum; + uint32_t saddr; + uint32_t daddr; +}; + +struct icmp_hdr +{ + uint8_t type; + uint8_t code; + uint16_t checksum; + union + { + struct + { + uint16_t id; + uint16_t sequence; + } echo; + } un; +}; + +struct family_ops; +struct icmp_data +{ + LARGE_INTEGER send_time; + int socket; + unsigned short id; + unsigned short seq; + const struct family_ops *ops; +}; + +static void ipv4_init_icmp_hdr( struct icmp_data *data, struct icmp_hdr *icmp_hdr ) +{ + icmp_hdr->type = ICMP4_ECHO_REQUEST; + icmp_hdr->code = 0; + icmp_hdr->checksum = 0; + icmp_hdr->un.echo.id = data->id = getpid() & 0xffff; /* will be overwritten for linux ping socks */ + icmp_hdr->un.echo.sequence = data->seq = InterlockedIncrement( &icmp_sequence ) & 0xffff; +} + +/* rfc 1071 checksum */ +static unsigned short chksum( BYTE *data, unsigned int count ) +{ + unsigned int sum = 0, carry = 0; + unsigned short check, s; + + while (count > 1) + { + s = *(unsigned short *)data; + data += 2; + sum += carry; + sum += s; + carry = s > sum; + count -= 2; + } + sum += carry; /* This won't produce another carry */ + sum = (sum & 0xffff) + (sum >> 16); + + if (count) sum += *data; /* LE-only */ + + sum = (sum & 0xffff) + (sum >> 16); + /* fold in any carry */ + sum = (sum & 0xffff) + (sum >> 16); + + check = ~sum; + return check; +} + +#ifdef __linux__ +static unsigned short null_chksum( BYTE *data, unsigned int count ) +{ + return 0; +} +#endif + +static void ipv4_set_socket_opts( struct icmp_data *data, struct icmp_send_echo_params *params ) +{ + int val; + + val = params->ttl; + if (val) setsockopt( data->socket, IPPROTO_IP, IP_TTL, &val, sizeof(val) ); + val = params->tos; + if (val) setsockopt( data->socket, IPPROTO_IP, IP_TOS, &val, sizeof(val) ); +} + +#ifdef __linux__ +static void ipv4_linux_ping_set_socket_opts( struct icmp_data *data, struct icmp_send_echo_params *params ) +{ + static const int val = 1; + + ipv4_set_socket_opts( data, params ); + + setsockopt( data->socket, IPPROTO_IP, IP_RECVTTL, &val, sizeof(val) ); + setsockopt( data->socket, IPPROTO_IP, IP_RECVTOS, &val, sizeof(val) ); +} +#endif + +struct family_ops +{ + int family; + int icmp_protocol; + void (*init_icmp_hdr)( struct icmp_data *data, struct icmp_hdr *icmp_hdr ); + unsigned short (*chksum)( BYTE *data, unsigned int count ); + void (*set_socket_opts)( struct icmp_data *data, struct icmp_send_echo_params *params ); +}; + +static const struct family_ops ipv4 = +{ + AF_INET, + IPPROTO_ICMP, + ipv4_init_icmp_hdr, + chksum, + ipv4_set_socket_opts, +}; + +#ifdef __linux__ +/* linux ipv4 ping sockets behave more like ipv6 raw sockets */ +static const struct family_ops ipv4_linux_ping = +{ + AF_INET, + IPPROTO_ICMP, + ipv4_init_icmp_hdr, + null_chksum, + ipv4_linux_ping_set_socket_opts, +}; +#endif + +static IP_STATUS errno_to_ip_status( int err ) +{ + switch( err ) + { + case EHOSTUNREACH: return IP_DEST_HOST_UNREACHABLE; + default: return IP_GENERAL_FAILURE; + } +} + +static int SOCKADDR_INET_to_sockaddr( const SOCKADDR_INET *in, struct sockaddr *out, int len ) +{ + switch (in->si_family) + { + case WS_AF_INET: + { + struct sockaddr_in *sa = (struct sockaddr_in *)out; + + if (len < sizeof(*sa)) return 0; + sa->sin_family = AF_INET; + sa->sin_port = in->Ipv4.sin_port; + sa->sin_addr.s_addr = in->Ipv4.sin_addr.WS_s_addr; + return sizeof(*sa); + } + case WS_AF_INET6: + { + struct sockaddr_in6 *sa = (struct sockaddr_in6 *)out; + + if (len < sizeof(*sa)) return 0; + sa->sin6_family = AF_INET6; + sa->sin6_port = in->Ipv6.sin6_port; + sa->sin6_flowinfo = in->Ipv6.sin6_flowinfo; + memcpy( sa->sin6_addr.s6_addr, in->Ipv6.sin6_addr.WS_s6_addr, sizeof(sa->sin6_addr.s6_addr) ); + sa->sin6_scope_id = in->Ipv6.sin6_scope_id; + return sizeof(*sa); + } + } + return 0; +} + +static NTSTATUS icmp_data_create( ADDRESS_FAMILY win_family, struct icmp_data **icmp_data ) +{ + struct icmp_data *data; + const struct family_ops *ops; + + if (win_family == WS_AF_INET) ops = &ipv4; + else return STATUS_INVALID_PARAMETER; + + data = malloc( sizeof(*data) ); + if (!data) return STATUS_NO_MEMORY; + + data->socket = socket( ops->family, SOCK_RAW, ops->icmp_protocol ); + if (data->socket < 0) /* Try a ping-socket */ + { + TRACE( "failed to open raw sock, trying a dgram sock\n" ); + data->socket = socket( ops->family, SOCK_DGRAM, ops->icmp_protocol ); + if (data->socket < 0) + { + WARN( "Unable to create socket\n" ); + free( data ); + return STATUS_ACCESS_DENIED; + } +#ifdef __linux__ + if (ops->family == AF_INET) ops = &ipv4_linux_ping; +#endif + } + data->ops = ops; + + *icmp_data = data; + return STATUS_SUCCESS; +} + +static void icmp_data_free( struct icmp_data *data ) +{ + close( data->socket ); + free( data ); +} + +NTSTATUS icmp_send_echo( void *args ) +{ + struct icmp_send_echo_params *params = args; + struct icmp_hdr *icmp_hdr; /* this is the same for both ipv4 and ipv6 */ + struct sockaddr_storage dst_storage; + struct sockaddr *dst = (struct sockaddr *)&dst_storage; + struct icmp_data *data; + int dst_len, ret; + NTSTATUS status; + + status = icmp_data_create( params->dst->si_family, &data ); + if (status) return status; + data->ops->set_socket_opts( data, params ); + + icmp_hdr = malloc( sizeof(*icmp_hdr) + params->request_size ); + if (!icmp_hdr) + { + icmp_data_free( data ); + return STATUS_NO_MEMORY; + } + data->ops->init_icmp_hdr( data, icmp_hdr ); + memcpy( icmp_hdr + 1, params->request, params->request_size ); + icmp_hdr->checksum = data->ops->chksum( (BYTE *)icmp_hdr, sizeof(*icmp_hdr) + params->request_size ); + + dst_len = SOCKADDR_INET_to_sockaddr( params->dst, dst, sizeof(dst_storage) ); + + NtQueryPerformanceCounter( &data->send_time, NULL ); + ret = sendto( data->socket, icmp_hdr, sizeof(*icmp_hdr) + params->request_size, 0, dst, dst_len ); + free( icmp_hdr ); + + if (ret < 0) + { + TRACE( "sendto() rets %d errno %d\n", ret, errno ); + icmp_data_free( data ); + params->ip_status = errno_to_ip_status( errno ); + return STATUS_SUCCESS; + } + + /* FIXME */ + icmp_data_free( data ); + return STATUS_NOT_SUPPORTED; +} diff --git a/dlls/nsiproxy.sys/nsi.c b/dlls/nsiproxy.sys/nsi.c index 565ae2d11dd..25219a1c3fe 100644 --- a/dlls/nsiproxy.sys/nsi.c +++ b/dlls/nsiproxy.sys/nsi.c @@ -147,6 +147,7 @@ static NTSTATUS unix_nsi_get_parameter_ex( void *args )
const unixlib_entry_t __wine_unix_call_funcs[] = { + icmp_send_echo, unix_nsi_enumerate_all_ex, unix_nsi_get_all_parameters_ex, unix_nsi_get_parameter_ex diff --git a/dlls/nsiproxy.sys/nsiproxy_private.h b/dlls/nsiproxy.sys/nsiproxy_private.h new file mode 100644 index 00000000000..9831998793d --- /dev/null +++ b/dlls/nsiproxy.sys/nsiproxy_private.h @@ -0,0 +1,28 @@ +/* + * nsiproxy.sys + * + * Copyright 2021 Huw Davies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +struct icmp_send_echo_params +{ + SOCKADDR_INET *dst; + void *request; + DWORD request_size; + BYTE ttl, tos; + ULONG ip_status; +}; diff --git a/dlls/nsiproxy.sys/unix_private.h b/dlls/nsiproxy.sys/unix_private.h index 97e264e9b1a..0dfe9e69dbc 100644 --- a/dlls/nsiproxy.sys/unix_private.h +++ b/dlls/nsiproxy.sys/unix_private.h @@ -156,3 +156,5 @@ static inline int ascii_strcasecmp( const char *s1, const char *s2 ) { return ascii_strncasecmp( s1, s2, -1 ); } + +NTSTATUS icmp_send_echo( void *args ) DECLSPEC_HIDDEN;