/*++ Copyright (c) 2001-2002 Microsoft Corporation Module Name: io.c Abstract: This module contains teredo I/O management functions. Socket management, overlapped completion indication, and buffer management ideas were originally implemented for tftpd by JeffV. Author: Mohit Talwar (mohitt) Wed Oct 24 14:05:36 2001 Environment: User mode only. --*/ #include "precomp.h" #pragma hdrstop WCHAR TeredoTunnelDeviceName[] = L"\\\\.\\\\Tun0"; DWORD GetPreferredSourceAddress( IN PSOCKADDR_IN Destination, OUT PSOCKADDR_IN Source ) { int BytesReturned; if (WSAIoctl( g_sIPv4Socket, SIO_ROUTING_INTERFACE_QUERY, Destination, sizeof(SOCKADDR_IN), Source, sizeof(SOCKADDR_IN), &BytesReturned, NULL, NULL) == SOCKET_ERROR) { return WSAGetLastError(); } // // When the source is local, the node is configured as the teredo server. // Hence it needs to explicitly specify the port to bind to. Assign here! // if ((Source->sin_addr.s_addr == Destination->sin_addr.s_addr) || (Source->sin_addr.s_addr == htonl(INADDR_LOOPBACK))) { *Source = *Destination; } return NO_ERROR; } __inline DWORD TeredoResolveServer( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Resolve the teredo IPv4 server address and UDP service port. Arguments: TeredoIo - Supplies the I/O state. Return Value: NO_ERROR or failure code. --*/ { struct addrinfo *Addresses; DWORD Error; // // Resolve the teredo server name. // Error = GetAddrInfoW(TeredoServerName, NULL, NULL, &Addresses); if (Error == NO_ERROR) { Error = ERROR_INCORRECT_ADDRESS; if (Addresses->ai_family == AF_INET) { TeredoIo->ServerAddress.sin_addr = ((PSOCKADDR_IN) Addresses->ai_addr)->sin_addr; TeredoIo->ServerAddress.sin_port = TEREDO_PORT; Error = NO_ERROR; } else if (Addresses->ai_family == AF_INET6) { PIN6_ADDR Ipv6Address; IN_ADDR Ipv4Address; USHORT Port; // // Extract server's IPv4 address and port from the IPv6 address. // Ipv6Address = &(((PSOCKADDR_IN6) Addresses->ai_addr)->sin6_addr); if (TeredoServicePrefix(Ipv6Address)) { TeredoParseAddress(Ipv6Address, &Ipv4Address, &Port); if (Port == TEREDO_PORT) { TeredoIo->ServerAddress.sin_addr = Ipv4Address; TeredoIo->ServerAddress.sin_port = Port; Error = NO_ERROR; } } } freeaddrinfo(Addresses); } return Error; } PTEREDO_PACKET TeredoCreatePacket( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Creates a teredo packet. Arguments: TeredoIo - Supplies the I/O state. Return Value: Returns the created packet. --*/ { PTEREDO_PACKET Packet = (PTEREDO_PACKET) HeapAlloc( TeredoIo->PacketHeap, 0, sizeof(TEREDO_PACKET) + IPV6_TEREDOMTU); if (Packet == NULL) { return NULL; } TeredoInitializePacket(Packet); Packet->Buffer.len = IPV6_TEREDOMTU; // // Obtain a reference on the teredo object for each outstanding packet. // (*TeredoIo->Reference)(); return Packet; } VOID TeredoDestroyPacket( IN PTEREDO_IO TeredoIo, IN PTEREDO_PACKET Packet ) /*++ Routine Description: Destroys a teredo packet. Arguments: TeredoIo - Supplies the I/O state. Packet - Supplies the packet to destroy. Return Value: None. --*/ { ASSERT(Packet->Type != TEREDO_PACKET_BUBBLE); ASSERT(Packet->Type != TEREDO_PACKET_MULTICAST); HeapFree(TeredoIo->PacketHeap, 0, Packet); (*TeredoIo->Dereference)(); } ULONG TeredoPostReceives( IN PTEREDO_IO TeredoIo, IN PTEREDO_PACKET Packet OPTIONAL ) /*++ Routine Description: Post an asynchronous receive request on the UDP socket. NOTE: The supplied packet (if any) is destroyed if there are already enough (TEREDO_HIGH_WATER_MARK) receives posted on the UDP socket. Arguments: TeredoIo - Supplies the I/O state. Packet - Supplies the packet to reuse, or NULL. Return Value: Returns the number of receives posted on the UDP socket. --*/ { ULONG Count = 0, PostedReceives = TeredoIo->PostedReceives; DWORD Error; // // Attempt to post as many receives as required to... // 1. - either - have high water-mark number of posted receives. // 2. - or - satisfy the current burst of packets. // while (PostedReceives < TEREDO_HIGH_WATER_MARK) { // // Allocate the Packet if we're not reusing one. // if (Packet == NULL) { Packet = TeredoCreatePacket(TeredoIo); if (Packet == NULL) { return PostedReceives; } } Packet->Type = TEREDO_PACKET_RECEIVE; ZeroMemory((PUCHAR) &(Packet->Overlapped), sizeof(OVERLAPPED)); Error = WSARecvFrom( TeredoIo->Socket, &(Packet->Buffer), 1, NULL, &(Packet->Flags), (PSOCKADDR) &(Packet->SocketAddress), &(Packet->SocketAddressLength), &(Packet->Overlapped), NULL); if (Error == SOCKET_ERROR) { Error = WSAGetLastError(); } switch (Error) { case NO_ERROR: // // The completion routine will have already been scheduled. // PostedReceives = InterlockedIncrement(&(TeredoIo->PostedReceives)); if (Count++ > TEREDO_LOW_WATER_MARK) { // // Enough already! // return PostedReceives; } Packet = NULL; continue; case WSA_IO_PENDING: // // The overlapped operation has been successfully initiated. // Completion will be indicated at a later time. // PostedReceives = InterlockedIncrement(&(TeredoIo->PostedReceives)); return PostedReceives; case WSAECONNRESET: // // A previous send operation resulted in an ICMP "Port Unreachable" // message. But why let that stop us? Post the same packet again. // continue; default: // // The overlapped operation was not successfully initiated. // No completion indication will occur. // goto Bail; } } Bail: if (Packet != NULL) { TeredoDestroyPacket(TeredoIo, Packet); } return PostedReceives; } VOID CALLBACK TeredoReceiveNotification( IN PVOID Parameter, IN BOOLEAN TimerOrWaitFired ) /*++ Routine Description: Callback for when there are pending read notifications on the UDP socket. We attempt to post more packets. Arguments: Parameter - Supplies the I/O state. TimerOrWaitFired - Ignored. Return Value: None. --*/ { ULONG Old, New; PTEREDO_IO TeredoIo = Cast(Parameter, TEREDO_IO); New = TeredoIo->PostedReceives; while(New < TEREDO_LOW_WATER_MARK) { // // If this fails, the event triggering this callback will stop // signalling due to a lack of a successful WSARecvFrom. This will // likely occur during low-memory or stress conditions. When the // system returns to normal, the low water-mark packets will be // reposted, thus re-enabling the event which triggers this callback. // Old = New; New = TeredoPostReceives(TeredoIo, NULL); if (New == Old) { // // There is no change in the number of posted receive packets. // return; } } } PTEREDO_PACKET TeredoTransmitPacket( IN PTEREDO_IO TeredoIo, IN PTEREDO_PACKET Packet ) /*++ Routine Description: Post an asynchronous transmit request on the UDP socket. Arguments: TeredoIo - Supplies the I/O state. Packet - Supplies the packet to transmit. Return Value: Returns the supplied packet if the transmit completed or failed; NULL if the transmit will complete asynchronously. --*/ { DWORD Error, Bytes; ASSERT((Packet->Type == TEREDO_PACKET_BUBBLE) || (Packet->Type == TEREDO_PACKET_BOUNCE) || (Packet->Type == TEREDO_PACKET_TRANSMIT) || (Packet->Type == TEREDO_PACKET_MULTICAST)); // // Try sending it non-blocking. // Error = WSASendTo( TeredoIo->Socket, &(Packet->Buffer), 1, &Bytes, Packet->Flags, (PSOCKADDR) &(Packet->SocketAddress), Packet->SocketAddressLength, NULL, NULL); if ((Error != SOCKET_ERROR) || (WSAGetLastError() != WSAEWOULDBLOCK)) { return Packet; } // // WSASendTo threatens to block, so we send it overlapped. // ZeroMemory((PUCHAR) &(Packet->Overlapped), sizeof(OVERLAPPED)); Error = WSASendTo( TeredoIo->Socket, &(Packet->Buffer), 1, &Bytes, Packet->Flags, (PSOCKADDR) &(Packet->SocketAddress), Packet->SocketAddressLength, &(Packet->Overlapped), NULL); if ((Error != SOCKET_ERROR) || (WSAGetLastError() != WSA_IO_PENDING)) { return Packet; } // // The overlapped operation has been successfully initiated. // Completion will be indicated at a later time. // return NULL; } VOID TeredoDestroySocket( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Close the UDP socket. Arguments: TeredoIo - Supplies the I/O state. Return Value: None. --*/ { if (TeredoIo->ReceiveEventWait != NULL) { UnregisterWait(TeredoIo->ReceiveEventWait); TeredoIo->ReceiveEventWait = NULL; } if (TeredoIo->ReceiveEvent != NULL) { CloseHandle(TeredoIo->ReceiveEvent); TeredoIo->ReceiveEvent = NULL; } if (TeredoIo->Socket != INVALID_SOCKET) { // // Close the socket. This will disable the FD_READ event select, // as well as cancel all pending overlapped operations. // closesocket(TeredoIo->Socket); TeredoIo->Socket = INVALID_SOCKET; } } DWORD TeredoCreateSocket( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Open the UDP socket for receives and transmits. Arguments: TeredoIo - Supplies the I/O state. Return Value: NO_ERROR or failure code. --*/ { DWORD Error; struct ip_mreq Multicast; BOOL Loopback; // // Create the socket. // TeredoIo->Socket = WSASocket( AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (TeredoIo->Socket == INVALID_SOCKET) { return GetLastError(); } // // Bind the socket on the correct address and port. // if (bind( TeredoIo->Socket, (PSOCKADDR) &(TeredoIo->SourceAddress), sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { goto Bail; } // // Register for completion callbacks on the socket. // if (!BindIoCompletionCallback( (HANDLE) TeredoIo->Socket, TeredoIo->IoCompletionCallback, 0)) { goto Bail; } // // Select the socket for read notifications so we know when to post // more packets. This also sets the socket to nonblocking mode. // TeredoIo->ReceiveEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (TeredoIo->ReceiveEvent == NULL) { goto Bail; } if (WSAEventSelect( TeredoIo->Socket, TeredoIo->ReceiveEvent, FD_READ) == SOCKET_ERROR) { goto Bail; } if (!RegisterWaitForSingleObject( &(TeredoIo->ReceiveEventWait), TeredoIo->ReceiveEvent, TeredoReceiveNotification, (PVOID) TeredoIo, INFINITE, 0)) { goto Bail; } // // Prepost low water-mark number of receive Packets. If the FD_READ event // signals on the socket before we're done, we'll exceed the low water-mark // here but that's really quite harmless. // SetEvent(TeredoIo->ReceiveEvent); // // See if there is a multicast group to join. // if (IN4_MULTICAST(TeredoIo->Group)) { // // Default TTL of multicast packets is 1, so don't bother setting it. // Set loopback to ignore self generated multicast packets. // Failure is not fatal! // Loopback = FALSE; (VOID) setsockopt( TeredoIo->Socket, IPPROTO_IP, IP_MULTICAST_LOOP, (const CHAR *) &Loopback, sizeof(BOOL)); // // Join the multicast group on the native interface. // Failure is not fatal! // Multicast.imr_multiaddr = TeredoIo->Group; Multicast.imr_interface = TeredoIo->SourceAddress.sin_addr; (VOID) setsockopt( TeredoIo->Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const CHAR *) &Multicast, sizeof(struct ip_mreq)); } return NO_ERROR; Bail: Error = GetLastError(); TeredoDestroySocket(TeredoIo); return Error; } BOOL TeredoPostRead( IN PTEREDO_IO TeredoIo, IN PTEREDO_PACKET Packet OPTIONAL ) /*++ Routine Description: Post an asynchronous read request on the TUN interface device. Arguments: TeredoIo - Supplies the I/O state. Packet - Supplies the packet to reuse, or NULL. Return Value: TRUE if a read was successfully posted, FALSE otherwise. --*/ { BOOL Success; // // Allocate the Packet if we're not reusing one. // if (Packet == NULL) { Packet = TeredoCreatePacket(TeredoIo); if (Packet == NULL) { return FALSE; } } Packet->Type = TEREDO_PACKET_READ; ZeroMemory((PUCHAR) &(Packet->Overlapped), sizeof(OVERLAPPED)); Success = ReadFile( TeredoIo->TunnelDevice, Packet->Buffer.buf, Packet->Buffer.len, NULL, &(Packet->Overlapped)); if (Success || (GetLastError() == ERROR_IO_PENDING)) { // // On success, the completion routine will have already been scheduled. // return TRUE; } TeredoDestroyPacket(TeredoIo, Packet); return FALSE; } PTEREDO_PACKET TeredoWritePacket( IN PTEREDO_IO TeredoIo, IN PTEREDO_PACKET Packet ) /*++ Routine Description: Post an asynchronous write request on the TUN interface device. Arguments: TeredoIo - Supplies the I/O state. Packet - Supplies the packet to write. Return Value: Returns the supplied packet if the write failed; NULL if the write will complete asynchronously. --*/ { BOOL Success; ASSERT(Packet->Type == TEREDO_PACKET_WRITE); ZeroMemory((PUCHAR) &(Packet->Overlapped), sizeof(OVERLAPPED)); Success = WriteFile( TeredoIo->TunnelDevice, Packet->Buffer.buf, Packet->Buffer.len, NULL, &(Packet->Overlapped)); if (Success || (GetLastError() == ERROR_IO_PENDING)) { // // On success, the completion routine will have already been scheduled. // return NULL; } return Packet; } VOID TeredoCloseDevice( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Close the TUN interface device. Arguments: TeredoIo - Supplies the I/O state. Return Value: None. --*/ { // // Close the device. This will cancel all pending overlapped operations. // if (TeredoIo->TunnelDevice != INVALID_HANDLE_VALUE) { CloseHandle(TeredoIo->TunnelDevice); TeredoIo->TunnelDevice = INVALID_HANDLE_VALUE; wcscpy(TeredoIo->TunnelInterface, L""); } } DWORD TeredoOpenDevice( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Open the TUN interface device for reads and writes. Arguments: None. Return Value: NO_ERROR or failure code. --*/ { DWORD Error; ULONG i; TeredoIo->TunnelDevice = CreateFile( TeredoTunnelDeviceName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (TeredoIo->TunnelDevice == INVALID_HANDLE_VALUE) { return GetLastError(); } // // Register for completion callbacks on the tun device. // if (!BindIoCompletionCallback( TeredoIo->TunnelDevice, TeredoIo->IoCompletionCallback, 0)) { Error = GetLastError(); goto Bail; } // // Post a fixed number of reads on the device. // for (i = 0; i < TEREDO_LOW_WATER_MARK; i++) { if (!TeredoPostRead(TeredoIo, NULL)) { break; } } if (i != 0) { return NO_ERROR; } Error = ERROR_READ_FAULT; // // We couldn't post a single read on the device. What good is it? // Bail: TeredoCloseDevice(TeredoIo); return Error; } VOID TeredoStopIo( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Stop I/O processing. Arguments: TeredoIo - Supplies the I/O state. Return Value: None. --*/ { if (TeredoIo->TunnelDevice != INVALID_HANDLE_VALUE) { TeredoCloseDevice(TeredoIo); } if (TeredoIo->Socket != INVALID_SOCKET) { TeredoDestroySocket(TeredoIo); } TeredoIo->ServerAddress.sin_port = 0; TeredoIo->ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY); TeredoIo->SourceAddress.sin_addr.s_addr = htonl(INADDR_ANY); } DWORD TeredoStartIo( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Start I/O processing. Arguments: TeredoIo - Supplies the I/O state. Return Value: NO_ERROR or failure code. --*/ { DWORD Error; // // Resolve the teredo server name and service name. // Error = TeredoResolveServer(TeredoIo); if (Error != NO_ERROR) { Trace1(ERR, _T("TeredoResolveServer: %u"), Error); return Error; } // // Get the preferred source address to the teredo server. // Error = GetPreferredSourceAddress( &(TeredoIo->ServerAddress), &(TeredoIo->SourceAddress)); if (Error != NO_ERROR) { Trace1(ERR, _T("GetPreferredSourceAddress: %u"), Error); goto Bail; } // // Create the UDP Socket. // Error = TeredoCreateSocket(TeredoIo); if (Error != NO_ERROR) { Trace1(ERR, _T("TeredoCreateSocket: %u"), Error); goto Bail; } // // Open the TunnelDevice. // Error = TeredoOpenDevice(TeredoIo); if (Error != NO_ERROR) { Trace1(ERR, _T("TeredoOpenDevice: %u"), Error); goto Bail; } return NO_ERROR; Bail: TeredoStopIo(TeredoIo); return Error; } DWORD TeredoRefreshSocket( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Refresh the I/O state upon deletion of SourceAddress. Arguments: TeredoIo - Supplies the I/O state. Return Value: NO_ERROR if the I/O state is successfully refreshed, o/w failure code. The caller is responsible for cleaning up the I/O state upon failure. --*/ { DWORD Error; SOCKADDR_IN Old = TeredoIo->SourceAddress; // // Let's re-resolve the teredo server address and port. // Refresh might have been triggered by a change in server/service name. // Error = TeredoResolveServer(TeredoIo); if (Error != NO_ERROR) { return Error; } // // Get the preferred source address to the teredo server. // Error = GetPreferredSourceAddress( &(TeredoIo->ServerAddress), &(TeredoIo->SourceAddress)); if (Error != NO_ERROR) { return Error; } if (IN4_SOCKADDR_EQUAL(&(TeredoIo->SourceAddress), &Old)) { // // No change to the bound address and port. Whew! // return NO_ERROR; } // // Destroy the old UDP socket. // TeredoDestroySocket(TeredoIo); // // Create a new UDP socket, bound to the new address and port. // Error = TeredoCreateSocket(TeredoIo); if (Error != NO_ERROR) { return Error; } return NO_ERROR; } DWORD TeredoInitializeIo( IN PTEREDO_IO TeredoIo, IN IN_ADDR Group, IN PTEREDO_REFERENCE Reference, IN PTEREDO_DEREFERENCE Dereference, IN LPOVERLAPPED_COMPLETION_ROUTINE IoCompletionCallback ) /*++ Routine Description: Initialize the I/O state. Arguments: TeredoIo - Supplies the I/O state. Group - Supplies the multicast group to join (or INADDR_ANY). Return Value: NO_ERROR or failure code. --*/ { #if DBG TeredoIo->Signature = TEREDO_IO_SIGNATURE; #endif // DBG TeredoIo->PostedReceives = 0; TeredoIo->ReceiveEvent = TeredoIo->ReceiveEventWait = NULL; TeredoIo->Group = Group; ZeroMemory(&(TeredoIo->ServerAddress), sizeof(SOCKADDR_IN)); TeredoIo->ServerAddress.sin_family = AF_INET; ZeroMemory(&(TeredoIo->SourceAddress), sizeof(SOCKADDR_IN)); TeredoIo->SourceAddress.sin_family = AF_INET; TeredoIo->Socket = INVALID_SOCKET; TeredoIo->TunnelDevice = INVALID_HANDLE_VALUE; wcscpy(TeredoIo->TunnelInterface, L""); TeredoIo->Reference = Reference; TeredoIo->Dereference = Dereference; TeredoIo->IoCompletionCallback = IoCompletionCallback; TeredoIo->PacketHeap = HeapCreate(0, 0, 0); if (TeredoIo->PacketHeap == NULL) { return GetLastError(); } return NO_ERROR; } VOID TeredoCleanupIo( IN PTEREDO_IO TeredoIo ) /*++ Routine Description: Cleanup the I/O state. Arguments: TeredoIo - Supplies the I/O state. Return Value: None. --*/ { HeapDestroy(TeredoIo->PacketHeap); TeredoIo->PacketHeap = NULL; }