|
|
/*++
Copyright (c) 2001-2002 Microsoft Corporation
Module Name:
server.c Abstract:
This module contains the teredo server (and relay) implementation.
Author:
Mohit Talwar (mohitt) Fri Nov 02 14:55:38 2001
Environment:
User mode only.
--*/
#include "precomp.h"
#pragma hdrstop
TEREDO_SERVER_STATE TeredoServer;
TEREDO_PACKET_IO_COMPLETE TeredoServerReadComplete; TEREDO_PACKET_IO_COMPLETE TeredoServerWriteComplete; TEREDO_PACKET_IO_COMPLETE TeredoServerBounceComplete; TEREDO_PACKET_IO_COMPLETE TeredoServerReceiveComplete; TEREDO_PACKET_IO_COMPLETE TeredoServerTransmitComplete;
USHORT __inline TeredoChecksumDatagram( IN CONST IN6_ADDR *Destination, IN CONST IN6_ADDR *Source, IN USHORT NextHeader, IN UCHAR CONST *Buffer, IN USHORT Bytes ) /*++
Routine Description: 16-bit one's complement of the one's complement sum of a 'Buffer' of size 'Bytes'. NOTE: The sum is independent of the byte order! Arguments:
Buffer - Supplies the buffer containing data to compute checksum for.
Bytes - Supplies the number of bytes to compute checksum for.
Return Value:
The checksum.
--*/ { DWORD Sum, Words, i; PUSHORT Start = (PUSHORT) Buffer;
//
// If 'Bytes' is odd, this has to be handled differently.
// That, however, is never the case for us, so we optimize.
// Also ensure that 'Buffer' is aligned on a 2 byte boundary.
//
ASSERT(((((DWORD_PTR) Buffer) % 2) == 0) && ((Bytes % 2) == 0)); Words = Bytes / 2;
//
// Start with the pseudo-header.
//
Sum = htons(Bytes) + (NextHeader << 8); for (i = 0; i < 8; i++) { Sum += Source->s6_words[i] + Destination->s6_words[i]; }
for (i = 0; i < Words; i++) { Sum += Start[i]; } Sum = (Sum & 0x0000ffff) + (Sum >> 16); Sum += (Sum >> 16);
return LOWORD(~((DWORD_PTR) Sum)); }
VOID CALLBACK TeredoServerIoCompletionCallback( IN DWORD ErrorCode, IN DWORD Bytes, IN LPOVERLAPPED Overlapped ) /*++
Routine Description:
Callback routine for I/O completion on TUN interface device or UDP socket.
Arguments:
ErrorCode - Supplies the I/O completion status.
Bytes - Supplies the number of bytes transferred.
Overlapped - Supplies the completion context. Return Value:
None.
--*/ { static CONST PTEREDO_PACKET_IO_COMPLETE Callback[] = { TeredoServerReadComplete, TeredoServerWriteComplete, NULL, // No bubbling...
TeredoServerBounceComplete, TeredoServerReceiveComplete, TeredoServerTransmitComplete, NULL, // No multicasting...
}; PTEREDO_PACKET Packet = Cast( CONTAINING_RECORD(Overlapped, TEREDO_PACKET, Overlapped), TEREDO_PACKET);
ASSERT((Packet->Type != TEREDO_PACKET_BUBBLE) && (Packet->Type != TEREDO_PACKET_MULTICAST)); //
// This completion function usually posts the packet for another I/O.
// Since we are called by a non-I/O worker thread, asynchronous I/O
// requests posted here might terminate when this thread does. This
// is rare enough that we don't special case it. Moreover, we only
// make best effort guarantees to the upper layer!
//
(*Callback[Packet->Type])(ErrorCode, Bytes, Packet); }
VOID TeredoServerAddressDeletionNotification( IN IN_ADDR Address ) /*++
Routine Description:
Process an address deletion request. Arguments:
Address - Supplies the address that was deleted. Return Value:
None. Caller LOCK: API.
--*/ { if (!IN4_ADDR_EQUAL(Address, TeredoServer.Io.SourceAddress.sin_addr)) { return; }
//
// Refresh the socket state (the socket bound to SourceAddress).
//
if (TeredoRefreshSocket(&(TeredoServer.Io)) != NO_ERROR) { //
// Online -> Offline.
//
TeredoStopServer(); return; }
if (!IN4_ADDR_EQUAL( TeredoServer.Io.SourceAddress.sin_addr, TeredoServer.Io.ServerAddress.sin_addr)) { //
// Online -> Offline.
//
TeredoStopServer(); return; } }
VOID TeredoStartServer( VOID ) /*++
Routine Description:
Attempt to start the teredo service at the server.
Events / Transitions ServiceStart Offline -> Online. ServiceEnable Offline -> Online. AdapterArrival Offline -> Online. AddressAddition Offline -> Online.
Arguments:
None.
Return Value:
None.
Caller LOCK: API.
--*/ { TraceEnter("TeredoStartServer");
//
// Can't have both the client and server on the same node.
//
if (TeredoClient.State != TEREDO_STATE_OFFLINE) { return; }
//
// Well, the service has already been started!
//
if (TeredoServer.State != TEREDO_STATE_OFFLINE) { return; }
TeredoServer.State = TEREDO_STATE_ONLINE;
//
// Start I/O processing.
//
if (TeredoStartIo(&(TeredoServer.Io)) != NO_ERROR) { goto Bail; }
if (!IN4_ADDR_EQUAL( TeredoServer.Io.SourceAddress.sin_addr, TeredoServer.Io.ServerAddress.sin_addr)) { goto Bail; } return;
Bail: TeredoServer.State = TEREDO_STATE_OFFLINE; TeredoStopIo(&(TeredoServer.Io)); }
VOID TeredoStopServer( VOID ) /*++
Routine Description:
Stop the teredo service at the server. Events / Transitions ServiceStop Online -> Offline. ServiceDisable Online -> Offline. AdapterRemoval Online -> Offline. AddressDeletion Online -> Offline.
Arguments:
None.
Return Value:
None.
Caller LOCK: API.
--*/ { TraceEnter("TeredoStopServer");
//
// Well, the service was never started!
//
if (TeredoServer.State == TEREDO_STATE_OFFLINE) { return; }
TeredoServer.State = TEREDO_STATE_OFFLINE;
TeredoStopIo(&(TeredoServer.Io)); }
DWORD TeredoInitializeServer( VOID ) /*++
Routine Description:
Initializes the server. Arguments:
None.
Return Value:
NO_ERROR or failure code.
--*/ { DWORD Error; IN_ADDR Group; Group.s_addr = htonl(INADDR_ANY); //
// Obtain a reference on the teredo server for initialization.
//
TeredoServer.ReferenceCount = 1;
Error = TeredoInitializeIo( &(TeredoServer.Io), Group, TeredoReferenceServer, TeredoDereferenceServer, TeredoServerIoCompletionCallback); if (Error != NO_ERROR) { return Error; }
TeredoServer.State = TEREDO_STATE_OFFLINE;
IncEventCount("TeredoInitializeServer");
return NO_ERROR; }
VOID TeredoUninitializeServer( VOID ) /*++
Routine Description:
Uninitializes the server. Typically invoked upon service stop. Arguments:
None.
Return Value:
None.
--*/ { TeredoStopServer(); TeredoDereferenceServer(); }
VOID TeredoCleanupServer( VOID ) /*++
Routine Description:
Cleans up the server after the last reference to it has been released. Arguments:
None.
Return Value:
None.
--*/ { TeredoCleanupIo(&(TeredoServer.Io)); DecEventCount("TeredoCleanupServer"); }
VOID TeredoBuildRouterAdvertisementPacket( OUT PTEREDO_PACKET Packet, IN IN6_ADDR Destination, IN IN_ADDR Address, IN USHORT Port ) /*++
Routine Description:
Construct an RA in response to the client's RS. Arguments:
Packet - Returns the constructed RA in the caller supplied packet.
Destination - Supplies the destination address of the RA, the client's random link-local address in the triggering RS. Passed by value so this function may reuse the packet buffer. Address - Supplies the client's address, used to source the RS.
Port - Supplies the client's port, used to source the RS. Return Value:
None.
--*/ { PIP6_HDR Ipv6 = (PIP6_HDR) Packet->Buffer.buf; ICMPv6Header *Icmpv6 = (ICMPv6Header *) (Ipv6 + 1); NDRouterAdvertisement *Ra = (NDRouterAdvertisement *) (Icmpv6 + 1); NDOptionPrefixInformation *Prefix = (NDOptionPrefixInformation *) (Ra + 1); NDOptionMTU *Mtu = (NDOptionMTU *) (Prefix + 1); PUCHAR End = (PUCHAR) (Mtu + 1); Packet->Buffer.len = (ULONG) (End - (Packet->Buffer.buf)); ZeroMemory(Packet->Buffer.buf, Packet->Buffer.len);
//
// Construct the IPv6 Header...
//
Ipv6->ip6_plen = htons((USHORT) (Packet->Buffer.len - sizeof(IP6_HDR))); Ipv6->ip6_nxt = IP_PROTOCOL_ICMPv6; Ipv6->ip6_hlim = 255; Ipv6->ip6_vfc = IPV6_VERSION;
//
// ...using the server's link local address: fe80:IPv4 Address:UDP Port::1
//
Ipv6->ip6_src.s6_words[0] = 0x80fe; Ipv6->ip6_src.s6_words[1] = ((PUSHORT) &TeredoServer.Io.ServerAddress.sin_addr)[0]; Ipv6->ip6_src.s6_words[2] = ((PUSHORT) &TeredoServer.Io.ServerAddress.sin_addr)[1]; Ipv6->ip6_src.s6_words[3] = TeredoServer.Io.ServerAddress.sin_port; Ipv6->ip6_src.s6_words[7] = 0x0100; Ipv6->ip6_dest = Destination; //
// Construct ICMPv6 Header.
//
Icmpv6->Type = ICMPv6_ROUTER_ADVERT;
//
// Construct RouterAdvertisement Header.
//
Ra->RouterLifetime = htons(TEREDO_ROUTER_LIFETIME); Ra->Flags = ROUTE_PREF_LOW; //
// Construct Prefix Option.
//
Prefix->Type = ND_OPTION_PREFIX_INFORMATION; Prefix->Length = sizeof(NDOptionPrefixInformation) / 8; Prefix->PrefixLength = 64; Prefix->Flags = ND_PREFIX_FLAG_AUTONOMOUS; Prefix->ValidLifetime = Prefix->PreferredLifetime = IPV6_INFINITE_LIFETIME;
Prefix->Prefix.s6_words[0] = TeredoIpv6ServicePrefix.s6_words[0]; Prefix->Prefix.s6_words[1] = ((PUSHORT) &Address)[0]; Prefix->Prefix.s6_words[2] = ((PUSHORT) &Address)[1]; Prefix->Prefix.s6_words[3] = Port;
//
// Construct MTU Option.
//
Mtu->Type = ND_OPTION_MTU; Mtu->Length = sizeof(NDOptionMTU) / 8; Mtu->MTU = htonl(IPV6_TEREDOMTU);
//
// Checksum Packet!
//
Icmpv6->Checksum = TeredoChecksumDatagram( &(Ipv6->ip6_dest), &(Ipv6->ip6_src), IP_PROTOCOL_ICMPv6, (PUCHAR) Icmpv6, (USHORT) (End - ((PUCHAR) Icmpv6))); }
PTEREDO_PACKET TeredoReceiveRouterSolicitation( IN PTEREDO_PACKET Packet, IN ULONG Bytes ) /*++
Routine Description:
Process the router solicitation packet received on the UDP socket. Arguments:
Packet - Supplies the packet that was received.
Bytes - Supplies the length of the packet. Return Value:
Returns the supplied packet if processing completed or failed; NULL if the processing will complete asynchronously.
--*/ { PUCHAR Buffer = Packet->Buffer.buf; ICMPv6Header *Icmpv6; ULONG Length; PIP6_HDR Ipv6 = (PIP6_HDR) Packet->Buffer.buf; Icmpv6 = TeredoParseIpv6Headers(Buffer, Bytes); if (Icmpv6 == NULL) { return Packet; } if ((Icmpv6->Type != ICMPv6_ROUTER_SOLICIT) || (Icmpv6->Code != 0)) { return Packet; } Buffer = (PUCHAR) (Icmpv6 + 1); Bytes -= (ULONG) (Buffer - Packet->Buffer.buf); //
// Parse the rest of the router solicitation header.
//
if (Bytes < sizeof(ULONG)) { return Packet; } Buffer += sizeof(ULONG); Bytes -= sizeof(ULONG); while (Bytes != 0) { //
// Parse TLV options.
//
if (Bytes < 8) { return Packet; } Length = (Buffer[1] * 8); if ((Length == 0) || (Bytes < Length)) { return Packet; } Buffer += Length; Bytes -= Length; }
//
// Checksum Packet!
//
if (TeredoChecksumDatagram( &(Ipv6->ip6_dest), &(Ipv6->ip6_src), IP_PROTOCOL_ICMPv6, (PUCHAR) Icmpv6, (USHORT) (Buffer - ((PUCHAR) Icmpv6))) != 0) { return Packet; } //
// We have a valid router solicitation, so tunnel an advertisement!
// Reuse the RS packet to bounce the RA.
//
TeredoBuildRouterAdvertisementPacket( Packet, Ipv6->ip6_src, Packet->SocketAddress.sin_addr, Packet->SocketAddress.sin_port); Packet->Type = TEREDO_PACKET_BOUNCE; return TeredoTransmitPacket(&(TeredoServer.Io), Packet); }
PTEREDO_PACKET TeredoServerReceiveData( IN PTEREDO_PACKET Packet, IN ULONG Bytes ) /*++
Routine Description:
Process the data packet received on the UDP socket. Arguments:
Packet - Supplies the packet that was received.
Bytes - Supplies the length of the packet. Return Value:
Returns the supplied packet if processing completed or failed; NULL if the processing will complete asynchronously.
--*/ { PIP6_HDR Ipv6 = (PIP6_HDR) Packet->Buffer.buf; IN_ADDR Address; USHORT Port;
//
// Validate source address.
//
if (!TeredoServicePrefix(&(Ipv6->ip6_src))) { return Packet; }
TeredoParseAddress(&(Ipv6->ip6_src), &Address, &Port); if (!IN4_ADDR_EQUAL(Packet->SocketAddress.sin_addr, Address) || (Packet->SocketAddress.sin_port != Port)) { //
// Should have been constructed by the *right* teredo peer.
//
return Packet; } //
// NOTE: Caller has previously verified that the source has global scope.
//
//
// Validate destination address.
//
if (TeredoServicePrefix(&(Ipv6->ip6_dest))) { TeredoParseAddress(&(Ipv6->ip6_dest), &Address, &Port); if (!TeredoIpv4GlobalAddress((PUCHAR) &Address)) { //
// The IPv4 destination address should be global scope.
//
return Packet; }
//
// Now tunnel it to the destination.
//
Packet->SocketAddress.sin_addr = Address; Packet->SocketAddress.sin_port = Port; Packet->Type = TEREDO_PACKET_BOUNCE; Packet->Buffer.len = Bytes; return TeredoTransmitPacket(&(TeredoServer.Io), Packet); }
if (!TeredoIpv6GlobalAddress(&(Ipv6->ip6_dest))) { //
// The IPv6 destination address should be global scope.
//
return Packet; }
//
// Else forward it to the stack for forwarding.
//
Packet->Type = TEREDO_PACKET_WRITE; Packet->Buffer.len = Bytes; return TeredoWritePacket(&(TeredoServer.Io), Packet); }
VOID TeredoServerReadComplete( IN DWORD Error, IN ULONG Bytes, IN PTEREDO_PACKET Packet ) /*++
Routine Description:
Process a read completion on the TUN device.
--*/ { PIP6_HDR Ipv6 = (PIP6_HDR) Packet->Buffer.buf; IN_ADDR Address; USHORT Port; if ((Error != NO_ERROR) || (Bytes < sizeof(IP6_HDR))) { //
// Attempt to post the read again.
// If we are going offline, the packet is destroyed in the attempt.
//
TeredoPostRead(&(TeredoServer.Io), Packet); return; }
TraceEnter("TeredoServerReadComplete");
//
// Validate destination address.
//
if (!TeredoServicePrefix(&(Ipv6->ip6_dest))) { TeredoPostRead(&(TeredoServer.Io), Packet); return; } TeredoParseAddress(&(Ipv6->ip6_dest), &Address, &Port); if (!TeredoIpv4GlobalAddress((PUCHAR) &Address)) { //
// The IPv4 source address should be global scope.
//
TeredoPostRead(&(TeredoServer.Io), Packet); return; }
//
// Now tunnel it to the destination.
//
Packet->SocketAddress.sin_addr = Address; Packet->SocketAddress.sin_port = Port; Packet->Type = TEREDO_PACKET_TRANSMIT; Packet->Buffer.len = Bytes; if (TeredoTransmitPacket(&(TeredoServer.Io), Packet) == NULL) { return; } //
// We are done processing this packet.
//
TeredoServerTransmitComplete(NO_ERROR, Bytes, Packet); }
VOID TeredoServerWriteComplete( IN DWORD Error, IN ULONG Bytes, IN PTEREDO_PACKET Packet ) /*++
Routine Description:
Process a write completion on the TUN device.
--*/ { TraceEnter("TeredoServerWriteComplete"); //
// Attempt to post the receive again.
// If we are going offline, the packet is destroyed in the attempt.
//
Packet->Type = TEREDO_PACKET_RECEIVE; Packet->Buffer.len = IPV6_TEREDOMTU; TeredoPostReceives(&(TeredoServer.Io), Packet); }
VOID TeredoServerBounceComplete( IN DWORD Error, IN ULONG Bytes, IN PTEREDO_PACKET Packet ) /*++
Routine Description:
Process a bounce completion on the UDP socket.
--*/ { TraceEnter("TeredoServerBounceComplete");
//
// Attempt to post the receive again.
// If we are going offline, the packet is destroyed in the attempt.
//
Packet->Type = TEREDO_PACKET_RECEIVE; Packet->Buffer.len = IPV6_TEREDOMTU; TeredoPostReceives(&(TeredoServer.Io), Packet); }
VOID TeredoServerReceiveComplete( IN DWORD Error, IN ULONG Bytes, IN PTEREDO_PACKET Packet ) /*++
Routine Description:
Process a receive completion on the UDP socket.
--*/ { PIP6_HDR Ipv6 = (PIP6_HDR) Packet->Buffer.buf; InterlockedDecrement(&(TeredoServer.Io.PostedReceives)); if ((Error != NO_ERROR) || (Bytes < sizeof(IP6_HDR)) || ((Ipv6->ip6_vfc & IP_VER_MASK) != IPV6_VERSION) || (Bytes < (ntohs(Ipv6->ip6_plen) + sizeof(IP6_HDR))) || (!TeredoIpv4GlobalAddress( (PUCHAR) &(Packet->SocketAddress.sin_addr)))) { //
// Attempt to post the receive again.
// If we are going offline, the packet is destroyed in the attempt.
//
TeredoPostReceives(&(TeredoServer.Io), Packet); return; }
TraceEnter("TeredoServerReceiveComplete"); if (IN6_IS_ADDR_LINKLOCAL(&(Ipv6->ip6_src)) || IN6_IS_ADDR_UNSPECIFIED(&(Ipv6->ip6_src))) { //
// This should be a valid router solicitation. Note that only router
// solicitation packets are accepted with a link-local source address.
//
Packet = TeredoReceiveRouterSolicitation(Packet, Bytes); } else { //
// This may be a packet of any other kind.
//
Packet = TeredoServerReceiveData(Packet, Bytes); }
if (Packet != NULL) { //
// We are done processing this packet.
//
TeredoServerWriteComplete(NO_ERROR, Bytes, Packet); } }
VOID TeredoServerTransmitComplete( IN DWORD Error, IN ULONG Bytes, IN PTEREDO_PACKET Packet ) /*++
Routine Description:
Process a transmit completion on the UDP socket.
--*/ { TraceEnter("TeredoServerTransmitComplete"); //
// Attempt to post the read again.
// If we are going offline, the packet is destroyed in the attempt.
//
Packet->Type = TEREDO_PACKET_READ; Packet->Buffer.len = IPV6_TEREDOMTU; TeredoPostRead(&(TeredoServer.Io), Packet); }
|