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 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 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 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 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 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 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