1 /*
2 * PROJECT: ReactOS TCP/IP protocol driver
3 * LICENCE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: ICMP functions implementation
5 * COPYRIGHT: 2019 Victor Perevertkin (victor.perevertkin@reactos.org)
6 */
7
8 #include "precomp.h"
9 #include <checksum.h>
10
11 typedef struct _ICMP_PACKET_CONTEXT
12 {
13 TDI_REQUEST TdiRequest;
14 KDPC TimeoutDpc;
15 KEVENT InitializationFinishedEvent;
16 KEVENT DatagramProcessedEvent;
17 LARGE_INTEGER TimerResolution;
18 INT64 StartTicks;
19 PIRP Irp;
20 PUCHAR CurrentReply;
21 UINT32 RemainingSize;
22 LONG nReplies;
23 PIO_WORKITEM FinishWorker;
24 KTIMER TimeoutTimer;
25 } ICMP_PACKET_CONTEXT, *PICMP_PACKET_CONTEXT;
26
27 static volatile INT16 IcmpSequence = 0;
28
29 static
30 UINT32
GetReplyStatus(PICMP_HEADER IcmpHeader)31 GetReplyStatus(PICMP_HEADER IcmpHeader)
32 {
33 switch (IcmpHeader->Type)
34 {
35 case ICMP_TYPE_ECHO_REPLY:
36 return IP_SUCCESS;
37 case ICMP_TYPE_DEST_UNREACH:
38 switch (IcmpHeader->Code)
39 {
40 case ICMP_CODE_DU_NET_UNREACH:
41 return IP_DEST_NET_UNREACHABLE;
42 case ICMP_CODE_DU_HOST_UNREACH:
43 return IP_DEST_HOST_UNREACHABLE;
44 case ICMP_CODE_DU_PROTOCOL_UNREACH:
45 return IP_DEST_PROT_UNREACHABLE;
46 case ICMP_CODE_DU_PORT_UNREACH:
47 return IP_DEST_PORT_UNREACHABLE;
48 case ICMP_CODE_DU_FRAG_DF_SET:
49 return IP_DEST_NET_UNREACHABLE;
50 case ICMP_CODE_DU_SOURCE_ROUTE_FAILED:
51 return IP_BAD_ROUTE;
52 default:
53 return IP_DEST_NET_UNREACHABLE;
54 }
55 case ICMP_TYPE_SOURCE_QUENCH:
56 return IP_SOURCE_QUENCH;
57 case ICMP_TYPE_TIME_EXCEEDED:
58 if (IcmpHeader->Code == ICMP_CODE_TE_REASSEMBLY)
59 return IP_TTL_EXPIRED_REASSEM;
60 else
61 return IP_TTL_EXPIRED_TRANSIT;
62 case ICMP_TYPE_PARAMETER:
63 return IP_PARAM_PROBLEM;
64 default:
65 return IP_REQ_TIMED_OUT;
66 }
67 }
68
69 static
70 VOID
ClearReceiveHandler(_In_ PADDRESS_FILE AddrFile)71 ClearReceiveHandler(
72 _In_ PADDRESS_FILE AddrFile)
73 {
74 LockObject(AddrFile);
75 AddrFile->RegisteredReceiveDatagramHandler = FALSE;
76 UnlockObject(AddrFile);
77 }
78
79 IO_WORKITEM_ROUTINE EndRequestHandler;
80
81 VOID
82 NTAPI
EndRequestHandler(PDEVICE_OBJECT DeviceObject,PVOID _Context)83 EndRequestHandler(
84 PDEVICE_OBJECT DeviceObject,
85 PVOID _Context)
86 {
87 PICMP_PACKET_CONTEXT Context = (PICMP_PACKET_CONTEXT)_Context;
88 PIO_STACK_LOCATION CurrentStack;
89 PIRP Irp;
90 UINT32 nReplies;
91 KIRQL OldIrql;
92
93 ClearReceiveHandler((PADDRESS_FILE)Context->TdiRequest.Handle.AddressHandle);
94
95 KeWaitForSingleObject(&Context->DatagramProcessedEvent, Executive, KernelMode, FALSE, NULL);
96
97 TI_DbgPrint(DEBUG_ICMP, ("Finishing request Context: %p\n", Context));
98
99 Irp = Context->Irp;
100 CurrentStack = IoGetCurrentIrpStackLocation(Irp);
101
102 if (Context->nReplies > 0)
103 {
104 ((PICMP_ECHO_REPLY)Irp->AssociatedIrp.SystemBuffer)->Reserved = Context->nReplies;
105 Irp->IoStatus.Status = STATUS_SUCCESS;
106 Irp->IoStatus.Information = CurrentStack->Parameters.DeviceIoControl.OutputBufferLength;
107 }
108 else
109 {
110 PICMP_ECHO_REPLY ReplyBuffer = (PICMP_ECHO_REPLY)Irp->AssociatedIrp.SystemBuffer;
111 RtlZeroMemory(ReplyBuffer, sizeof(*ReplyBuffer));
112 ReplyBuffer->Status = IP_REQ_TIMED_OUT;
113
114 Irp->IoStatus.Status = STATUS_TIMEOUT;
115 Irp->IoStatus.Information = sizeof(*ReplyBuffer);
116 }
117
118 // for debugging
119 nReplies = ((PICMP_ECHO_REPLY)Irp->AssociatedIrp.SystemBuffer)->Reserved;
120
121 // taken from dispatch.c:IRPFinish
122 IoAcquireCancelSpinLock(&OldIrql);
123 IoSetCancelRoutine(Irp, NULL);
124 IoReleaseCancelSpinLock(OldIrql);
125 IoCompleteRequest(Irp, IO_NETWORK_INCREMENT);
126
127 {
128 NTSTATUS _Status = FileCloseAddress(&Context->TdiRequest);
129 ASSERT(NT_SUCCESS(_Status));
130 }
131
132 IoFreeWorkItem(Context->FinishWorker);
133 ExFreePoolWithTag(Context, OUT_DATA_TAG);
134
135 TI_DbgPrint(DEBUG_ICMP, ("Leaving, nReplies: %u\n", nReplies));
136 }
137
138 NTSTATUS
139 NTAPI
ReceiveDatagram(_In_opt_ PVOID TdiEventContext,_In_ LONG SourceAddressLength,_In_reads_bytes_ (SourceAddressLength)PVOID SourceAddress,_In_ LONG OptionsLength,_In_reads_bytes_opt_ (OptionsLength)PVOID Options,_In_ ULONG ReceiveDatagramFlags,_In_ ULONG BytesIndicated,_In_ ULONG BytesAvailable,_Out_ ULONG * OutBytesTaken,_In_ PVOID Tsdu,_Out_opt_ PIRP * IoRequestPacket)140 ReceiveDatagram(
141 _In_opt_ PVOID TdiEventContext,
142 _In_ LONG SourceAddressLength,
143 _In_reads_bytes_(SourceAddressLength) PVOID SourceAddress,
144 _In_ LONG OptionsLength,
145 _In_reads_bytes_opt_(OptionsLength) PVOID Options,
146 _In_ ULONG ReceiveDatagramFlags,
147 _In_ ULONG BytesIndicated,
148 _In_ ULONG BytesAvailable,
149 _Out_ ULONG *OutBytesTaken,
150 _In_ PVOID Tsdu,
151 _Out_opt_ PIRP *IoRequestPacket)
152 {
153 PICMP_PACKET_CONTEXT Context = TdiEventContext;
154 PIPv4_HEADER IpHeader = Tsdu;
155 UINT16 IpHeaderSize = sizeof(IPv4_HEADER) + OptionsLength;
156 PICMP_HEADER IcmpHeader = (PICMP_HEADER)((PUCHAR)Tsdu + IpHeaderSize);
157
158 PVOID DataBuffer = (PUCHAR)Tsdu + IpHeaderSize + sizeof(ICMP_HEADER);
159 INT32 DataSize = min(BytesAvailable, UINT16_MAX) - IpHeaderSize - sizeof(ICMP_HEADER);
160
161 INT64 CurrentTime;
162 UINT32 RoundTripTime;
163 PICMP_ECHO_REPLY CurrentReply;
164 PUCHAR CurrentUserBuffer;
165
166 // do not handle echo requests
167 if (DataSize >= 0 && IcmpHeader->Type == ICMP_TYPE_ECHO_REQUEST)
168 {
169 return STATUS_SUCCESS;
170 }
171
172 KeWaitForSingleObject(&Context->InitializationFinishedEvent, Executive, KernelMode, FALSE, NULL);
173 KeClearEvent(&Context->DatagramProcessedEvent);
174
175 ASSERT(SourceAddressLength == sizeof(IPAddr));
176 TI_DbgPrint(DEBUG_ICMP, ("Received datagram Context: 0x%p\n", TdiEventContext));
177
178 CurrentTime = KeQueryPerformanceCounter(NULL).QuadPart;
179 RoundTripTime = (CurrentTime - Context->StartTicks) * 1000 / Context->TimerResolution.QuadPart;
180 CurrentReply = (PICMP_ECHO_REPLY)Context->CurrentReply;
181
182 if (Context->RemainingSize >= sizeof(ICMP_ECHO_REPLY) && DataSize >= 0)
183 {
184 TI_DbgPrint(DEBUG_ICMP, ("RemainingSize: %u, RoundTripTime: %u\n", Context->RemainingSize, RoundTripTime));
185
186 memcpy(&CurrentReply->Address, SourceAddress, sizeof(CurrentReply->Address));
187 CurrentReply->Status = GetReplyStatus(IcmpHeader);
188 CurrentReply->RoundTripTime = RoundTripTime;
189 CurrentReply->Reserved = 0;
190 CurrentReply->Data = NULL;
191 CurrentReply->DataSize = 0;
192 CurrentReply->Options.Ttl = IpHeader->Ttl;
193 CurrentReply->Options.Tos = IpHeader->Tos;
194 CurrentReply->Options.Flags = IpHeader->FlagsFragOfs >> 13;
195 CurrentReply->Options.OptionsData = NULL;
196 CurrentReply->Options.OptionsSize = 0;
197
198 Context->RemainingSize -= sizeof(ICMP_ECHO_REPLY);
199 Context->CurrentReply += sizeof(ICMP_ECHO_REPLY);
200 }
201
202 CurrentUserBuffer = (PUCHAR)Context->Irp->UserBuffer + (Context->CurrentReply - (PUCHAR)Context->Irp->AssociatedIrp.SystemBuffer);
203
204 if (DataSize > 0 && Context->RemainingSize > 0)
205 {
206 UINT32 _DataSize = min(Context->RemainingSize, DataSize);
207
208 memcpy(Context->CurrentReply + Context->RemainingSize - _DataSize, DataBuffer, _DataSize);
209 CurrentReply->Data = CurrentUserBuffer + Context->RemainingSize - _DataSize;
210 CurrentReply->DataSize = _DataSize;
211
212 Context->RemainingSize -= _DataSize;
213 // Context->ReplyBuffer += _DataSize;
214 }
215 else
216 {
217 TI_DbgPrint(DEBUG_ICMP, ("RemainingSize: %u, DataSize: %d\n", Context->RemainingSize, DataSize));
218 }
219
220 if (OptionsLength > 0 && Context->RemainingSize > 0)
221 {
222 UINT32 _OptSize = min(Context->RemainingSize, OptionsLength);
223
224 memcpy(Context->CurrentReply + Context->RemainingSize + _OptSize, Options, _OptSize);
225 CurrentReply->Options.OptionsData = CurrentUserBuffer + Context->RemainingSize + _OptSize;
226 CurrentReply->Options.OptionsSize = _OptSize;
227
228 Context->RemainingSize -= _OptSize;
229 // Context->ReplyBuffer += _OptSize;
230 }
231 else
232 {
233 TI_DbgPrint(DEBUG_ICMP, ("RemainingSize: %u, OptSize: %d\n", Context->RemainingSize, OptionsLength));
234 }
235
236 Context->nReplies++;
237
238 if (Context->RemainingSize < sizeof(ICMP_ECHO_REPLY))
239 {
240 TI_DbgPrint(DEBUG_ICMP, ("The space is over: %u\n", Context->RemainingSize));
241
242 // if the timer was inserted, that means DPC has not been queued yet
243 if (KeCancelTimer(&Context->TimeoutTimer))
244 {
245 PADDRESS_FILE AddrFile = (PADDRESS_FILE)Context->TdiRequest.Handle.AddressHandle;
246 ClearReceiveHandler(AddrFile);
247
248 IoQueueWorkItem(Context->FinishWorker, &EndRequestHandler, DelayedWorkQueue, Context);
249 }
250 }
251
252 KeSetEvent(&Context->DatagramProcessedEvent, IO_NO_INCREMENT, FALSE);
253 return STATUS_SUCCESS;
254 }
255
256 KDEFERRED_ROUTINE TimeoutHandler;
257
258 VOID
259 NTAPI
TimeoutHandler(_In_ PKDPC Dpc,_In_opt_ PVOID _Context,_In_opt_ PVOID SystemArgument1,_In_opt_ PVOID SystemArgument2)260 TimeoutHandler(
261 _In_ PKDPC Dpc,
262 _In_opt_ PVOID _Context,
263 _In_opt_ PVOID SystemArgument1,
264 _In_opt_ PVOID SystemArgument2)
265 {
266 PICMP_PACKET_CONTEXT Context = (PICMP_PACKET_CONTEXT)_Context;
267
268 IoQueueWorkItem(Context->FinishWorker, &EndRequestHandler, DelayedWorkQueue, _Context);
269 }
270
271 NTSTATUS
DispEchoRequest(_In_ PDEVICE_OBJECT DeviceObject,_In_ PIRP Irp,_In_ PIO_STACK_LOCATION IrpSp)272 DispEchoRequest(
273 _In_ PDEVICE_OBJECT DeviceObject,
274 _In_ PIRP Irp,
275 _In_ PIO_STACK_LOCATION IrpSp)
276 {
277 PICMP_ECHO_REQUEST Request = Irp->AssociatedIrp.SystemBuffer;
278 UINT32 OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
279 UINT32 InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
280 NTSTATUS Status;
281 TDI_CONNECTION_INFORMATION ConnectionInfo;
282 TA_IP_ADDRESS RemoteAddressTa, LocalAddressTa;
283 PADDRESS_FILE AddrFile;
284 ULONG DataUsed;
285 PUCHAR Buffer;
286 UINT16 RequestSize;
287 PICMP_PACKET_CONTEXT SendContext;
288 LARGE_INTEGER RequestTimeout;
289 UINT8 SavedTtl;
290
291 TI_DbgPrint(DEBUG_ICMP, ("About to send datagram, OutputBufferLength: %u, SystemBuffer: %p\n", OutputBufferLength, Irp->AssociatedIrp.SystemBuffer));
292
293 // check buffers
294 if (OutputBufferLength < sizeof(ICMP_ECHO_REPLY) || InputBufferLength < sizeof(ICMP_ECHO_REQUEST))
295 {
296 return STATUS_INVALID_PARAMETER;
297 }
298
299 // check request parameters
300 if ((Request->DataSize > UINT16_MAX - sizeof(ICMP_HEADER) - sizeof(IPv4_HEADER)) ||
301 ((UINT32)Request->DataOffset + Request->DataSize > InputBufferLength) ||
302 ((UINT32)Request->OptionsOffset + Request->OptionsSize > InputBufferLength))
303 {
304 return STATUS_INVALID_PARAMETER;
305 }
306
307 SendContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(*SendContext), OUT_DATA_TAG);
308 if (!SendContext)
309 {
310 return STATUS_INSUFFICIENT_RESOURCES;
311 }
312
313 RtlZeroMemory(&SendContext->TdiRequest, sizeof(SendContext->TdiRequest));
314 SendContext->TdiRequest.RequestContext = Irp;
315
316 // setting up everything needed for sending the packet
317
318 RtlZeroMemory(&RemoteAddressTa, sizeof(RemoteAddressTa));
319 RtlZeroMemory(&LocalAddressTa, sizeof(LocalAddressTa));
320 RtlZeroMemory(&ConnectionInfo, sizeof(ConnectionInfo));
321
322 RemoteAddressTa.TAAddressCount = 1;
323 RemoteAddressTa.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
324 RemoteAddressTa.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
325 RemoteAddressTa.Address[0].Address[0].in_addr = Request->Address;
326
327 LocalAddressTa.TAAddressCount = 1;
328 LocalAddressTa.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
329 LocalAddressTa.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
330 LocalAddressTa.Address[0].Address[0].in_addr = 0;
331
332 Status = FileOpenAddress(&SendContext->TdiRequest, &LocalAddressTa, IPPROTO_ICMP, FALSE, NULL);
333
334 if (!NT_SUCCESS(Status))
335 {
336 TI_DbgPrint(DEBUG_ICMP, ("Failed to open address file status: 0x%x\n", Status));
337
338 ExFreePoolWithTag(SendContext, OUT_DATA_TAG);
339
340 return Status;
341 }
342
343 AddrFile = (PADDRESS_FILE)SendContext->TdiRequest.Handle.AddressHandle;
344
345 // setting up the context
346
347 KeQueryPerformanceCounter(&SendContext->TimerResolution);
348 SendContext->Irp = Irp;
349 SendContext->CurrentReply = Irp->AssociatedIrp.SystemBuffer;
350 SendContext->RemainingSize = OutputBufferLength;
351 SendContext->nReplies = 0;
352 SendContext->FinishWorker = IoAllocateWorkItem(DeviceObject);
353 KeInitializeEvent(&SendContext->InitializationFinishedEvent, NotificationEvent, FALSE);
354 KeInitializeEvent(&SendContext->DatagramProcessedEvent, NotificationEvent, TRUE);
355
356 KeInitializeDpc(&SendContext->TimeoutDpc, &TimeoutHandler, SendContext);
357 KeInitializeTimerEx(&SendContext->TimeoutTimer, SynchronizationTimer);
358
359 RequestTimeout.QuadPart = (-1LL) * 10 * 1000 * Request->Timeout;
360
361 ConnectionInfo.RemoteAddress = &RemoteAddressTa;
362 ConnectionInfo.RemoteAddressLength = sizeof(RemoteAddressTa);
363
364 RequestSize = sizeof(ICMP_HEADER) + Request->DataSize;
365
366 // making up the request packet
367 Buffer = ExAllocatePoolWithTag(NonPagedPool, RequestSize, OUT_DATA_TAG);
368
369 if (!Buffer)
370 {
371 ExFreePoolWithTag(SendContext, OUT_DATA_TAG);
372
373 return STATUS_INSUFFICIENT_RESOURCES;
374 }
375
376 ((PICMP_HEADER)Buffer)->Type = ICMP_TYPE_ECHO_REQUEST;
377 ((PICMP_HEADER)Buffer)->Code = ICMP_TYPE_ECHO_REPLY;
378 ((PICMP_HEADER)Buffer)->Checksum = 0;
379 ((PICMP_HEADER)Buffer)->Identifier = (UINT_PTR)PsGetCurrentProcessId() & UINT16_MAX;
380 ((PICMP_HEADER)Buffer)->Seq = InterlockedIncrement16(&IcmpSequence);
381 memcpy(Buffer + sizeof(ICMP_HEADER), (PUCHAR)Request + Request->DataOffset, Request->DataSize);
382 ((PICMP_HEADER)Buffer)->Checksum = IPv4Checksum(Buffer, RequestSize, 0);
383 SavedTtl = Request->Ttl;
384
385 RtlZeroMemory(Irp->AssociatedIrp.SystemBuffer, OutputBufferLength);
386
387 LockObject(AddrFile);
388
389 AddrFile->TTL = SavedTtl;
390 AddrFile->ReceiveDatagramHandlerContext = SendContext;
391 AddrFile->ReceiveDatagramHandler = ReceiveDatagram;
392 AddrFile->RegisteredReceiveDatagramHandler = TRUE;
393
394 UnlockObject(AddrFile);
395
396 Status = AddrFile->Send(AddrFile, &ConnectionInfo, (PCHAR)Buffer, RequestSize, &DataUsed);
397
398 // From this point we may receive a reply packet.
399 // But we are not ready for it thus InitializationFinishedEvent is needed (see below)
400
401 SendContext->StartTicks = KeQueryPerformanceCounter(NULL).QuadPart;
402
403 ExFreePoolWithTag(Buffer, OUT_DATA_TAG);
404
405 if (!NT_SUCCESS(Status))
406 {
407 NTSTATUS _Status;
408
409 ClearReceiveHandler(AddrFile);
410 _Status = FileCloseAddress(&SendContext->TdiRequest);
411 ASSERT(NT_SUCCESS(_Status));
412
413 IoFreeWorkItem(SendContext->FinishWorker);
414 ExFreePoolWithTag(SendContext, OUT_DATA_TAG);
415
416 TI_DbgPrint(DEBUG_ICMP, ("Failed to send a datagram: 0x%x\n", Status));
417 return Status;
418 }
419
420 IoMarkIrpPending(Irp);
421 KeSetTimer(&SendContext->TimeoutTimer, RequestTimeout, &SendContext->TimeoutDpc);
422 KeSetEvent(&SendContext->InitializationFinishedEvent, IO_NO_INCREMENT, FALSE);
423
424 return STATUS_PENDING;
425 }
426