View Javadoc
1   /*
2    * Copyright 2015 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;
17  
18  import io.netty5.util.CharsetUtil;
19  import io.netty5.util.internal.ObjectUtil;
20  import io.netty5.util.internal.PlatformDependent;
21  import io.netty5.util.internal.SystemPropertyUtil;
22  import io.netty5.util.internal.logging.InternalLogger;
23  import io.netty5.util.internal.logging.InternalLoggerFactory;
24  
25  import java.net.InetAddress;
26  import java.nio.charset.Charset;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.concurrent.atomic.AtomicLong;
32  
33  /**
34   * Default {@link HostsFileEntriesResolver} that resolves hosts file entries only once.
35   */
36  public final class DefaultHostsFileEntriesResolver implements HostsFileEntriesResolver {
37  
38      private static final InternalLogger logger =
39              InternalLoggerFactory.getInstance(DefaultHostsFileEntriesResolver.class);
40      private static final long DEFAULT_REFRESH_INTERVAL;
41  
42      private final long refreshInterval;
43      private final AtomicLong lastRefresh = new AtomicLong(System.nanoTime());
44      private final HostsFileEntriesProvider.Parser hostsFileParser;
45      private volatile Map<String, List<InetAddress>> inet4Entries;
46      private volatile Map<String, List<InetAddress>> inet6Entries;
47  
48      static {
49          DEFAULT_REFRESH_INTERVAL = SystemPropertyUtil.getLong(
50                  "io.netty5.hostsFileRefreshInterval", /*nanos*/0);
51  
52          if (logger.isDebugEnabled()) {
53              logger.debug("-Dio.netty5.hostsFileRefreshInterval: {}", DEFAULT_REFRESH_INTERVAL);
54          }
55      }
56  
57      public DefaultHostsFileEntriesResolver() {
58          this(HostsFileEntriesProvider.parser(), DEFAULT_REFRESH_INTERVAL);
59      }
60  
61      // for testing purpose only
62      DefaultHostsFileEntriesResolver(HostsFileEntriesProvider.Parser hostsFileParser, long refreshInterval) {
63          this.hostsFileParser = hostsFileParser;
64          this.refreshInterval = ObjectUtil.checkPositiveOrZero(refreshInterval, "refreshInterval");
65          HostsFileEntriesProvider entries = parseEntries(hostsFileParser);
66          inet4Entries = entries.ipv4Entries();
67          inet6Entries = entries.ipv6Entries();
68      }
69  
70      @Override
71      public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
72          return firstAddress(addresses(inetHost, resolvedAddressTypes));
73      }
74  
75      /**
76       * Resolves all addresses of a hostname against the entries in a hosts file, depending on the specified
77       * {@link ResolvedAddressTypes}.
78       *
79       * @param inetHost the hostname to resolve
80       * @param resolvedAddressTypes the address types to resolve
81       * @return all matching addresses or {@code null} in case the hostname cannot be resolved
82       */
83      public List<InetAddress> addresses(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
84          String normalized = normalize(inetHost);
85          ensureHostsFileEntriesAreFresh();
86  
87          switch (resolvedAddressTypes) {
88              case IPV4_ONLY:
89                  return inet4Entries.get(normalized);
90              case IPV6_ONLY:
91                  return inet6Entries.get(normalized);
92              case IPV4_PREFERRED:
93                  List<InetAddress> allInet4Addresses = inet4Entries.get(normalized);
94                  return allInet4Addresses != null ? allAddresses(allInet4Addresses, inet6Entries.get(normalized)) :
95                          inet6Entries.get(normalized);
96              case IPV6_PREFERRED:
97                  List<InetAddress> allInet6Addresses = inet6Entries.get(normalized);
98                  return allInet6Addresses != null ? allAddresses(allInet6Addresses, inet4Entries.get(normalized)) :
99                          inet4Entries.get(normalized);
100             default:
101                 throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
102         }
103     }
104 
105     private void ensureHostsFileEntriesAreFresh() {
106         long interval = refreshInterval;
107         if (interval == 0) {
108             return;
109         }
110         long last = lastRefresh.get();
111         long currentTime = System.nanoTime();
112         if (currentTime - last > interval) {
113             if (lastRefresh.compareAndSet(last, currentTime)) {
114                 HostsFileEntriesProvider entries = parseEntries(hostsFileParser);
115                 inet4Entries = entries.ipv4Entries();
116                 inet6Entries = entries.ipv6Entries();
117             }
118         }
119     }
120 
121     // package-private for testing purposes
122     String normalize(String inetHost) {
123         return inetHost.toLowerCase(Locale.ENGLISH);
124     }
125 
126     private static List<InetAddress> allAddresses(List<InetAddress> a, List<InetAddress> b) {
127         List<InetAddress> result = new ArrayList<>(a.size() + (b == null ? 0 : b.size()));
128         result.addAll(a);
129         if (b != null) {
130             result.addAll(b);
131         }
132         return result;
133     }
134 
135     private static InetAddress firstAddress(List<InetAddress> addresses) {
136         return addresses != null && !addresses.isEmpty() ? addresses.get(0) : null;
137     }
138 
139     private static HostsFileEntriesProvider parseEntries(HostsFileEntriesProvider.Parser parser) {
140         if (PlatformDependent.isWindows()) {
141             // Ony windows there seems to be no standard for the encoding used for the hosts file, so let us
142             // try multiple until we either were able to parse it or there is none left and so we return an
143             // empty instance.
144             return parser.parseSilently(Charset.defaultCharset(), CharsetUtil.UTF_16, CharsetUtil.UTF_8);
145         }
146         return parser.parseSilently();
147     }
148 }