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

555 lines
14 KiB
C

/*++
Copyright (c) 2001 Microsoft Corporation
Abstract:
Routines implementing Dynamic DNS registration of IPv6 addresses.
--*/
#include "precomp.h"
#pragma hdrstop
#include <windns.h>
//
// DHCP IPv4 addresses inside Microsoft have a TTL of 900.
// But since IPv6 is in testing/development mode currently,
// we use a smaller TTL.
//
#define MAX_AAAA_TTL 60 // Seconds.
//
// We must update the DNS records occasionally,
// or the DNS server might garbage-collect them.
// MSDN recommends a one-day interval.
//
#define MIN_UPDATE_INTERVAL (1*DAYS*1000) // Milliseconds.
__inline ULONG
MIN(ULONG a, ULONG b)
{
if (a < b)
return a;
else
return b;
}
SOCKET g_hIpv6Socket = INVALID_SOCKET;
WSAEVENT g_hIpv6AddressChangeEvent = NULL;
HANDLE g_hIpv6AddressChangeWait = NULL;
WSAOVERLAPPED g_hIpv6AddressChangeOverlapped;
//
// Our caller uses StopIpv6AddressChangeNotification
// if we fail, so we don't need to cleanup.
//
DWORD
StartIpv6AddressChangeNotification()
{
ASSERT(g_hIpv6Socket == INVALID_SOCKET);
g_hIpv6Socket = WSASocket(AF_INET6, 0, 0,
NULL, 0,
WSA_FLAG_OVERLAPPED);
if (g_hIpv6Socket == INVALID_SOCKET)
return WSAGetLastError();
//
// We create an auto-reset event in the signalled state.
// So OnIpv6AddressChange will be executed initially.
//
ASSERT(g_hIpv6AddressChangeEvent == NULL);
g_hIpv6AddressChangeEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
if (g_hIpv6AddressChangeEvent == NULL)
return GetLastError();
//
// We specify a timeout, so that we update DNS
// at least that often. Otherwise the DNS server might
// garbage-collect our records.
//
IncEventCount("AC:StartIpv6AddressChangeNotification");
if (! RegisterWaitForSingleObject(&g_hIpv6AddressChangeWait,
g_hIpv6AddressChangeEvent,
OnIpv6AddressChange,
NULL,
MIN_UPDATE_INTERVAL,
WT_EXECUTELONGFUNCTION)) {
DecEventCount("AC:StartIpv6AddressChangeNotification");
return GetLastError();
}
return NO_ERROR;
}
//
// Assume that if the primary DNS server is the same, then that's
// good enough to combine the records.
//
BOOL
IsSameDNSServer(
PIP_ADAPTER_ADDRESSES pIf1,
PIP_ADAPTER_ADDRESSES pIf2
)
{
PIP_ADAPTER_DNS_SERVER_ADDRESS pDns1, pDns2;
pDns1 = pIf1->FirstDnsServerAddress;
pDns2 = pIf2->FirstDnsServerAddress;
if ((pDns1 == NULL) || (pDns2 == NULL)) {
return FALSE;
}
return !memcmp(pDns1->Address.lpSockaddr,
pDns2->Address.lpSockaddr,
pDns1->Address.iSockaddrLength);
}
DNS_RECORD *
BuildRecordSetW(
WCHAR *hostname,
PIP_ADAPTER_ADDRESSES pFirstIf,
PIP4_ARRAY *ppServerList
)
{
DNS_RECORD *RSet, *pNext;
int i, iAddressCount = 0;
PIP_ADAPTER_UNICAST_ADDRESS Address;
PIP_ADAPTER_ADDRESSES pIf;
int ServerCount = 0;
PIP_ADAPTER_DNS_SERVER_ADDRESS DnsServer;
LPSOCKADDR_IN sin;
//
// Count DNS servers
//
for (DnsServer = pFirstIf->FirstDnsServerAddress;
DnsServer;
DnsServer = DnsServer->Next)
{
if (DnsServer->Address.lpSockaddr->sa_family != AF_INET) {
//
// DNS api currently only supports IPv4 addresses of servers
//
continue;
}
ServerCount++;
}
if (ServerCount == 0) {
*ppServerList = NULL;
return NULL;
}
//
// Fill in DNS server array
//
*ppServerList = MALLOC(FIELD_OFFSET(IP4_ARRAY, AddrArray[ServerCount]));
if (*ppServerList == NULL) {
return NULL;
}
(*ppServerList)->AddrCount = ServerCount;
for (i = 0, DnsServer = pFirstIf->FirstDnsServerAddress;
DnsServer;
DnsServer = DnsServer->Next)
{
sin = (LPSOCKADDR_IN)DnsServer->Address.lpSockaddr;
if (sin->sin_family == AF_INET) {
(*ppServerList)->AddrArray[i++] = sin->sin_addr.s_addr;
}
}
ASSERT(i == ServerCount);
//
// Count eligible addresses
//
for (pIf=pFirstIf; pIf; pIf=pIf->Next) {
if (!(pIf->Flags & IP_ADAPTER_DDNS_ENABLED))
continue;
//
// Make sure interface has same DNS server
//
if ((pIf != pFirstIf) && !IsSameDNSServer(pFirstIf, pIf)) {
continue;
}
for (Address=pIf->FirstUnicastAddress; Address; Address=Address->Next) {
if ((Address->Address.lpSockaddr->sa_family == AF_INET6) &&
(Address->Flags & IP_ADAPTER_ADDRESS_DNS_ELIGIBLE)) {
iAddressCount++;
}
}
}
Trace1(FSM, _T("DDNS building record set of %u addresses"), iAddressCount);
if (iAddressCount == 0) {
//
// Build a record set that specifies deletion.
//
RSet = MALLOC(sizeof *RSet);
if (RSet == NULL) {
return NULL;
}
memset(RSet, 0, sizeof *RSet);
RSet->pName = (LPTSTR)hostname;
RSet->wType = DNS_TYPE_AAAA;
return RSet;
}
RSet = MALLOC(sizeof *RSet * iAddressCount);
if (RSet == NULL) {
return NULL;
}
memset(RSet, 0, sizeof *RSet * iAddressCount);
pNext = NULL;
i = iAddressCount;
while (--i >= 0) {
RSet[i].pNext = pNext;
pNext = &RSet[i];
}
i=0;
for (pIf=pFirstIf; pIf; pIf=pIf->Next) {
if (!(pIf->Flags & IP_ADAPTER_DDNS_ENABLED))
continue;
if ((pIf != pFirstIf) && !IsSameDNSServer(pFirstIf, pIf)) {
continue;
}
for (Address=pIf->FirstUnicastAddress;
Address;
Address=Address->Next) {
if ((Address->Address.lpSockaddr->sa_family == AF_INET6) &&
(Address->Flags & IP_ADAPTER_ADDRESS_DNS_ELIGIBLE)) {
SOCKADDR_IN6 *sin6 = (SOCKADDR_IN6 *)
Address->Address.lpSockaddr;
RSet[i].pName = (LPTSTR)hostname;
//
// Using a large TTL is not good because it means
// any changes (adding a new address, removing an address)
// might not be visible for a long time.
//
RSet[i].dwTtl = MIN(MAX_AAAA_TTL,
MIN(Address->PreferredLifetime,
Address->LeaseLifetime));
RSet[i].wType = DNS_TYPE_AAAA;
RSet[i].wDataLength = sizeof RSet[i].Data.AAAA;
RSet[i].Data.AAAA.Ip6Address =
* (IP6_ADDRESS *) &sin6->sin6_addr;
i++;
}
}
}
ASSERT(i == iAddressCount);
return RSet;
}
VOID
ReportDnsUpdateStatusW(
IN DNS_STATUS Status,
IN WCHAR *hostname,
IN DNS_RECORD *RSet
)
{
Trace3(ERR, _T("6to4svc: DnsReplaceRecordSet(%ls) %s: status %d"),
hostname,
RSet->wDataLength == 0 ? "delete" : "replace",
Status);
}
//
// This function adapted from net\tcpip\commands\ipconfig\info.c
//
VOID
GetInterfaceDeviceName(
IN ULONG Ipv4IfIndex,
IN PIP_INTERFACE_INFO InterfaceInfo,
OUT LPWSTR *IfDeviceName
)
{
DWORD i;
//
// search the InterfaceInfo to get the devicename for this interface.
//
(*IfDeviceName) = NULL;
for( i = 0; i < (DWORD)InterfaceInfo->NumAdapters; i ++ ) {
if( InterfaceInfo->Adapter[i].Index != Ipv4IfIndex ) continue;
(*IfDeviceName) = InterfaceInfo->Adapter[i].Name + strlen(
"\\Device\\Tcpip_" );
break;
}
}
VOID
RegisterNameOnInterface(
PIP_ADAPTER_ADDRESSES pIf,
PWCHAR hostname,
DWORD namelen)
{
DNS_RECORD *RSet = NULL;
PIP4_ARRAY pServerList = NULL;
DWORD Status;
//
// Convert to a DNS record set.
//
RSet = BuildRecordSetW(hostname, pIf, &pServerList);
if ((RSet == NULL) || (pServerList == NULL)) {
goto Cleanup;
}
Trace2(ERR, _T("DDNS registering %ls to server %d.%d.%d.%d"),
hostname, PRINT_IPADDR(pServerList->AddrArray[0]));
//
// REVIEW: We could (should?) compare the current record set
// to the previous record set, and only update DNS
// if there has been a change or if there was a timeout.
//
Status = DnsReplaceRecordSetW(
RSet,
DNS_UPDATE_CACHE_SECURITY_CONTEXT,
NULL,
pServerList,
NULL);
if (Status != NO_ERROR) {
Trace1(ERR, _T("Error: DnsReplaceRecordSet returned %d"), Status);
}
ReportDnsUpdateStatusW(Status, hostname, RSet);
Cleanup:
if (pServerList) {
FREE(pServerList);
}
if (RSet) {
FREE(RSet);
}
}
VOID
DoDdnsOnInterface(
PIP_ADAPTER_ADDRESSES pIf)
{
// Leave room to add a trailing "."
WCHAR hostname[NI_MAXHOST+1];
DWORD namelen = NI_MAXHOST;
//
// Get the fully-qualified DNS name for this machine
// and append a trailing dot.
//
if (! GetComputerNameExW(ComputerNamePhysicalDnsFullyQualified,
hostname, &namelen)) {
return;
}
namelen = (DWORD)wcslen(hostname);
hostname[namelen] = L'.';
hostname[namelen+1] = L'\0';
RegisterNameOnInterface(pIf, hostname, namelen);
//
// Also register the connection-specific name if configured to do so.
//
if (pIf->Flags & IP_ADAPTER_REGISTER_ADAPTER_SUFFIX) {
if (! GetComputerNameExW(ComputerNamePhysicalDnsHostname,
hostname, &namelen)) {
return;
}
wcscat(hostname, L".");
wcscat(hostname, pIf->DnsSuffix);
namelen = (DWORD)wcslen(hostname);
hostname[namelen] = L'.';
hostname[namelen+1] = L'\0';
RegisterNameOnInterface(pIf, hostname, namelen);
}
}
VOID CALLBACK
OnIpv6AddressChange(
IN PVOID lpParameter,
IN BOOLEAN TimerOrWaitFired)
{
PIP_ADAPTER_ADDRESSES pAdapterAddresses = NULL;
PIP_ADAPTER_ADDRESSES pIf, pIf2;
ULONG BytesNeeded = 0;
DWORD dwErr;
DWORD BytesReturned;
//
// Sleep for one second.
// Often there will be multiple address changes in a small time period,
// and we prefer to update DNS once.
//
Sleep(1000);
ENTER_API();
TraceEnter("OnIpv6AddressChange");
if (g_stService == DISABLED) {
Trace0(FSM, L"Service disabled");
goto Done;
}
//
// First request another async notification.
// We must do this *before* getting the address list,
// to avoid missing an address change.
//
if (TimerOrWaitFired == FALSE) {
for (;;) {
ZeroMemory(&g_hIpv6AddressChangeOverlapped, sizeof(WSAOVERLAPPED));
g_hIpv6AddressChangeOverlapped.hEvent = g_hIpv6AddressChangeEvent;
dwErr = WSAIoctl(g_hIpv6Socket, SIO_ADDRESS_LIST_CHANGE,
NULL, 0,
NULL, 0, &BytesReturned,
&g_hIpv6AddressChangeOverlapped,
NULL);
if (dwErr != 0) {
dwErr = WSAGetLastError();
if (dwErr != WSA_IO_PENDING) {
goto Done;
}
//
// The overlapped operation was initiated.
//
break;
}
//
// The overlapped operation completed immediately.
// Just try it again.
//
}
}
//
// Get the address list.
//
for (;;) {
//
// GetAdaptersAddresses only returns addresses of the specified address
// family. To obtain both IPv4 DNS server addresses and IPv6 unicast
// addresses in the same call we need to pass AF_UNSPEC.
//
dwErr = GetAdaptersAddresses(
AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_FRIENDLY_NAME,
NULL, pAdapterAddresses, &BytesNeeded);
if (dwErr == NO_ERROR) {
break;
}
if (dwErr != ERROR_BUFFER_OVERFLOW) {
Trace1(ERR, _T("Error: GetAdaptersAddresses returned %d"), dwErr);
goto Cleanup;
}
if (pAdapterAddresses == NULL)
pAdapterAddresses = MALLOC(BytesNeeded);
else {
PVOID Mem;
Mem = REALLOC(pAdapterAddresses, BytesNeeded);
if (Mem == NULL) {
FREE(pAdapterAddresses);
}
pAdapterAddresses = Mem;
}
if (pAdapterAddresses == NULL) {
Trace0(ERR, _T("Error: malloc failed"));
goto Cleanup;
}
}
for (pIf=pAdapterAddresses; pIf; pIf=pIf->Next) {
if (pIf->Flags & IP_ADAPTER_DDNS_ENABLED) {
//
// See if we've already done this interface because it
// had the same DNS server as a previous one.
//
for (pIf2=pAdapterAddresses; pIf2 != pIf; pIf2 = pIf2->Next) {
if (!(pIf2->Flags & IP_ADAPTER_DDNS_ENABLED))
continue;
if (IsSameDNSServer(pIf2, pIf)) {
break;
}
}
//
// If not, go ahead and do DDNS.
//
if (pIf2 == pIf) {
DoDdnsOnInterface(pIf);
}
}
}
Cleanup:
if (pAdapterAddresses) {
FREE(pAdapterAddresses);
}
Done:
TraceLeave("OnIpv6AddressChange");
LEAVE_API();
}
VOID
StopIpv6AddressChangeNotification()
{
if (g_hIpv6AddressChangeWait != NULL) {
//
// Block until we're sure that the address change callback isn't
// still running.
//
LEAVE_API();
UnregisterWaitEx(g_hIpv6AddressChangeWait, INVALID_HANDLE_VALUE);
ENTER_API();
//
// Release the event we counted for RegisterWaitForSingleObject
//
DecEventCount("AC:StopIpv6AddressChangeNotification");
g_hIpv6AddressChangeWait = NULL;
}
if (g_hIpv6AddressChangeEvent != NULL) {
CloseHandle(g_hIpv6AddressChangeEvent);
g_hIpv6AddressChangeEvent = NULL;
}
if (g_hIpv6Socket != INVALID_SOCKET) {
closesocket(g_hIpv6Socket);
g_hIpv6Socket = INVALID_SOCKET;
}
}