windows-nt/Source/XPSP1/NT/net/tcpip/tpipv6/6to4svc/io.c
2020-09-26 16:20:57 +08:00

1020 lines
22 KiB
C

/*++
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;
}