xref: /reactos/drivers/network/tcpip/tcpip/icmp.c (revision d6eebaa4)
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