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