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