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