diff --git a/NOTICE.txt b/NOTICE.txt index f9888e61e1..9cf3e812ad 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -237,3 +237,12 @@ This product contains the Maven wrapper scripts from 'Maven Wrapper', that provi * license/LICENSE.mvn-wrapper.txt (Apache License 2.0) * HOMEPAGE: * https://github.com/takari/maven-wrapper + +This product contains the dnsinfo.h header file, that provides a way to retrieve the system DNS configuration on MacOS. +This private header is also used by Apple's open source + mDNSResponder (https://opensource.apple.com/tarballs/mDNSResponder/). + + * LICENSE: + * license/LICENSE.dnsinfo.txt (Apache License 2.0) + * HOMEPAGE: + * http://www.opensource.apple.com/source/configd/configd-453.19/dnsinfo/dnsinfo.h \ No newline at end of file diff --git a/all/pom.xml b/all/pom.xml index da260d80ad..e6fecec1bb 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -65,6 +65,14 @@ compile true + + ${project.groupId} + netty-resolver-dns-native-macos + ${project.version} + osx-x86_64 + compile + true + @@ -89,6 +97,14 @@ compile true + + ${project.groupId} + netty-resolver-dns-native-macos + ${project.version} + osx-x86_64 + compile + true + @@ -120,6 +136,13 @@ compile true + + ${project.groupId} + netty-resolver-dns-native-macos + ${project.version} + compile + true + ${project.groupId} diff --git a/license/LICENSE.dnsinfo.txt b/license/LICENSE.dnsinfo.txt new file mode 100644 index 0000000000..7554838ad7 --- /dev/null +++ b/license/LICENSE.dnsinfo.txt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2004-2006, 2008, 2009, 2011 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ diff --git a/pom.xml b/pom.xml index b37283315d..11098725fe 100644 --- a/pom.xml +++ b/pom.xml @@ -352,6 +352,7 @@ codec-xml resolver resolver-dns + resolver-dns-native-macos tarball transport transport-native-unix-common-tests @@ -713,6 +714,7 @@ 0.13.1 + true true true \d+\.\d+\.\d+\.Final diff --git a/resolver-dns-native-macos/pom.xml b/resolver-dns-native-macos/pom.xml new file mode 100644 index 0000000000..6b7558ab94 --- /dev/null +++ b/resolver-dns-native-macos/pom.xml @@ -0,0 +1,185 @@ + + + + 4.0.0 + + io.netty + netty-parent + 5.0.0.Final-SNAPSHOT + + netty-resolver-dns-native-macos + + Netty/Resolver/DNS/MacOS + jar + + + + mac + + + mac + + + + LDFLAGS=-Wl,-weak_library,${unix.common.lib.unpacked.dir}/lib${unix.common.lib.name}.a + false + + + + + maven-dependency-plugin + + + + unpack + generate-sources + + unpack-dependencies + + + ${project.groupId} + netty-transport-native-unix-common + ${jni.classifier} + ${unix.common.lib.dir} + META-INF/native/** + false + true + + + + + + + org.fusesource.hawtjni + maven-hawtjni-plugin + + + build-native-lib + + netty_resolver_dns_native_macos_${os.detected.arch} + ${project.basedir}/src/main/c + ${project.build.outputDirectory} + + . + + ${jni.compiler.args.ldflags} + ${jni.compiler.args.cflags} + + + + generate + build + + + + + + + maven-jar-plugin + + + + native-jar + + jar + + + + + true + + + META-INF/native/libnetty_resolver_dns_native_macos_${os.detected.arch}.jnilib; osname=MacOSX, processor=${os.detected.arch}" + ${javaModuleName} + + true + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + ${jni.classifier} + + + + + + + + + io.netty + netty-transport-native-unix-common + ${project.version} + ${jni.classifier} + + true + + + + + + + io.netty.resolver.dns.macos + + netty-unix-common + ${project.build.directory}/unix-common-lib + ${unix.common.lib.dir}/META-INF/native/lib + ${unix.common.lib.dir}/META-INF/native/include + CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -I${unix.common.include.unpacked.dir} + LDFLAGS=-z now -L${unix.common.lib.unpacked.dir} -Wl,--whole-archive -l${unix.common.lib.name} -Wl,--no-whole-archive + + + + + io.netty + netty-common + ${project.version} + + + io.netty + netty-resolver-dns + ${project.version} + + + io.netty + netty-transport-native-unix-common + ${project.version} + + + + + + + maven-jar-plugin + + + + default-jar + + + META-INF/native/** + + + + + + + + + diff --git a/resolver-dns-native-macos/src/main/c/dnsinfo.h b/resolver-dns-native-macos/src/main/c/dnsinfo.h new file mode 100644 index 0000000000..8b10e8d4c6 --- /dev/null +++ b/resolver-dns-native-macos/src/main/c/dnsinfo.h @@ -0,0 +1,106 @@ +/* + * 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. + */ +/* + * Copyright (c) 2004-2006, 2008, 2009, 2011 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef __DNSINFO_H__ +#define __DNSINFO_H__ +/* + * These routines provide access to the systems DNS configuration + */ +#include +#include +#include +#include +#include +#define DNSINFO_VERSION 20111104 +#define DEFAULT_SEARCH_ORDER 200000 /* search order for the "default" resolver domain name */ +#define DNS_PTR(type, name) \ + union { \ + type name; \ + uint64_t _ ## name ## _p; \ + } +#define DNS_VAR(type, name) \ + type name +#pragma pack(4) +typedef struct { + struct in_addr address; + struct in_addr mask; +} dns_sortaddr_t; +#pragma pack() +#pragma pack(4) +typedef struct { + DNS_PTR(char *, domain); /* domain */ + DNS_VAR(int32_t, n_nameserver); /* # nameserver */ + DNS_PTR(struct sockaddr **, nameserver); + DNS_VAR(uint16_t, port); /* port (in host byte order) */ + DNS_VAR(int32_t, n_search); /* # search */ + DNS_PTR(char **, search); + DNS_VAR(int32_t, n_sortaddr); /* # sortaddr */ + DNS_PTR(dns_sortaddr_t **, sortaddr); + DNS_PTR(char *, options); /* options */ + DNS_VAR(uint32_t, timeout); /* timeout */ + DNS_VAR(uint32_t, search_order); /* search_order */ + DNS_VAR(uint32_t, if_index); + DNS_VAR(uint32_t, flags); + DNS_VAR(uint32_t, reach_flags); /* SCNetworkReachabilityFlags */ + DNS_VAR(uint32_t, reserved[5]); +} dns_resolver_t; +#pragma pack() +#define DNS_RESOLVER_FLAGS_SCOPED 1 /* configuration is for scoped questions */ +#pragma pack(4) +typedef struct { + DNS_VAR(int32_t, n_resolver); /* resolver configurations */ + DNS_PTR(dns_resolver_t **, resolver); + DNS_VAR(int32_t, n_scoped_resolver); /* "scoped" resolver configurations */ + DNS_PTR(dns_resolver_t **, scoped_resolver); + DNS_VAR(uint32_t, reserved[5]); +} dns_config_t; +#pragma pack() +__BEGIN_DECLS +/* + * DNS configuration access APIs + */ +const char * +dns_configuration_notify_key (); +dns_config_t * +dns_configuration_copy (); +void +dns_configuration_free (dns_config_t *config); +void +_dns_configuration_ack (dns_config_t *config, + const char *bundle_id); +__END_DECLS +#endif /* __DNSINFO_H__ */ \ No newline at end of file diff --git a/resolver-dns-native-macos/src/main/c/netty_resolver_dns_macos.c b/resolver-dns-native-macos/src/main/c/netty_resolver_dns_macos.c new file mode 100644 index 0000000000..68df75c6c1 --- /dev/null +++ b/resolver-dns-native-macos/src/main/c/netty_resolver_dns_macos.c @@ -0,0 +1,263 @@ +/* + * 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. + */ +#include +#include +#include +#include +#include +#include +#include "dnsinfo.h" +#include "netty_unix_jni.h" +#include "netty_unix_util.h" +#include "netty_unix_socket.h" +#include "netty_unix_errors.h" + +static jclass dnsResolverClass = NULL; +static jclass byteArrayClass = NULL; +static jclass stringClass = NULL; +static jmethodID dnsResolverMethodId = NULL; + +// JNI Registered Methods Begin + +// We use the same API as mDNSResponder and Chromium to retrieve the current nameserver configuration for the system: +// See: +// https://src.chromium.org/viewvc/chrome?revision=218617&view=revision +// https://opensource.apple.com/tarballs/mDNSResponder/ +static jobjectArray netty_resolver_dns_macos_resolvers(JNIEnv* env, jclass clazz) { + dns_config_t* config = dns_configuration_copy(); + + jobjectArray array = (*env)->NewObjectArray(env, config->n_resolver, dnsResolverClass, NULL); + if (array == NULL) { + goto error; + } + + for (int i = 0; i < config->n_resolver; i++) { + dns_resolver_t* resolver = config->resolver[i]; + jstring domain = NULL; + + if (resolver->domain != NULL) { + domain = (*env)->NewStringUTF(env, resolver->domain); + if (domain == NULL) { + goto error; + } + } + + jobjectArray addressArray = (*env)->NewObjectArray(env, resolver->n_nameserver, byteArrayClass, NULL); + if (addressArray == NULL) { + goto error; + } + + for (int a = 0; a < resolver->n_nameserver; a++) { + jbyteArray address = netty_unix_socket_createInetSocketAddressArray(env, (const struct sockaddr_storage *) resolver->nameserver[a]); + if (address == NULL) { + netty_unix_errors_throwOutOfMemoryError(env); + goto error; + } + (*env)->SetObjectArrayElement(env, addressArray, a, address); + } + + jint port = resolver->port; + + jobjectArray searchArray = (*env)->NewObjectArray(env, resolver->n_search, stringClass, NULL); + if (searchArray == NULL) { + goto error; + } + + for (int a = 0; a < resolver->n_search; a++) { + jstring search = (*env)->NewStringUTF(env, resolver->search[a]); + if (search == NULL) { + goto error; + } + + (*env)->SetObjectArrayElement(env, searchArray, a, search); + } + + jstring options = NULL; + if (resolver->options != NULL) { + options = (*env)->NewStringUTF(env, resolver->options); + if (options == NULL) { + goto error; + } + } + + jint timeout = resolver->timeout; + jint searchOrder = resolver->search_order; + + jobject java_resolver = (*env)->NewObject(env, dnsResolverClass, dnsResolverMethodId, domain, + addressArray, port, searchArray, options, timeout, searchOrder); + if (java_resolver == NULL) { + goto error; + } + (*env)->SetObjectArrayElement(env, array, i, java_resolver); + } + + dns_configuration_free(config); + return array; +error: + dns_configuration_free(config); + return NULL; +} + + +// JNI Method Registration Table Begin + +static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) { + JNINativeMethod* dynamicMethods = malloc(sizeof(JNINativeMethod) * 1); + + char* dynamicTypeName = netty_unix_util_prepend(packagePrefix, "io/netty/resolver/dns/macos/DnsResolver;"); + JNINativeMethod* dynamicMethod = &dynamicMethods[0]; + dynamicMethod->name = "resolvers"; + dynamicMethod->signature = netty_unix_util_prepend("()[L", dynamicTypeName); + dynamicMethod->fnPtr = (void *) netty_resolver_dns_macos_resolvers; + free(dynamicTypeName); + return dynamicMethods; +} + +static void freeDynamicMethodsTable(JNINativeMethod* dynamicMethods) { + free(dynamicMethods[0].signature); + free(dynamicMethods); +} + +// JNI Method Registration Table End + + +static void JNI_OnUnload_netty_resolver_dns_native_macos0(JavaVM* vm, void* reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_VERSION) != JNI_OK) { + // Something is wrong but nothing we can do about this :( + return; + } + + if (byteArrayClass != NULL) { + (*env)->DeleteGlobalRef(env, byteArrayClass); + byteArrayClass = NULL; + } + + if (stringClass != NULL) { + (*env)->DeleteGlobalRef(env, stringClass); + stringClass = NULL; + } +} + +static void netty_resolver_dns_native_macos0_OnUnLoad(JNIEnv* env) { + +} + +static jint JNI_OnLoad_netty_resolver_dns_native_macos0(JavaVM* vm, void* reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, NETTY_JNI_VERSION) != JNI_OK) { + return JNI_ERR; + } + +#ifndef NETTY_BUILD_STATIC + Dl_info dlinfo; + jint status = 0; + // We need to use an address of a function that is uniquely part of this library, so choose a static + // function. See https://github.com/netty/netty/issues/4840. + if (!dladdr((void*) netty_resolver_dns_native_macos0_OnUnLoad, &dlinfo)) { + fprintf(stderr, "FATAL: resolver-dns-native-macos JNI call to dladdr failed!\n"); + return JNI_ERR; + } + char* packagePrefix = netty_unix_util_parse_package_prefix(dlinfo.dli_fname, "netty_resolver_dns_native_macos", &status); + if (status == JNI_ERR) { + fprintf(stderr, "FATAL: resolver-dns-native-macos JNI encountered unexpected dlinfo.dli_fname: %s\n", dlinfo.dli_fname); + return JNI_ERR; + } +#endif /* NETTY_BUILD_STATIC */ + + // Register the methods which are not referenced by static member variables + JNINativeMethod* dynamicMethods = createDynamicMethodsTable(packagePrefix); + if (netty_unix_util_register_natives(env, + packagePrefix, + "io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProvider", + dynamicMethods, 1) != 0) { + freeDynamicMethodsTable(dynamicMethods); + fprintf(stderr, "FATAL: Couldnt register natives"); + + return JNI_ERR; + } + freeDynamicMethodsTable(dynamicMethods); + dynamicMethods = NULL; + + + char* nettyClassName = netty_unix_util_prepend(packagePrefix, "io/netty/resolver/dns/macos/DnsResolver"); + jclass localDnsResolverClass = (*env)->FindClass(env, nettyClassName); + free(nettyClassName); + nettyClassName = NULL; + if (localDnsResolverClass == NULL) { + // pending exception... + return JNI_ERR; + } + dnsResolverClass = (jclass) (*env)->NewGlobalRef(env, localDnsResolverClass); + if (dnsResolverClass == NULL) { + return JNI_ERR; + } + dnsResolverMethodId = (*env)->GetMethodID(env, dnsResolverClass, "", "(Ljava/lang/String;[[BI[Ljava/lang/String;Ljava/lang/String;II)V"); + if (dnsResolverMethodId == NULL) { + netty_unix_errors_throwRuntimeException(env, "failed to get method ID: DnsResolver.(String, byte[][], String[], String, int, int)"); + return JNI_ERR; + } + + if (packagePrefix != NULL) { + free(packagePrefix); + packagePrefix = NULL; + } + + jclass byteArrayCls = (*env)->FindClass(env, "[B"); + if (byteArrayCls == NULL) { + // pending exception... + return JNI_ERR; + } + byteArrayClass = (jclass) (*env)->NewGlobalRef(env, byteArrayCls); + if (byteArrayClass == NULL) { + return JNI_ERR; + } + + jclass stringCls = (*env)->FindClass(env, "java/lang/String"); + if (stringCls == NULL) { + // pending exception... + return JNI_ERR; + } + stringClass = (jclass) (*env)->NewGlobalRef(env, stringCls); + if (stringClass == NULL) { + return JNI_ERR; + } + + return NETTY_JNI_VERSION; +} + +// We build with -fvisibility=hidden so ensure we mark everything that needs to be visible with JNIEXPORT +// http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-February/014549.html + +// Invoked by the JVM when statically linked +JNIEXPORT jint JNI_OnLoad_netty_resolver_dns_native_macos(JavaVM* vm, void* reserved) { + return JNI_OnLoad_netty_resolver_dns_native_macos0(vm, reserved); +} + +// Invoked by the JVM when statically linked +JNIEXPORT void JNI_OnUnload_netty_resolver_dns_native_macos(JavaVM* vm, void* reserved) { + JNI_OnUnload_netty_resolver_dns_native_macos0(vm, reserved); +} + +#ifndef NETTY_BUILD_STATIC +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return JNI_OnLoad_netty_resolver_dns_native_macos0(vm, reserved); +} + +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) { + return JNI_OnUnload_netty_resolver_dns_native_macos0(vm, reserved); +} +#endif /* NETTY_BUILD_STATIC */ diff --git a/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/DnsResolver.java b/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/DnsResolver.java new file mode 100644 index 0000000000..9744c658cb --- /dev/null +++ b/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/DnsResolver.java @@ -0,0 +1,81 @@ +/* + * 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.channel.unix.NativeInetAddress; + +import java.net.InetSocketAddress; + +/** + * Represent the {@code dns_resolver_t} struct. + */ +final class DnsResolver { + + private final String domain; + private final InetSocketAddress[] nameservers; + private final int port; + private final String[] searches; + private final String options; + private final int timeout; + private final int searchOrder; + + DnsResolver(String domain, byte[][] nameservers, int port, + String[] searches, String options, int timeout, int searchOrder) { + this.domain = domain; + if (nameservers == null) { + this.nameservers = new InetSocketAddress[0]; + } else { + this.nameservers = new InetSocketAddress[nameservers.length]; + for (int i = 0; i < nameservers.length; i++) { + byte[] addr = nameservers[i]; + this.nameservers[i] = NativeInetAddress.address(addr, 0, addr.length); + } + } + this.port = port; + this.searches = searches; + this.options = options; + this.timeout = timeout; + this.searchOrder = searchOrder; + } + + String domain() { + return domain; + } + + InetSocketAddress[] nameservers() { + return nameservers; + } + + int port() { + return port; + } + + String[] searches() { + return searches; + } + + String options() { + return options; + } + + int timeout() { + return timeout; + } + + int searchOrder() { + return searchOrder; + } +} diff --git a/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProvider.java b/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProvider.java new file mode 100644 index 0000000000..f82f571511 --- /dev/null +++ b/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProvider.java @@ -0,0 +1,183 @@ +/* + * 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 + * Apple's open source mDNSResponder 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 currentMappings = retrieveCurrentMappings(); + private final AtomicLong lastRefresh = new AtomicLong(System.nanoTime()); + + private static Map retrieveCurrentMappings() { + DnsResolver[] resolvers = resolvers(); + + if (resolvers == null || resolvers.length == 0) { + return Collections.emptyMap(); + } + Map resolverMap = new HashMap(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 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(); +} diff --git a/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/package-info.java b/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/package-info.java new file mode 100644 index 0000000000..d12eeb90a3 --- /dev/null +++ b/resolver-dns-native-macos/src/main/java/io/netty/resolver/dns/macos/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * MacOS specific nameserver resolution. + */ +@UnstableApi +package io.netty.resolver.dns.macos; + +import io.netty.util.internal.UnstableApi; diff --git a/resolver-dns-native-macos/src/test/java/io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProviderTest.java b/resolver-dns-native-macos/src/test/java/io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProviderTest.java new file mode 100644 index 0000000000..aafec23727 --- /dev/null +++ b/resolver-dns-native-macos/src/test/java/io/netty/resolver/dns/macos/MacOSDnsServerAddressStreamProviderTest.java @@ -0,0 +1,43 @@ +/* + * 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 org.junit.Assert; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; + +public class MacOSDnsServerAddressStreamProviderTest { + + @BeforeClass + public static void assume() { + Assume.assumeTrue(MacOSDnsServerAddressStreamProvider.isAvailable()); + } + + @Test + public void test() { + DnsServerAddressStreamProvider provider = new MacOSDnsServerAddressStreamProvider(); + DnsServerAddressStream stream = provider.nameServerAddressStream("netty.io"); + Assert.assertNotNull(stream); + Assert.assertNotEquals(0, stream.size()); + + for (int i = 0; i < stream.size(); i++) { + Assert.assertNotEquals(0, stream.next().getPort()); + } + } +} diff --git a/tarball/pom.xml b/tarball/pom.xml index e2d8387c2c..2bbd0613f7 100644 --- a/tarball/pom.xml +++ b/tarball/pom.xml @@ -123,6 +123,14 @@ compile true + + ${project.groupId} + netty-resolver-dns-native-macos + ${project.version} + osx-x86_64 + compile + true + @@ -145,6 +153,14 @@ compile true + + ${project.groupId} + netty-resolver-dns-native-macos + ${project.version} + osx-x86_64 + compile + true + diff --git a/transport-native-unix-common/src/main/c/netty_unix_socket.c b/transport-native-unix-common/src/main/c/netty_unix_socket.c index 5a50efaaa6..8303e23f42 100644 --- a/transport-native-unix-common/src/main/c/netty_unix_socket.c +++ b/transport-native-unix-common/src/main/c/netty_unix_socket.c @@ -173,7 +173,7 @@ static void initInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storag } } -static jbyteArray createInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr) { +jbyteArray netty_unix_socket_createInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr) { jsize len = addressLength(addr); jbyteArray bArray = (*env)->NewByteArray(env, len); if (bArray == NULL) { @@ -587,7 +587,7 @@ static jbyteArray netty_unix_socket_remoteAddress(JNIEnv* env, jclass clazz, jin if (getpeername(fd, (struct sockaddr*) &addr, &len) == -1) { return NULL; } - return createInetSocketAddressArray(env, &addr); + return netty_unix_socket_createInetSocketAddressArray(env, &addr); } static jbyteArray netty_unix_socket_localAddress(JNIEnv* env, jclass clazz, jint fd) { @@ -596,7 +596,7 @@ static jbyteArray netty_unix_socket_localAddress(JNIEnv* env, jclass clazz, jint if (getsockname(fd, (struct sockaddr*) &addr, &len) == -1) { return NULL; } - return createInetSocketAddressArray(env, &addr); + return netty_unix_socket_createInetSocketAddressArray(env, &addr); } static jint netty_unix_socket_newSocketDgramFd(JNIEnv* env, jclass clazz, jboolean ipv6) { diff --git a/transport-native-unix-common/src/main/c/netty_unix_socket.h b/transport-native-unix-common/src/main/c/netty_unix_socket.h index e24de06b54..96d8794bc2 100644 --- a/transport-native-unix-common/src/main/c/netty_unix_socket.h +++ b/transport-native-unix-common/src/main/c/netty_unix_socket.h @@ -21,6 +21,8 @@ // External C methods int netty_unix_socket_initSockaddr(JNIEnv* env, jboolean ipv6, jbyteArray address, jint scopeId, jint jport, const struct sockaddr_storage* addr, socklen_t* addrSize); +jbyteArray netty_unix_socket_createInetSocketAddressArray(JNIEnv* env, const struct sockaddr_storage* addr); + int netty_unix_socket_getOption(JNIEnv* env, jint fd, int level, int optname, void* optval, socklen_t optlen); int netty_unix_socket_setOption(JNIEnv* env, jint fd, int level, int optname, const void* optval, socklen_t len); int netty_unix_socket_ipAddressLength(const struct sockaddr_storage* addr);