7006496: Use modern Windows API to retrieve OS DNS servers

Reviewed-by: dfuchs, chegar, aefimov
This commit is contained in:
Anuraag Agrawal 2020-01-24 16:16:39 +00:00 committed by Aleksei Efimov
parent 5b1f960752
commit 4fdcb47304
3 changed files with 136 additions and 168 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,9 +25,9 @@
package sun.net.dns; package sun.net.dns;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.LinkedList; import java.util.concurrent.TimeUnit;
import java.util.StringTokenizer;
/* /*
* An implementation of sun.net.ResolverConfiguration for Windows. * An implementation of sun.net.ResolverConfiguration for Windows.
@ -42,38 +42,64 @@ public class ResolverConfigurationImpl
// Resolver options // Resolver options
private final Options opts; private final Options opts;
// Addresses have changed // Addresses have changed. We default to true to make sure we
private static boolean changed = false; // resolve the first time it is requested.
private static boolean changed = true;
// Time of last refresh. // Time of last refresh.
private static long lastRefresh = -1; private static long lastRefresh;
// Cache timeout (120 seconds) - should be converted into property // Cache timeout (120 seconds) - should be converted into property
// or configured as preference in the future. // or configured as preference in the future.
private static final int TIMEOUT = 120000; private static final long TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(120);
// DNS suffix list and name servers populated by native method // DNS suffix list and name servers populated by native method
private static String os_searchlist; private static String os_searchlist;
private static String os_nameservers; private static String os_nameservers;
// Cached lists // Cached lists
private static LinkedList<String> searchlist; private static ArrayList<String> searchlist;
private static LinkedList<String> nameservers; private static ArrayList<String> nameservers;
// Parse string that consists of token delimited by space or commas // Parse string that consists of token delimited by comma
// and return LinkedHashMap // and return ArrayList. Refer to ResolverConfigurationImpl.c and
private LinkedList<String> stringToList(String str) { // strappend to see how the string is created.
LinkedList<String> ll = new LinkedList<>(); private ArrayList<String> stringToList(String str) {
// String is delimited by comma.
// comma and space are valid delimiters String[] tokens = str.split(",");
StringTokenizer st = new StringTokenizer(str, ", "); ArrayList<String> l = new ArrayList<>(tokens.length);
while (st.hasMoreTokens()) { for (String s : tokens) {
String s = st.nextToken(); if (!s.isEmpty() && !l.contains(s)) {
if (!ll.contains(s)) { l.add(s);
ll.add(s);
} }
} }
return ll; l.trimToSize();
return l;
}
// Parse string that consists of token delimited by comma
// and return ArrayList. Refer to ResolverConfigurationImpl.c and
// strappend to see how the string is created.
// In addition to splitting the string, converts IPv6 addresses to
// BSD-style.
private ArrayList<String> addressesToList(String str) {
// String is delimited by comma
String[] tokens = str.split(",");
ArrayList<String> l = new ArrayList<>(tokens.length);
for (String s : tokens) {
if (!s.isEmpty()) {
if (s.indexOf(':') >= 0 && s.charAt(0) != '[') {
// Not BSD style
s = '[' + s + ']';
}
if (!s.isEmpty() && !l.contains(s)) {
l.add(s);
}
}
}
l.trimToSize();
return l;
} }
// Load DNS configuration from OS // Load DNS configuration from OS
@ -81,28 +107,34 @@ public class ResolverConfigurationImpl
private void loadConfig() { private void loadConfig() {
assert Thread.holdsLock(lock); assert Thread.holdsLock(lock);
// if address have changed then DNS probably changed as well; // A change in the network address of the machine usually indicates
// otherwise check if cached settings have expired. // a change in DNS configuration too so we always refresh the config
// // after such a change.
if (changed) { if (changed) {
changed = false; changed = false;
} else { } else {
if (lastRefresh >= 0) { // Otherwise we refresh if TIMEOUT_NANOS has passed since last
long currTime = System.currentTimeMillis(); // load.
if ((currTime - lastRefresh) < TIMEOUT) { long currTime = System.nanoTime();
// lastRefresh will always have been set once because we start with
// changed = true.
if ((currTime - lastRefresh) < TIMEOUT_NANOS) {
return; return;
} }
} }
}
// load DNS configuration, update timestamp, create // Native code that uses Windows API to find out the DNS server
// new HashMaps from the loaded configuration // addresses and search suffixes. It builds a comma-delimited string
// // of nameservers and domain suffixes and sets them to the static
// os_nameservers and os_searchlist. We then split these into Java
// Lists here.
loadDNSconfig0(); loadDNSconfig0();
lastRefresh = System.currentTimeMillis(); // Record the time of update and refresh the lists of addresses /
// domain suffixes.
lastRefresh = System.nanoTime();
searchlist = stringToList(os_searchlist); searchlist = stringToList(os_searchlist);
nameservers = stringToList(os_nameservers); nameservers = addressesToList(os_nameservers);
os_searchlist = null; // can be GC'ed os_searchlist = null; // can be GC'ed
os_nameservers = null; os_nameservers = null;
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -73,8 +73,8 @@ const int MAX_TRIES = 3;
* for each adapter on the system. Returned in *adapters. * for each adapter on the system. Returned in *adapters.
* Buffer is malloc'd and must be freed (unless error returned) * Buffer is malloc'd and must be freed (unless error returned)
*/ */
static int getAdapters (JNIEnv *env, IP_ADAPTER_ADDRESSES **adapters) { int getAdapters (JNIEnv *env, int flags, IP_ADAPTER_ADDRESSES **adapters) {
DWORD ret, flags; DWORD ret;
IP_ADAPTER_ADDRESSES *adapterInfo; IP_ADAPTER_ADDRESSES *adapterInfo;
ULONG len; ULONG len;
int try; int try;
@ -87,9 +87,6 @@ static int getAdapters (JNIEnv *env, IP_ADAPTER_ADDRESSES **adapters) {
} }
len = BUFF_SIZE; len = BUFF_SIZE;
flags = GAA_FLAG_SKIP_DNS_SERVER;
flags |= GAA_FLAG_SKIP_MULTICAST;
flags |= GAA_FLAG_INCLUDE_PREFIX;
ret = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapterInfo, &len); ret = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapterInfo, &len);
for (try = 0; ret == ERROR_BUFFER_OVERFLOW && try < MAX_TRIES; ++try) { for (try = 0; ret == ERROR_BUFFER_OVERFLOW && try < MAX_TRIES; ++try) {
@ -240,7 +237,7 @@ static int ipinflen = 2048;
*/ */
int getAllInterfacesAndAddresses (JNIEnv *env, netif **netifPP) int getAllInterfacesAndAddresses (JNIEnv *env, netif **netifPP)
{ {
DWORD ret; DWORD ret, flags;
MIB_IPADDRTABLE *tableP; MIB_IPADDRTABLE *tableP;
IP_ADAPTER_ADDRESSES *ptr, *adapters=NULL; IP_ADAPTER_ADDRESSES *ptr, *adapters=NULL;
ULONG len=ipinflen, count=0; ULONG len=ipinflen, count=0;
@ -296,7 +293,11 @@ int getAllInterfacesAndAddresses (JNIEnv *env, netif **netifPP)
} }
} }
free(tableP); free(tableP);
ret = getAdapters (env, &adapters);
flags = GAA_FLAG_SKIP_DNS_SERVER;
flags |= GAA_FLAG_SKIP_MULTICAST;
flags |= GAA_FLAG_INCLUDE_PREFIX;
ret = getAdapters (env, flags, &adapters);
if (ret != ERROR_SUCCESS) { if (ret != ERROR_SUCCESS) {
goto err; goto err;
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,17 +24,15 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <windows.h>
#include <stdio.h> #include <stdio.h>
#include <stddef.h> #include <stddef.h>
#include <iprtrmib.h>
#include <time.h> #include <time.h>
#include <assert.h> #include <assert.h>
#include <iphlpapi.h>
#include "net_util.h"
#include "jni_util.h" #include "jni_util.h"
#define MAX_STR_LEN 256 #define MAX_STR_LEN 1024
#define STS_NO_CONFIG 0x0 /* no configuration found */ #define STS_NO_CONFIG 0x0 /* no configuration found */
#define STS_SL_FOUND 0x1 /* search list found */ #define STS_SL_FOUND 0x1 /* search list found */
@ -48,9 +46,11 @@
static jfieldID searchlistID; static jfieldID searchlistID;
static jfieldID nameserversID; static jfieldID nameserversID;
extern int getAdapters(JNIEnv *env, int flags, IP_ADAPTER_ADDRESSES **adapters);
/* /*
* Utility routine to append s2 to s1 with a space delimiter. * Utility routine to append s2 to s1 with a comma delimiter.
* strappend(s1="abc", "def") => "abc def" * strappend(s1="abc", "def") => "abc,def"
* strappend(s1="", "def") => "def * strappend(s1="", "def") => "def
*/ */
void strappend(char *s1, char *s2) { void strappend(char *s1, char *s2) {
@ -60,41 +60,32 @@ void strappend(char *s1, char *s2) {
return; return;
len = strlen(s1)+1; len = strlen(s1)+1;
if (s1[0] != 0) /* needs space character */ if (s1[0] != 0) /* needs comma character */
len++; len++;
if (len + strlen(s2) > MAX_STR_LEN) /* insufficient space */ if (len + strlen(s2) > MAX_STR_LEN) /* insufficient space */
return; return;
if (s1[0] != 0) { if (s1[0] != 0) {
strcat(s1, " "); strcat(s1, ",");
} }
strcat(s1, s2); strcat(s1, s2);
} }
/* /*
* Windows 2000/XP * Use DNS server addresses returned by GetAdaptersAddresses for currently
* * active interfaces.
* Use registry approach based on settings described in Appendix C
* of "Microsoft Windows 2000 TCP/IP Implementation Details".
*
* DNS suffix list is obtained from SearchList registry setting. If
* this is not specified we compile suffix list based on the
* per-connection domain suffix.
*
* DNS name servers and domain settings are on a per-connection
* basic. We therefore enumerate the network adapters to get the
* names of each adapter and then query the corresponding registry
* settings to obtain NameServer/DhcpNameServer and Domain/DhcpDomain.
*/ */
static int loadConfig(char *sl, char *ns) { static int loadConfig(JNIEnv *env, char *sl, char *ns) {
IP_ADAPTER_INFO *adapterP; IP_ADAPTER_ADDRESSES *adapters, *adapter;
ULONG size; IP_ADAPTER_DNS_SERVER_ADDRESS *dnsServer;
DWORD ret; WCHAR *suffix;
DWORD ret, flags;
DWORD dwLen; DWORD dwLen;
ULONG ulType; ULONG ulType;
char result[MAX_STR_LEN]; char result[MAX_STR_LEN];
HANDLE hKey; HANDLE hKey;
int gotSearchList = 0; SOCKADDR *sockAddr;
struct sockaddr_in6 *sockAddrIpv6;
/* /*
* First see if there is a global suffix list specified. * First see if there is a global suffix list specified.
@ -112,128 +103,74 @@ static int loadConfig(char *sl, char *ns) {
assert(ulType == REG_SZ); assert(ulType == REG_SZ);
if (strlen(result) > 0) { if (strlen(result) > 0) {
strappend(sl, result); strappend(sl, result);
gotSearchList = 1;
} }
} }
RegCloseKey(hKey); RegCloseKey(hKey);
} }
/*
* Ask the IP Helper library to enumerate the adapters // We only need DNS server addresses so skip everything else.
*/ flags = GAA_FLAG_SKIP_UNICAST;
size = sizeof(IP_ADAPTER_INFO); flags |= GAA_FLAG_SKIP_ANYCAST;
adapterP = (IP_ADAPTER_INFO *)malloc(size); flags |= GAA_FLAG_SKIP_MULTICAST;
if (adapterP == NULL) { flags |= GAA_FLAG_SKIP_FRIENDLY_NAME;
ret = getAdapters(env, flags, &adapters);
if (ret != ERROR_SUCCESS) {
return STS_ERROR; return STS_ERROR;
} }
ret = GetAdaptersInfo(adapterP, &size);
if (ret == ERROR_BUFFER_OVERFLOW) {
IP_ADAPTER_INFO *newAdapterP = (IP_ADAPTER_INFO *)realloc(adapterP, size);
if (newAdapterP == NULL) {
free(adapterP);
return STS_ERROR;
}
adapterP = newAdapterP;
ret = GetAdaptersInfo(adapterP, &size); adapter = adapters;
while (adapter != NULL) {
// Only load config from enabled adapters.
if (adapter->OperStatus == IfOperStatusUp) {
dnsServer = adapter->FirstDnsServerAddress;
while (dnsServer != NULL) {
sockAddr = dnsServer->Address.lpSockaddr;
if (sockAddr->sa_family == AF_INET6) {
sockAddrIpv6 = (struct sockaddr_in6 *)sockAddr;
if (sockAddrIpv6->sin6_scope_id != 0) {
// An address with a scope is either link-local or
// site-local, which aren't valid for DNS queries so
// we can skip them.
dnsServer = dnsServer->Next;
continue;
}
} }
/*
* Iterate through the list of adapters as registry settings are
* keyed on the adapter name (GUID).
*/
if (ret == ERROR_SUCCESS) {
IP_ADAPTER_INFO *curr = adapterP;
while (curr != NULL) {
char key[MAX_STR_LEN];
sprintf(key,
"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\%s",
curr->AdapterName);
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
key,
0,
KEY_READ,
(PHKEY)&hKey);
if (ret == ERROR_SUCCESS) {
DWORD enableDhcp = 0;
/*
* Is DHCP enabled on this interface
*/
dwLen = sizeof(enableDhcp);
ret = RegQueryValueEx(hKey, "EnableDhcp", NULL, &ulType,
(LPBYTE)&enableDhcp, &dwLen);
/*
* If we don't have the suffix list when get the Domain
* or DhcpDomain. If DHCP is enabled then Domain overides
* DhcpDomain
*/
if (!gotSearchList) {
result[0] = '\0';
dwLen = sizeof(result); dwLen = sizeof(result);
ret = RegQueryValueEx(hKey, "Domain", NULL, &ulType, ret = WSAAddressToStringA(sockAddr,
(LPBYTE)&result, &dwLen); dnsServer->Address.iSockaddrLength, NULL,
if (((ret != ERROR_SUCCESS) || (strlen(result) == 0)) && result, &dwLen);
enableDhcp) { if (ret == 0) {
dwLen = sizeof(result);
ret = RegQueryValueEx(hKey, "DhcpDomain", NULL, &ulType,
(LPBYTE)&result, &dwLen);
}
if (ret == ERROR_SUCCESS) {
assert(ulType == REG_SZ);
strappend(sl, result);
}
}
/*
* Get DNS servers based on NameServer or DhcpNameServer
* registry setting. If NameServer is set then it overrides
* DhcpNameServer (even if DHCP is enabled).
*/
result[0] = '\0';
dwLen = sizeof(result);
ret = RegQueryValueEx(hKey, "NameServer", NULL, &ulType,
(LPBYTE)&result, &dwLen);
if (((ret != ERROR_SUCCESS) || (strlen(result) == 0)) &&
enableDhcp) {
dwLen = sizeof(result);
ret = RegQueryValueEx(hKey, "DhcpNameServer", NULL, &ulType,
(LPBYTE)&result, &dwLen);
}
if (ret == ERROR_SUCCESS) {
assert(ulType == REG_SZ);
strappend(ns, result); strappend(ns, result);
} }
/* dnsServer = dnsServer->Next;
* Finished with this registry key
*/
RegCloseKey(hKey);
} }
/* // Add connection-specific search domains in addition to global one.
* Onto the next adapeter suffix = adapter->DnsSuffix;
*/ if (suffix != NULL) {
curr = curr->Next; ret = WideCharToMultiByte(CP_UTF8, 0, suffix, -1,
result, sizeof(result), NULL, NULL);
if (ret != 0) {
strappend(sl, result);
}
} }
} }
/* adapter = adapter->Next;
* Free the adpater structure
*/
if (adapterP) {
free(adapterP);
} }
free(adapters);
return STS_SL_FOUND & STS_NS_FOUND; return STS_SL_FOUND & STS_NS_FOUND;
} }
/* /*
* Initialize JNI field IDs. * Initialize JNI field IDs and classes.
*/ */
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_sun_net_dns_ResolverConfigurationImpl_init0(JNIEnv *env, jclass cls) Java_sun_net_dns_ResolverConfigurationImpl_init0(JNIEnv *env, jclass cls)
@ -260,7 +197,7 @@ Java_sun_net_dns_ResolverConfigurationImpl_loadDNSconfig0(JNIEnv *env, jclass cl
searchlist[0] = '\0'; searchlist[0] = '\0';
nameservers[0] = '\0'; nameservers[0] = '\0';
if (loadConfig(searchlist, nameservers) != STS_ERROR) { if (loadConfig(env, searchlist, nameservers) != STS_ERROR) {
/* /*
* Populate static fields in sun.net.DefaultResolverConfiguration * Populate static fields in sun.net.DefaultResolverConfiguration
@ -272,8 +209,6 @@ Java_sun_net_dns_ResolverConfigurationImpl_loadDNSconfig0(JNIEnv *env, jclass cl
obj = (*env)->NewStringUTF(env, nameservers); obj = (*env)->NewStringUTF(env, nameservers);
CHECK_NULL(obj); CHECK_NULL(obj);
(*env)->SetStaticObjectField(env, cls, nameserversID, obj); (*env)->SetStaticObjectField(env, cls, nameserversID, obj);
} else {
JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
} }
} }