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 LockObject(AddrFile); 78 AddrFile->RegisteredReceiveDatagramHandler = FALSE; 79 UnlockObject(AddrFile); 80 } 81 82 IO_WORKITEM_ROUTINE EndRequestHandler; 83 84 VOID 85 NTAPI 86 EndRequestHandler( 87 PDEVICE_OBJECT DeviceObject, 88 PVOID _Context) 89 { 90 PICMP_PACKET_CONTEXT Context = (PICMP_PACKET_CONTEXT)_Context; 91 PIO_STACK_LOCATION CurrentStack; 92 PIRP Irp; 93 UINT32 nReplies; 94 KIRQL OldIrql; 95 96 ClearReceiveHandler((PADDRESS_FILE)Context->TdiRequest.Handle.AddressHandle); 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 271 IoQueueWorkItem(Context->FinishWorker, &EndRequestHandler, DelayedWorkQueue, _Context); 272 } 273 274 NTSTATUS 275 DispEchoRequest( 276 _In_ PDEVICE_OBJECT DeviceObject, 277 _In_ PIRP Irp, 278 _In_ PIO_STACK_LOCATION IrpSp) 279 { 280 PICMP_ECHO_REQUEST Request = Irp->AssociatedIrp.SystemBuffer; 281 UINT32 OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength; 282 UINT32 InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength; 283 NTSTATUS Status; 284 TDI_CONNECTION_INFORMATION ConnectionInfo; 285 TA_IP_ADDRESS RemoteAddressTa, LocalAddressTa; 286 PADDRESS_FILE AddrFile; 287 ULONG DataUsed; 288 PUCHAR Buffer; 289 UINT16 RequestSize; 290 PICMP_PACKET_CONTEXT SendContext; 291 LARGE_INTEGER RequestTimeout; 292 UINT8 SavedTtl; 293 294 TI_DbgPrint(DEBUG_ICMP, ("About to send datagram, OutputBufferLength: %u, SystemBuffer: %p\n", OutputBufferLength, Irp->AssociatedIrp.SystemBuffer)); 295 296 // check buffers 297 if (OutputBufferLength < sizeof(ICMP_ECHO_REPLY) || InputBufferLength < sizeof(ICMP_ECHO_REQUEST)) 298 { 299 return STATUS_INVALID_PARAMETER; 300 } 301 302 // check request parameters 303 if ((Request->DataSize > UINT16_MAX - sizeof(ICMP_HEADER) - sizeof(IPv4_HEADER)) || 304 ((UINT32)Request->DataOffset + Request->DataSize > InputBufferLength) || 305 ((UINT32)Request->OptionsOffset + Request->OptionsSize > InputBufferLength)) 306 { 307 return STATUS_INVALID_PARAMETER; 308 } 309 310 SendContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(*SendContext), OUT_DATA_TAG); 311 if (!SendContext) 312 { 313 return STATUS_INSUFFICIENT_RESOURCES; 314 } 315 316 RtlZeroMemory(&SendContext->TdiRequest, sizeof(SendContext->TdiRequest)); 317 SendContext->TdiRequest.RequestContext = Irp; 318 319 // setting up everything needed for sending the packet 320 321 RtlZeroMemory(&RemoteAddressTa, sizeof(RemoteAddressTa)); 322 RtlZeroMemory(&LocalAddressTa, sizeof(LocalAddressTa)); 323 RtlZeroMemory(&ConnectionInfo, sizeof(ConnectionInfo)); 324 325 RemoteAddressTa.TAAddressCount = 1; 326 RemoteAddressTa.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP; 327 RemoteAddressTa.Address[0].AddressType = TDI_ADDRESS_TYPE_IP; 328 RemoteAddressTa.Address[0].Address[0].in_addr = Request->Address; 329 330 LocalAddressTa.TAAddressCount = 1; 331 LocalAddressTa.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP; 332 LocalAddressTa.Address[0].AddressType = TDI_ADDRESS_TYPE_IP; 333 LocalAddressTa.Address[0].Address[0].in_addr = 0; 334 335 Status = FileOpenAddress(&SendContext->TdiRequest, &LocalAddressTa, IPPROTO_ICMP, FALSE, NULL); 336 337 if (!NT_SUCCESS(Status)) 338 { 339 TI_DbgPrint(DEBUG_ICMP, ("Failed to open address file status: 0x%x\n", Status)); 340 341 ExFreePoolWithTag(SendContext, OUT_DATA_TAG); 342 343 return Status; 344 } 345 346 AddrFile = (PADDRESS_FILE)SendContext->TdiRequest.Handle.AddressHandle; 347 348 // setting up the context 349 350 KeQueryPerformanceCounter(&SendContext->TimerResolution); 351 SendContext->Irp = Irp; 352 SendContext->CurrentReply = Irp->AssociatedIrp.SystemBuffer; 353 SendContext->RemainingSize = OutputBufferLength; 354 SendContext->nReplies = 0; 355 SendContext->FinishWorker = IoAllocateWorkItem(DeviceObject); 356 KeInitializeEvent(&SendContext->InitializationFinishedEvent, NotificationEvent, FALSE); 357 KeInitializeEvent(&SendContext->DatagramProcessedEvent, NotificationEvent, TRUE); 358 359 KeInitializeDpc(&SendContext->TimeoutDpc, &TimeoutHandler, SendContext); 360 KeInitializeTimerEx(&SendContext->TimeoutTimer, SynchronizationTimer); 361 362 RequestTimeout.QuadPart = (-1LL) * 10 * 1000 * Request->Timeout; 363 364 ConnectionInfo.RemoteAddress = &RemoteAddressTa; 365 ConnectionInfo.RemoteAddressLength = sizeof(RemoteAddressTa); 366 367 RequestSize = sizeof(ICMP_HEADER) + Request->DataSize; 368 369 // making up the request packet 370 Buffer = ExAllocatePoolWithTag(NonPagedPool, RequestSize, OUT_DATA_TAG); 371 372 if (!Buffer) 373 { 374 ExFreePoolWithTag(SendContext, OUT_DATA_TAG); 375 376 return STATUS_INSUFFICIENT_RESOURCES; 377 } 378 379 ((PICMP_HEADER)Buffer)->Type = ICMP_TYPE_ECHO_REQUEST; 380 ((PICMP_HEADER)Buffer)->Code = ICMP_TYPE_ECHO_REPLY; 381 ((PICMP_HEADER)Buffer)->Checksum = 0; 382 ((PICMP_HEADER)Buffer)->Identifier = (UINT_PTR)PsGetCurrentProcessId() & UINT16_MAX; 383 ((PICMP_HEADER)Buffer)->Seq = InterlockedIncrement16(&IcmpSequence); 384 memcpy(Buffer + sizeof(ICMP_HEADER), (PUCHAR)Request + Request->DataOffset, Request->DataSize); 385 ((PICMP_HEADER)Buffer)->Checksum = IPv4Checksum(Buffer, RequestSize, 0); 386 SavedTtl = Request->Ttl; 387 388 RtlZeroMemory(Irp->AssociatedIrp.SystemBuffer, OutputBufferLength); 389 390 LockObject(AddrFile); 391 392 AddrFile->TTL = SavedTtl; 393 AddrFile->ReceiveDatagramHandlerContext = SendContext; 394 AddrFile->ReceiveDatagramHandler = ReceiveDatagram; 395 AddrFile->RegisteredReceiveDatagramHandler = TRUE; 396 397 UnlockObject(AddrFile); 398 399 Status = AddrFile->Send(AddrFile, &ConnectionInfo, (PCHAR)Buffer, RequestSize, &DataUsed); 400 401 // From this point we may receive a reply packet. 402 // But we are not ready for it thus InitializationFinishedEvent is needed (see below) 403 404 SendContext->StartTicks = KeQueryPerformanceCounter(NULL).QuadPart; 405 406 ExFreePoolWithTag(Buffer, OUT_DATA_TAG); 407 408 if (!NT_SUCCESS(Status)) 409 { 410 NTSTATUS _Status; 411 412 ClearReceiveHandler(AddrFile); 413 _Status = FileCloseAddress(&SendContext->TdiRequest); 414 ASSERT(NT_SUCCESS(_Status)); 415 416 IoFreeWorkItem(SendContext->FinishWorker); 417 ExFreePoolWithTag(SendContext, OUT_DATA_TAG); 418 419 TI_DbgPrint(DEBUG_ICMP, ("Failed to send a datagram: 0x%x\n", Status)); 420 return Status; 421 } 422 423 IoMarkIrpPending(Irp); 424 KeSetTimer(&SendContext->TimeoutTimer, RequestTimeout, &SendContext->TimeoutDpc); 425 KeSetEvent(&SendContext->InitializationFinishedEvent, IO_NO_INCREMENT, FALSE); 426 427 return STATUS_PENDING; 428 } 429