nsiproxy.sys: Implement change notifications for NSI_IP_UNICAST_TABLE.
[wine.git] / dlls / nsiproxy.sys / device.c
blobc64f61ed0fe547200dcf8723e56ab15baf83f2e2
1 /*
2 * nsiproxy.sys
4 * Copyright 2021 Huw Davies
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include <stdarg.h>
23 #include "ntstatus.h"
24 #define WIN32_NO_STATUS
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winternl.h"
28 #include "winioctl.h"
29 #include "ddk/wdm.h"
30 #include "ifdef.h"
31 #include "netiodef.h"
32 #include "wine/nsi.h"
33 #include "wine/debug.h"
34 #include "wine/unixlib.h"
36 #include "nsiproxy_private.h"
38 WINE_DEFAULT_DEBUG_CHANNEL(nsi);
40 static HANDLE request_event;
42 #define DECLARE_CRITICAL_SECTION(cs) \
43 static CRITICAL_SECTION cs; \
44 static CRITICAL_SECTION_DEBUG cs##_debug = \
45 { 0, 0, &cs, { &cs##_debug.ProcessLocksList, &cs##_debug.ProcessLocksList }, \
46 0, 0, { (DWORD_PTR)(__FILE__ ": " # cs) }}; \
47 static CRITICAL_SECTION cs = { &cs##_debug, -1, 0, 0, 0, 0 };
48 DECLARE_CRITICAL_SECTION( nsiproxy_cs );
50 #define LIST_ENTRY_INIT( list ) { .Flink = &(list), .Blink = &(list) }
51 static LIST_ENTRY request_queue = LIST_ENTRY_INIT( request_queue );
52 static LIST_ENTRY notification_queue = LIST_ENTRY_INIT( notification_queue );
54 struct notification_data
56 NPI_MODULEID module;
57 UINT table;
60 static NTSTATUS nsiproxy_call( unsigned int code, void *args )
62 return WINE_UNIX_CALL( code, args );
65 enum unix_calls
67 icmp_cancel_listen,
68 icmp_close,
69 icmp_listen,
70 icmp_send_echo,
71 nsi_enumerate_all_ex,
72 nsi_get_all_parameters_ex,
73 nsi_get_parameter_ex,
74 nsi_get_notification,
77 static NTSTATUS nsiproxy_enumerate_all( IRP *irp )
79 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
80 struct nsiproxy_enumerate_all *in = (struct nsiproxy_enumerate_all *)irp->AssociatedIrp.SystemBuffer;
81 DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength;
82 void *out = irp->AssociatedIrp.SystemBuffer;
83 DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
84 struct nsi_enumerate_all_ex enum_all;
85 NTSTATUS status;
87 if (in_len != sizeof(*in)) return STATUS_INVALID_PARAMETER;
89 if (out_len < sizeof(UINT) + (in->key_size + in->rw_size + in->dynamic_size + in->static_size) * in->count)
90 return STATUS_INVALID_PARAMETER;
92 enum_all.unknown[0] = 0;
93 enum_all.unknown[1] = 0;
94 enum_all.first_arg = in->first_arg;
95 enum_all.second_arg = in->second_arg;
96 enum_all.module = &in->module;
97 enum_all.table = in->table;
98 enum_all.key_data = (BYTE *)out + sizeof(UINT);
99 enum_all.key_size = in->key_size;
100 enum_all.rw_data = (BYTE *)enum_all.key_data + in->key_size * in->count;
101 enum_all.rw_size = in->rw_size;
102 enum_all.dynamic_data = (BYTE *)enum_all.rw_data + in->rw_size * in->count;
103 enum_all.dynamic_size = in->dynamic_size;
104 enum_all.static_data = (BYTE *)enum_all.dynamic_data + in->dynamic_size * in->count;
105 enum_all.static_size = in->static_size;
106 enum_all.count = in->count;
108 status = nsiproxy_call( nsi_enumerate_all_ex, &enum_all );
109 if (status == STATUS_SUCCESS || status == STATUS_BUFFER_OVERFLOW)
111 irp->IoStatus.Information = out_len;
112 *(UINT *)out = enum_all.count;
114 else irp->IoStatus.Information = 0;
116 return status;
119 static NTSTATUS nsiproxy_get_all_parameters( IRP *irp )
121 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
122 struct nsiproxy_get_all_parameters *in = (struct nsiproxy_get_all_parameters *)irp->AssociatedIrp.SystemBuffer;
123 DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength;
124 BYTE *out = irp->AssociatedIrp.SystemBuffer;
125 DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
126 struct nsi_get_all_parameters_ex get_all;
127 NTSTATUS status;
129 if (in_len < offsetof(struct nsiproxy_get_all_parameters, key[0]) ||
130 in_len < offsetof(struct nsiproxy_get_all_parameters, key[in->key_size]))
131 return STATUS_INVALID_PARAMETER;
133 if (out_len < in->rw_size + in->dynamic_size + in->static_size)
134 return STATUS_INVALID_PARAMETER;
136 get_all.unknown[0] = 0;
137 get_all.unknown[1] = 0;
138 get_all.first_arg = in->first_arg;
139 get_all.unknown2 = 0;
140 get_all.module = &in->module;
141 get_all.table = in->table;
142 get_all.key = in->key;
143 get_all.key_size = in->key_size;
144 get_all.rw_data = out;
145 get_all.rw_size = in->rw_size;
146 get_all.dynamic_data = out + in->rw_size;
147 get_all.dynamic_size = in->dynamic_size;
148 get_all.static_data = out + in->rw_size + in->dynamic_size;
149 get_all.static_size = in->static_size;
151 status = nsiproxy_call( nsi_get_all_parameters_ex, &get_all );
152 irp->IoStatus.Information = (status == STATUS_SUCCESS) ? out_len : 0;
154 return status;
157 static NTSTATUS nsiproxy_get_parameter( IRP *irp )
159 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
160 struct nsiproxy_get_parameter *in = (struct nsiproxy_get_parameter *)irp->AssociatedIrp.SystemBuffer;
161 DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength;
162 void *out = irp->AssociatedIrp.SystemBuffer;
163 DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
164 struct nsi_get_parameter_ex get_param;
165 NTSTATUS status;
167 if (in_len < offsetof(struct nsiproxy_get_parameter, key[0]) ||
168 in_len < offsetof(struct nsiproxy_get_parameter, key[in->key_size]))
169 return STATUS_INVALID_PARAMETER;
171 get_param.unknown[0] = 0;
172 get_param.unknown[1] = 0;
173 get_param.first_arg = in->first_arg;
174 get_param.unknown2 = 0;
175 get_param.module = &in->module;
176 get_param.table = in->table;
177 get_param.key = in->key;
178 get_param.key_size = in->key_size;
179 get_param.param_type = in->param_type;
180 get_param.data = out;
181 get_param.data_size = out_len;
182 get_param.data_offset = in->data_offset;
184 status = nsiproxy_call( nsi_get_parameter_ex, &get_param );
185 irp->IoStatus.Information = (status == STATUS_SUCCESS) ? out_len : 0;
187 return status;
190 static inline icmp_handle irp_get_icmp_handle( IRP *irp )
192 return PtrToUlong( irp->Tail.Overlay.DriverContext[0] );
195 static inline icmp_handle irp_set_icmp_handle( IRP *irp, icmp_handle handle )
197 return PtrToUlong( InterlockedExchangePointer( irp->Tail.Overlay.DriverContext,
198 ULongToPtr( handle ) ) );
201 static void WINAPI icmp_echo_cancel( DEVICE_OBJECT *device, IRP *irp )
203 struct icmp_cancel_listen_params params;
205 TRACE( "device %p, irp %p.\n", device, irp );
207 IoReleaseCancelSpinLock( irp->CancelIrql );
209 EnterCriticalSection( &nsiproxy_cs );
211 /* If the handle is not set, either the irp is still
212 in the request queue, in which case the request thread will
213 cancel it, or the irp has already finished. If the handle
214 does exist then notify the listen thread. In all cases the irp
215 will be completed elsewhere. */
216 params.handle = irp_get_icmp_handle( irp );
217 if (params.handle) nsiproxy_call( icmp_cancel_listen, &params );
219 LeaveCriticalSection( &nsiproxy_cs );
222 static int icmp_echo_reply_struct_len( ULONG family, ULONG bits )
224 if (family == AF_INET)
225 return (bits == 32) ? sizeof(struct icmp_echo_reply_32) : sizeof(struct icmp_echo_reply_64);
226 return 0;
229 static NTSTATUS nsiproxy_icmp_echo( IRP *irp )
231 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
232 struct nsiproxy_icmp_echo *in = (struct nsiproxy_icmp_echo *)irp->AssociatedIrp.SystemBuffer;
233 DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength;
234 DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
236 TRACE( "\n" );
238 if (in_len < offsetof(struct nsiproxy_icmp_echo, data[0]) ||
239 in_len < offsetof(struct nsiproxy_icmp_echo, data[((in->opt_size + 3) & ~3) + in->req_size]) ||
240 out_len < icmp_echo_reply_struct_len( in->dst.si_family, in->bits ))
241 return STATUS_INVALID_PARAMETER;
243 switch (in->dst.si_family)
245 case AF_INET:
246 if (in->dst.Ipv4.sin_addr.s_addr == INADDR_ANY) return STATUS_INVALID_ADDRESS_WILDCARD;
247 break;
248 default:
249 return STATUS_INVALID_PARAMETER;
252 EnterCriticalSection( &nsiproxy_cs );
254 IoSetCancelRoutine( irp, icmp_echo_cancel );
255 if (irp->Cancel && !IoSetCancelRoutine( irp, NULL ))
257 /* IRP was canceled before we set cancel routine */
258 InitializeListHead( &irp->Tail.Overlay.ListEntry );
259 LeaveCriticalSection( &nsiproxy_cs );
260 return STATUS_CANCELLED;
263 InsertTailList( &request_queue, &irp->Tail.Overlay.ListEntry );
264 IoMarkIrpPending( irp );
266 LeaveCriticalSection( &nsiproxy_cs );
267 SetEvent( request_event );
269 return STATUS_PENDING;
272 static void WINAPI change_notification_cancel( DEVICE_OBJECT *device, IRP *irp )
274 TRACE( "device %p, irp %p.\n", device, irp );
276 IoReleaseCancelSpinLock( irp->CancelIrql );
278 EnterCriticalSection( &nsiproxy_cs );
279 RemoveEntryList( &irp->Tail.Overlay.ListEntry );
280 free( irp->Tail.Overlay.DriverContext[0] );
281 LeaveCriticalSection( &nsiproxy_cs );
283 irp->IoStatus.Status = STATUS_CANCELLED;
284 IoCompleteRequest( irp, IO_NO_INCREMENT );
287 static NTSTATUS nsiproxy_change_notification( IRP *irp )
289 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
290 struct nsiproxy_request_notification *in = (struct nsiproxy_request_notification *)irp->AssociatedIrp.SystemBuffer;
291 DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength;
292 struct notification_data *data;
294 TRACE( "irp %p.\n", irp );
296 if (in_len < sizeof(*in)) return STATUS_INVALID_PARAMETER;
297 if (!(data = calloc( 1, sizeof(*data) ))) return STATUS_NO_MEMORY;
298 /* FIXME: validate module and table. */
300 EnterCriticalSection( &nsiproxy_cs );
301 IoSetCancelRoutine( irp, change_notification_cancel );
302 if (irp->Cancel && !IoSetCancelRoutine( irp, NULL ))
304 /* IRP was canceled before we set cancel routine */
305 InitializeListHead( &irp->Tail.Overlay.ListEntry );
306 LeaveCriticalSection( &nsiproxy_cs );
307 free( data );
308 return STATUS_CANCELLED;
310 InsertTailList( &notification_queue, &irp->Tail.Overlay.ListEntry );
311 IoMarkIrpPending( irp );
312 data->module = in->module;
313 data->table = in->table;
314 irp->Tail.Overlay.DriverContext[0] = data;
315 LeaveCriticalSection( &nsiproxy_cs );
317 return STATUS_PENDING;
320 static NTSTATUS WINAPI nsi_ioctl( DEVICE_OBJECT *device, IRP *irp )
322 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
323 NTSTATUS status;
325 TRACE( "ioctl %lx insize %lu outsize %lu\n",
326 irpsp->Parameters.DeviceIoControl.IoControlCode,
327 irpsp->Parameters.DeviceIoControl.InputBufferLength,
328 irpsp->Parameters.DeviceIoControl.OutputBufferLength );
330 switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
332 case IOCTL_NSIPROXY_WINE_ENUMERATE_ALL:
333 status = nsiproxy_enumerate_all( irp );
334 break;
336 case IOCTL_NSIPROXY_WINE_GET_ALL_PARAMETERS:
337 status = nsiproxy_get_all_parameters( irp );
338 break;
340 case IOCTL_NSIPROXY_WINE_GET_PARAMETER:
341 status = nsiproxy_get_parameter( irp );
342 break;
344 case IOCTL_NSIPROXY_WINE_ICMP_ECHO:
345 status = nsiproxy_icmp_echo( irp );
346 break;
348 case IOCTL_NSIPROXY_WINE_CHANGE_NOTIFICATION:
349 status = nsiproxy_change_notification( irp );
350 break;
352 default:
353 FIXME( "ioctl %lx not supported\n", irpsp->Parameters.DeviceIoControl.IoControlCode );
354 status = STATUS_NOT_SUPPORTED;
355 break;
358 if (status != STATUS_PENDING)
360 irp->IoStatus.Status = status;
361 IoCompleteRequest( irp, IO_NO_INCREMENT );
363 return status;
366 static int add_device( DRIVER_OBJECT *driver )
368 UNICODE_STRING name = RTL_CONSTANT_STRING( L"\\Device\\Nsi" );
369 UNICODE_STRING link = RTL_CONSTANT_STRING( L"\\??\\Nsi" );
370 DEVICE_OBJECT *device;
371 NTSTATUS status;
373 if (!(status = IoCreateDevice( driver, 0, &name, FILE_DEVICE_NETWORK, FILE_DEVICE_SECURE_OPEN, FALSE, &device )))
374 status = IoCreateSymbolicLink( &link, &name );
375 if (status)
377 FIXME( "failed to create device error %lx\n", status );
378 return 0;
381 return 1;
384 static DWORD WINAPI listen_thread_proc( void *arg )
386 IRP *irp = arg;
387 IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp );
388 struct nsiproxy_icmp_echo *in = irp->AssociatedIrp.SystemBuffer;
389 struct icmp_close_params close_params;
390 struct icmp_listen_params params;
391 NTSTATUS status;
393 TRACE( "\n" );
395 params.user_reply_ptr = in->user_reply_ptr;
396 params.handle = irp_get_icmp_handle( irp );
397 params.timeout = in->timeout;
398 params.bits = in->bits;
399 params.reply = irp->AssociatedIrp.SystemBuffer;
400 params.reply_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
402 status = nsiproxy_call( icmp_listen, &params );
403 TRACE( "icmp_listen rets %08lx\n", status );
405 EnterCriticalSection( &nsiproxy_cs );
407 close_params.handle = irp_set_icmp_handle( irp, 0 );
408 nsiproxy_call( icmp_close, &close_params );
410 irp->IoStatus.Status = status;
411 if (status == STATUS_SUCCESS)
412 irp->IoStatus.Information = params.reply_len;
413 else
414 irp->IoStatus.Information = 0;
415 IoCompleteRequest( irp, IO_NO_INCREMENT );
417 LeaveCriticalSection( &nsiproxy_cs );
419 return 0;
422 static void handle_queued_send_echo( IRP *irp )
424 struct nsiproxy_icmp_echo *in = (struct nsiproxy_icmp_echo *)irp->AssociatedIrp.SystemBuffer;
425 struct icmp_send_echo_params params;
426 icmp_handle handle;
427 NTSTATUS status;
429 TRACE( "\n" );
430 params.request = in->data + ((in->opt_size + 3) & ~3);
431 params.request_size = in->req_size;
432 params.reply = irp->AssociatedIrp.SystemBuffer;
433 params.bits = in->bits;
434 params.ttl = in->ttl;
435 params.tos = in->tos;
436 params.dst = &in->dst;
437 params.handle = &handle;
439 status = nsiproxy_call( icmp_send_echo, &params );
440 TRACE( "icmp_send_echo rets %08lx\n", status );
442 if (status != STATUS_PENDING)
444 irp->IoStatus.Status = status;
445 if (status == STATUS_SUCCESS)
446 irp->IoStatus.Information = params.reply_len;
447 IoCompleteRequest( irp, IO_NO_INCREMENT );
449 else
451 irp_set_icmp_handle( irp, handle );
452 RtlQueueWorkItem( listen_thread_proc, irp, WT_EXECUTELONGFUNCTION );
456 static DWORD WINAPI request_thread_proc( void *arg )
458 LIST_ENTRY *entry;
460 while (WaitForSingleObject( request_event, INFINITE ) == WAIT_OBJECT_0)
462 TRACE( "request_event triggered\n" );
463 EnterCriticalSection( &nsiproxy_cs );
464 while ((entry = RemoveHeadList( &request_queue )) != &request_queue )
466 IRP *irp = CONTAINING_RECORD( entry, IRP, Tail.Overlay.ListEntry );
468 if (irp->Cancel)
470 irp->IoStatus.Status = STATUS_CANCELLED;
471 TRACE( "already cancelled\n" );
472 IoCompleteRequest( irp, IO_NO_INCREMENT );
473 continue;
476 handle_queued_send_echo( irp );
478 LeaveCriticalSection( &nsiproxy_cs );
480 return 0;
483 static DWORD WINAPI notification_thread_proc( void *arg )
485 struct nsi_get_notification_params params;
486 LIST_ENTRY *entry, *next;
487 NTSTATUS status;
489 while (!(status = nsiproxy_call( nsi_get_notification, &params )))
491 EnterCriticalSection( &nsiproxy_cs );
492 for (entry = notification_queue.Flink; entry != &notification_queue; entry = next)
494 IRP *irp = CONTAINING_RECORD( entry, IRP, Tail.Overlay.ListEntry );
495 struct notification_data *data = irp->Tail.Overlay.DriverContext[0];
497 next = entry->Flink;
498 if(irp->Cancel)
500 /* Cancel routine should care of freeing data and completing IRP. */
501 TRACE( "irp %p canceled.\n", irp );
502 continue;
504 if (!NmrIsEqualNpiModuleId( &data->module, &params.module ) || data->table != params.table)
505 continue;
507 irp->IoStatus.Status = 0;
508 RemoveEntryList( entry );
509 irp->Tail.Overlay.DriverContext[0] = NULL;
510 free( data );
511 TRACE("completing irp %p.\n", irp);
512 IoCompleteRequest( irp, IO_NO_INCREMENT );
514 LeaveCriticalSection( &nsiproxy_cs );
517 WARN( "nsi_get_notification failed, status %#lx.\n", status );
518 return 0;
521 NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path )
523 NTSTATUS status;
524 HANDLE thread;
526 TRACE( "(%p, %s)\n", driver, debugstr_w( path->Buffer ) );
528 status = __wine_init_unix_call();
529 if (status) return status;
531 driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = nsi_ioctl;
533 add_device( driver );
535 request_event = CreateEventW( NULL, FALSE, FALSE, NULL );
536 thread = CreateThread( NULL, 0, request_thread_proc, NULL, 0, NULL );
537 CloseHandle( thread );
538 thread = CreateThread( NULL, 0, notification_thread_proc, NULL, 0, NULL );
539 CloseHandle( thread );
541 return STATUS_SUCCESS;