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.netty5.resolver.dns.macos;
17  
18  import io.netty5.resolver.dns.DnsServerAddressStream;
19  import io.netty5.resolver.dns.DnsServerAddressStreamProvider;
20  import io.netty5.resolver.dns.DnsServerAddressStreamProviders;
21  import io.netty5.resolver.dns.DnsServerAddresses;
22  import io.netty5.util.internal.ClassInitializerUtil;
23  import io.netty5.util.internal.NativeLibraryLoader;
24  import io.netty5.util.internal.PlatformDependent;
25  import io.netty5.util.internal.StringUtil;
26  import io.netty5.util.internal.ThrowableUtil;
27  import io.netty5.util.internal.logging.InternalLogger;
28  import io.netty5.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              (r1, r2) -> {
48                  // Note: order is descending (from higher to lower) so entries with lower search order override
49                  // entries with higher search order.
50                  return Integer.compare(r2.searchOrder(), r1.searchOrder());
51              };
52  
53      private static final Throwable UNAVAILABILITY_CAUSE;
54  
55      private static final InternalLogger logger =
56              InternalLoggerFactory.getInstance(MacOSDnsServerAddressStreamProvider.class);
57  
58      // Let's refresh every 10 seconds.
59      private static final long REFRESH_INTERVAL = TimeUnit.SECONDS.toNanos(10);
60  
61      static {
62          // Preload all classes that will be used in the OnLoad(...) function of JNI to eliminate the possiblity of a
63          // class-loader deadlock. This is a workaround for https://github.com/netty/netty/issues/11209.
64  
65          // This needs to match all the classes that are loaded via NETTY_JNI_UTIL_LOAD_CLASS or looked up via
66          // NETTY_JNI_UTIL_FIND_CLASS.
67          ClassInitializerUtil.tryLoadClasses(MacOSDnsServerAddressStreamProvider.class,
68                  // netty_resolver_dns_macos
69                  byte[].class, String.class
70          );
71  
72          Throwable cause = null;
73          try {
74              loadNativeLibrary();
75          } catch (Throwable error) {
76              cause = error;
77          }
78          UNAVAILABILITY_CAUSE = cause;
79      }
80  
81      private static void loadNativeLibrary() {
82          if (!PlatformDependent.isOsx()) {
83              throw new IllegalStateException("Only supported on MacOS/OSX");
84          }
85          String staticLibName = "netty5_resolver_dns_native_macos";
86          String sharedLibName = staticLibName + '_' + PlatformDependent.normalizedArch();
87          ClassLoader cl = PlatformDependent.getClassLoader(MacOSDnsServerAddressStreamProvider.class);
88          try {
89              NativeLibraryLoader.load(sharedLibName, cl);
90          } catch (UnsatisfiedLinkError e1) {
91              try {
92                  NativeLibraryLoader.load(staticLibName, cl);
93                  logger.debug("Failed to load {}", sharedLibName, e1);
94              } catch (UnsatisfiedLinkError e2) {
95                  ThrowableUtil.addSuppressed(e1, e2);
96                  throw e1;
97              }
98          }
99      }
100 
101     public static boolean isAvailable() {
102         return UNAVAILABILITY_CAUSE == null;
103     }
104 
105     public static void ensureAvailability() {
106         if (UNAVAILABILITY_CAUSE != null) {
107             throw (Error) new UnsatisfiedLinkError(
108                     "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
109         }
110     }
111 
112     public static Throwable unavailabilityCause() {
113         return UNAVAILABILITY_CAUSE;
114     }
115 
116     public MacOSDnsServerAddressStreamProvider() {
117         ensureAvailability();
118         currentMappings = retrieveCurrentMappings();
119         lastRefresh = new AtomicLong(System.nanoTime());
120     }
121 
122     private volatile Map<String, DnsServerAddresses> currentMappings;
123     private final AtomicLong lastRefresh;
124 
125     private static Map<String, DnsServerAddresses> retrieveCurrentMappings() {
126         DnsResolver[] resolvers = resolvers();
127 
128         if (resolvers == null || resolvers.length == 0) {
129             return Collections.emptyMap();
130         }
131         Arrays.sort(resolvers, RESOLVER_COMPARATOR);
132         Map<String, DnsServerAddresses> resolverMap = new HashMap<>(resolvers.length);
133         for (DnsResolver resolver: resolvers) {
134             // Skip mdns
135             if ("mdns".equalsIgnoreCase(resolver.options())) {
136                 continue;
137             }
138             InetSocketAddress[] nameservers = resolver.nameservers();
139             if (nameservers == null || nameservers.length == 0) {
140                 continue;
141             }
142             String domain = resolver.domain();
143             if (domain == null) {
144                 // Default mapping.
145                 domain = StringUtil.EMPTY_STRING;
146             }
147             InetSocketAddress[] servers = resolver.nameservers();
148             for (int a = 0; a < servers.length; a++) {
149                 InetSocketAddress address = servers[a];
150                 // Check if the default port should be used
151                 if (address.getPort() == 0) {
152                     int port = resolver.port();
153                     if (port == 0) {
154                         port = 53;
155                     }
156                     servers[a] = new InetSocketAddress(address.getAddress(), port);
157                 }
158             }
159 
160             resolverMap.put(domain, DnsServerAddresses.sequential(servers));
161         }
162         return resolverMap;
163     }
164 
165     @Override
166     public DnsServerAddressStream nameServerAddressStream(String hostname) {
167         long last = lastRefresh.get();
168         Map<String, DnsServerAddresses> resolverMap = currentMappings;
169         if (System.nanoTime() - last > REFRESH_INTERVAL) {
170             // This is slightly racy which means it will be possible still use the old configuration for a small
171             // amount of time, but that's ok.
172             if (lastRefresh.compareAndSet(last, System.nanoTime())) {
173                 resolverMap = currentMappings = retrieveCurrentMappings();
174             }
175         }
176 
177         final String originalHostname = hostname;
178         for (;;) {
179             int i = hostname.indexOf('.', 1);
180             if (i < 0 || i == hostname.length() - 1) {
181                 // Try access default mapping.
182                 DnsServerAddresses addresses = resolverMap.get(StringUtil.EMPTY_STRING);
183                 if (addresses != null) {
184                     return addresses.stream();
185                 }
186                 return DnsServerAddressStreamProviders.unixDefault().nameServerAddressStream(originalHostname);
187             }
188 
189             DnsServerAddresses addresses = resolverMap.get(hostname);
190             if (addresses != null) {
191                 return addresses.stream();
192             }
193 
194             hostname = hostname.substring(i + 1);
195         }
196     }
197 
198     private static native DnsResolver[] resolvers();
199 }