View Javadoc
1   /*
2    * Copyright 2019 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.resolver.dns.macos;
17  
18  import io.netty.resolver.dns.DnsServerAddressStream;
19  import io.netty.resolver.dns.DnsServerAddressStreamProvider;
20  import io.netty.resolver.dns.DnsServerAddressStreamProviders;
21  import io.netty.resolver.dns.DnsServerAddresses;
22  import io.netty.util.internal.ClassInitializerUtil;
23  import io.netty.util.internal.NativeLibraryLoader;
24  import io.netty.util.internal.PlatformDependent;
25  import io.netty.util.internal.StringUtil;
26  import io.netty.util.internal.ThrowableUtil;
27  import io.netty.util.internal.logging.InternalLogger;
28  import io.netty.util.internal.logging.InternalLoggerFactory;
29  
30  import java.net.InetSocketAddress;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.Comparator;
34  import java.util.HashMap;
35  import java.util.Map;
36  import java.util.concurrent.TimeUnit;
37  import java.util.concurrent.atomic.AtomicLong;
38  
39  /**
40   * {@link DnsServerAddressStreamProvider} implementation which makes use of the same mechanism as
41   * <a href="https://opensource.apple.com/tarballs/mDNSResponder/">Apple's open source mDNSResponder</a> to retrieve the
42   * current nameserver configuration of the system.
43   */
44  public final class MacOSDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
45  
46      private static final Comparator<DnsResolver> RESOLVER_COMPARATOR =
47              new Comparator<DnsResolver>() {
48                  @Override
49                  public int compare(DnsResolver r1, DnsResolver r2) {
50                      // Note: order is descending (from higher to lower) so entries with lower search order override
51                      // entries with higher search order.
52                      return r1.searchOrder() < r2.searchOrder() ? 1 : r1.searchOrder() == r2.searchOrder() ? 0 : -1;
53                  }
54              };
55  
56      private static final Throwable UNAVAILABILITY_CAUSE;
57  
58      private static final InternalLogger logger =
59              InternalLoggerFactory.getInstance(MacOSDnsServerAddressStreamProvider.class);
60  
61      // Let's refresh every 10 seconds.
62      private static final long REFRESH_INTERVAL = TimeUnit.SECONDS.toNanos(10);
63  
64      static {
65          // Preload all classes that will be used in the OnLoad(...) function of JNI to eliminate the possiblity of a
66          // class-loader deadlock. This is a workaround for https://github.com/netty/netty/issues/11209.
67  
68          // This needs to match all the classes that are loaded via NETTY_JNI_UTIL_LOAD_CLASS or looked up via
69          // NETTY_JNI_UTIL_FIND_CLASS.
70          ClassInitializerUtil.tryLoadClasses(MacOSDnsServerAddressStreamProvider.class,
71                  // netty_resolver_dns_macos
72                  byte[].class, String.class
73          );
74  
75          Throwable cause = null;
76          try {
77              loadNativeLibrary();
78          } catch (Throwable error) {
79              cause = error;
80          }
81          UNAVAILABILITY_CAUSE = cause;
82      }
83  
84      private static void loadNativeLibrary() {
85          if (!PlatformDependent.isOsx()) {
86              throw new IllegalStateException("Only supported on MacOS/OSX");
87          }
88          String staticLibName = "netty_resolver_dns_native_macos";
89          String sharedLibName = staticLibName + '_' + PlatformDependent.normalizedArch();
90          ClassLoader cl = PlatformDependent.getClassLoader(MacOSDnsServerAddressStreamProvider.class);
91          try {
92              NativeLibraryLoader.load(sharedLibName, cl);
93          } catch (UnsatisfiedLinkError e1) {
94              try {
95                  NativeLibraryLoader.load(staticLibName, cl);
96                  logger.debug("Failed to load {}", sharedLibName, e1);
97              } catch (UnsatisfiedLinkError e2) {
98                  ThrowableUtil.addSuppressed(e1, e2);
99                  throw e1;
100             }
101         }
102     }
103 
104     public static boolean isAvailable() {
105         return UNAVAILABILITY_CAUSE == null;
106     }
107 
108     public static void ensureAvailability() {
109         if (UNAVAILABILITY_CAUSE != null) {
110             throw (Error) new UnsatisfiedLinkError(
111                     "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
112         }
113     }
114 
115     public static Throwable unavailabilityCause() {
116         return UNAVAILABILITY_CAUSE;
117     }
118 
119     public MacOSDnsServerAddressStreamProvider() {
120         ensureAvailability();
121         currentMappings = retrieveCurrentMappings();
122         lastRefresh = new AtomicLong(System.nanoTime());
123     }
124 
125     private volatile Map<String, DnsServerAddresses> currentMappings;
126     private final AtomicLong lastRefresh;
127 
128     private static Map<String, DnsServerAddresses> retrieveCurrentMappings() {
129         DnsResolver[] resolvers = resolvers();
130 
131         if (resolvers == null || resolvers.length == 0) {
132             return Collections.emptyMap();
133         }
134         Arrays.sort(resolvers, RESOLVER_COMPARATOR);
135         Map<String, DnsServerAddresses> resolverMap = new HashMap<String, DnsServerAddresses>(resolvers.length);
136         for (DnsResolver resolver: resolvers) {
137             // Skip mdns
138             if ("mdns".equalsIgnoreCase(resolver.options())) {
139                 continue;
140             }
141             InetSocketAddress[] nameservers = resolver.nameservers();
142             if (nameservers == null || nameservers.length == 0) {
143                 continue;
144             }
145             String domain = resolver.domain();
146             if (domain == null) {
147                 // Default mapping.
148                 domain = StringUtil.EMPTY_STRING;
149             }
150             InetSocketAddress[] servers = resolver.nameservers();
151             for (int a = 0; a < servers.length; a++) {
152                 InetSocketAddress address = servers[a];
153                 // Check if the default port should be used
154                 if (address.getPort() == 0) {
155                     int port = resolver.port();
156                     if (port == 0) {
157                         port = 53;
158                     }
159                     servers[a] = new InetSocketAddress(address.getAddress(), port);
160                 }
161             }
162 
163             resolverMap.put(domain, DnsServerAddresses.sequential(servers));
164         }
165         return resolverMap;
166     }
167 
168     @Override
169     public DnsServerAddressStream nameServerAddressStream(String hostname) {
170         long last = lastRefresh.get();
171         Map<String, DnsServerAddresses> resolverMap = currentMappings;
172         if (System.nanoTime() - last > REFRESH_INTERVAL) {
173             // This is slightly racy which means it will be possible still use the old configuration for a small
174             // amount of time, but that's ok.
175             if (lastRefresh.compareAndSet(last, System.nanoTime())) {
176                 resolverMap = currentMappings = retrieveCurrentMappings();
177             }
178         }
179 
180         final String originalHostname = hostname;
181         for (;;) {
182             int i = hostname.indexOf('.', 1);
183             if (i < 0 || i == hostname.length() - 1) {
184                 // Try access default mapping.
185                 DnsServerAddresses addresses = resolverMap.get(StringUtil.EMPTY_STRING);
186                 if (addresses != null) {
187                     return addresses.stream();
188                 }
189                 return DnsServerAddressStreamProviders.unixDefault().nameServerAddressStream(originalHostname);
190             }
191 
192             DnsServerAddresses addresses = resolverMap.get(hostname);
193             if (addresses != null) {
194                 return addresses.stream();
195             }
196 
197             hostname = hostname.substring(i + 1);
198         }
199     }
200 
201     private static native DnsResolver[] resolvers();
202 }