939e928312
Motivation: On MacOS it is not really good enough to check /etc/resolv.conf to determine the nameservers to use. We should retrieve the nameservers using the same way as mDNSResponser and chromium does by doing a JNI call. Modifications: Add MacOSDnsServerAddressStreamProvider and testcase Result: Use correct nameservers by default on MacOS.
184 lines
7.0 KiB
Java
184 lines
7.0 KiB
Java
/*
|
|
* Copyright 2019 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
package io.netty.resolver.dns.macos;
|
|
|
|
import io.netty.resolver.dns.DnsServerAddressStream;
|
|
import io.netty.resolver.dns.DnsServerAddressStreamProvider;
|
|
import io.netty.resolver.dns.DnsServerAddressStreamProviders;
|
|
import io.netty.resolver.dns.DnsServerAddresses;
|
|
import io.netty.util.internal.NativeLibraryLoader;
|
|
import io.netty.util.internal.PlatformDependent;
|
|
import io.netty.util.internal.StringUtil;
|
|
import io.netty.util.internal.SystemPropertyUtil;
|
|
import io.netty.util.internal.ThrowableUtil;
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
|
|
|
import java.net.InetSocketAddress;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
|
/**
|
|
* {@link DnsServerAddressStreamProvider} implementation which makes use of the same mechanism as
|
|
* <a href="https://opensource.apple.com/tarballs/mDNSResponder/">Apple's open source mDNSResponder</a> to retrieve the
|
|
* current nameserver configuration of the system.
|
|
*/
|
|
public final class MacOSDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
|
|
|
|
// Fallback provider
|
|
private static final DnsServerAddressStreamProvider DEFAULT_PROVIDER =
|
|
DnsServerAddressStreamProviders.platformDefault();
|
|
|
|
private static final Throwable UNAVAILABILITY_CAUSE;
|
|
|
|
private static final InternalLogger logger =
|
|
InternalLoggerFactory.getInstance(MacOSDnsServerAddressStreamProvider.class);
|
|
|
|
// Let's refresh every 10 seconds.
|
|
private static final long REFRESH_INTERVAL = TimeUnit.SECONDS.toNanos(10);
|
|
|
|
static {
|
|
Throwable cause = null;
|
|
try {
|
|
loadNativeLibrary();
|
|
} catch (Throwable error) {
|
|
cause = error;
|
|
}
|
|
UNAVAILABILITY_CAUSE = cause;
|
|
}
|
|
|
|
private static void loadNativeLibrary() {
|
|
String name = SystemPropertyUtil.get("os.name").toLowerCase(Locale.UK).trim();
|
|
if (!name.startsWith("mac")) {
|
|
throw new IllegalStateException("Only supported on MacOS");
|
|
}
|
|
String staticLibName = "netty_resolver_dns_native_macos";
|
|
String sharedLibName = staticLibName + '_' + PlatformDependent.normalizedArch();
|
|
ClassLoader cl = PlatformDependent.getClassLoader(MacOSDnsServerAddressStreamProvider.class);
|
|
try {
|
|
NativeLibraryLoader.load(sharedLibName, cl);
|
|
} catch (UnsatisfiedLinkError e1) {
|
|
try {
|
|
NativeLibraryLoader.load(staticLibName, cl);
|
|
logger.debug("Failed to load {}", sharedLibName, e1);
|
|
} catch (UnsatisfiedLinkError e2) {
|
|
ThrowableUtil.addSuppressed(e1, e2);
|
|
throw e1;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static boolean isAvailable() {
|
|
return UNAVAILABILITY_CAUSE == null;
|
|
}
|
|
|
|
public static void ensureAvailability() {
|
|
if (UNAVAILABILITY_CAUSE != null) {
|
|
throw (Error) new UnsatisfiedLinkError(
|
|
"failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
|
|
}
|
|
}
|
|
|
|
public static Throwable unavailabilityCause() {
|
|
return UNAVAILABILITY_CAUSE;
|
|
}
|
|
|
|
public MacOSDnsServerAddressStreamProvider() {
|
|
ensureAvailability();
|
|
}
|
|
|
|
private volatile Map<String, DnsServerAddresses> currentMappings = retrieveCurrentMappings();
|
|
private final AtomicLong lastRefresh = new AtomicLong(System.nanoTime());
|
|
|
|
private static Map<String, DnsServerAddresses> retrieveCurrentMappings() {
|
|
DnsResolver[] resolvers = resolvers();
|
|
|
|
if (resolvers == null || resolvers.length == 0) {
|
|
return Collections.emptyMap();
|
|
}
|
|
Map<String, DnsServerAddresses> resolverMap = new HashMap<String, DnsServerAddresses>(resolvers.length);
|
|
for (DnsResolver resolver: resolvers) {
|
|
// Skip mdns
|
|
if ("mdns".equalsIgnoreCase(resolver.options())) {
|
|
continue;
|
|
}
|
|
InetSocketAddress[] nameservers = resolver.nameservers();
|
|
if (nameservers == null || nameservers.length == 0) {
|
|
continue;
|
|
}
|
|
String domain = resolver.domain();
|
|
if (domain == null) {
|
|
// Default mapping.
|
|
domain = StringUtil.EMPTY_STRING;
|
|
}
|
|
InetSocketAddress[] servers = resolver.nameservers();
|
|
for (int a = 0; a < servers.length; a++) {
|
|
InetSocketAddress address = servers[a];
|
|
// Check if the default port should be used
|
|
if (address.getPort() == 0) {
|
|
int port = resolver.port();
|
|
if (port == 0) {
|
|
port = 53;
|
|
}
|
|
servers[a] = new InetSocketAddress(address.getAddress(), port);
|
|
}
|
|
}
|
|
|
|
resolverMap.put(domain, DnsServerAddresses.sequential(servers));
|
|
}
|
|
return resolverMap;
|
|
}
|
|
|
|
@Override
|
|
public DnsServerAddressStream nameServerAddressStream(String hostname) {
|
|
long last = lastRefresh.get();
|
|
Map<String, DnsServerAddresses> resolverMap = currentMappings;
|
|
if (System.nanoTime() - last > REFRESH_INTERVAL) {
|
|
// This is slightly racy which means it will be possible still use the old configuration for a small
|
|
// amount of time, but that's ok.
|
|
if (lastRefresh.compareAndSet(last, System.nanoTime())) {
|
|
resolverMap = currentMappings = retrieveCurrentMappings();
|
|
}
|
|
}
|
|
|
|
final String originalHostname = hostname;
|
|
for (;;) {
|
|
int i = hostname.indexOf('.', 1);
|
|
if (i < 0 || i == hostname.length() - 1) {
|
|
// Try access default mapping.
|
|
DnsServerAddresses addresses = resolverMap.get(StringUtil.EMPTY_STRING);
|
|
if (addresses != null) {
|
|
return addresses.stream();
|
|
}
|
|
return DEFAULT_PROVIDER.nameServerAddressStream(originalHostname);
|
|
}
|
|
|
|
DnsServerAddresses addresses = resolverMap.get(hostname);
|
|
if (addresses != null) {
|
|
return addresses.stream();
|
|
}
|
|
|
|
hostname = hostname.substring(i + 1);
|
|
}
|
|
}
|
|
|
|
private static native DnsResolver[] resolvers();
|
|
}
|