Skip to content

Commit a6aeb6d

Browse files
Resolve all localhost addresses without querying DNS servers (#16749)
Motivation: According to RFC 6761, all domains within .localhost. should be resolved to the loopback address without querying DNS servers. This is useful e.g. when you have multiple web servers behind a local reverse proxy and you want to route the requests based on the hostname. Previously, netty would only resolve hostnames specified in the hosts file (or localhost and the machine hostname on windows) without querying DNS servers. Modifications: - localhost and all domains within .localhost. are now directly resolved to the loopback address - Replaced isLocalWindowsHost with isLocalHostAddress - Replaced LOCALHOST_ADDRESS constant with getLocalHostAddress method to correctly return the correct loopback address based on resolvedAddressTypes - Added test that covers all combinations of DnsNameResolverChannelStrategy, ResolvedAddressTypes and various localhost hostnames Result: Localhost hostnames are now resolved directly to the loopback address without querying a DNS server. Overriding them in the hosts file is still possible. Fixes #16744 --------- Co-authored-by: Norman Maurer <[email protected]>
1 parent c328ba2 commit a6aeb6d

2 files changed

Lines changed: 90 additions & 21 deletions

File tree

resolver-dns/src/main/java/io/netty/resolver/dns/DnsNameResolver.java

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import java.util.HashMap;
8484
import java.util.Iterator;
8585
import java.util.List;
86+
import java.util.Locale;
8687
import java.util.Map;
8788
import java.util.concurrent.TimeUnit;
8889

@@ -102,8 +103,8 @@ public class DnsNameResolver extends InetNameResolver {
102103

103104
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DnsNameResolver.class);
104105
private static final String LOCALHOST = "localhost";
106+
private static final String DOT_LOCALHOST = '.' + LOCALHOST;
105107
private static final String WINDOWS_HOST_NAME;
106-
private static final InetAddress LOCALHOST_ADDRESS;
107108
private static final DnsRecord[] EMPTY_ADDITIONALS = new DnsRecord[0];
108109
private static final DnsRecordType[] IPV4_ONLY_RESOLVED_RECORD_TYPES =
109110
{DnsRecordType.A};
@@ -136,18 +137,14 @@ public boolean isSharable() {
136137
static {
137138
if (NetUtil.isIpV4StackPreferred() || !anyInterfaceSupportsIpV6()) {
138139
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_ONLY;
139-
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
140140
} else {
141141
if (NetUtil.isIpV6AddressesPreferred()) {
142142
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV6_PREFERRED;
143-
LOCALHOST_ADDRESS = NetUtil.LOCALHOST6;
144143
} else {
145144
DEFAULT_RESOLVE_ADDRESS_TYPES = ResolvedAddressTypes.IPV4_PREFERRED;
146-
LOCALHOST_ADDRESS = NetUtil.LOCALHOST4;
147145
}
148146
}
149147
logger.debug("Default ResolvedAddressTypes: {}", DEFAULT_RESOLVE_ADDRESS_TYPES);
150-
logger.debug("Localhost address: {}", LOCALHOST_ADDRESS);
151148

152149
String hostName;
153150
try {
@@ -711,7 +708,7 @@ private InetAddress resolveHostsFileEntry(String hostname) {
711708
return null;
712709
}
713710
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
714-
return address == null && isLocalWindowsHost(hostname) ? LOCALHOST_ADDRESS : address;
711+
return address == null && isLocalHostAddress(hostname)? getLocalHostAddress() : address;
715712
}
716713

717714
private List<InetAddress> resolveHostsFileEntries(String hostname) {
@@ -724,23 +721,54 @@ private List<InetAddress> resolveHostsFileEntries(String hostname) {
724721
.addresses(hostname, resolvedAddressTypes);
725722
} else {
726723
InetAddress address = hostsFileEntriesResolver.address(hostname, resolvedAddressTypes);
727-
addresses = address != null ? Collections.singletonList(address) : null;
724+
addresses = address != null? Collections.singletonList(address) : null;
728725
}
729-
return addresses == null && isLocalWindowsHost(hostname) ?
730-
Collections.singletonList(LOCALHOST_ADDRESS) : addresses;
726+
return addresses == null && isLocalHostAddress(hostname)?
727+
Collections.singletonList(getLocalHostAddress()) : addresses;
731728
}
732729

733730
/**
734-
* Checks whether the given hostname is the localhost/host (computer) name on Windows OS.
735-
* Windows OS removed the localhost/host (computer) name information from the hosts file in the later versions
736-
* and such hostname cannot be resolved from hosts file.
737-
* See https://github.com/netty/netty/issues/5386
738-
* See https://github.com/netty/netty/issues/11142
731+
* Checks whether the given hostname refers to the current computer. This is the case for:
732+
* <ul>
733+
* <li>localhost.</li>
734+
* <li>any domain within .localhost.</li>
735+
* <li>the hostname of the local computer on Windows</li>
736+
* </ul>
737+
* <p>
738+
* According to RFC 6761 Section 6.3, localhost and subdomains of localhost should be resolved to the loopback
739+
* address by name resolution libraries without querying DNS servers. The hostname of the local machine can usually
740+
* be resolved from the hosts file, but on Windows, this is no longer possible.
741+
*
742+
* @param hostname the hostname that's being looked up
743+
* @return true if the hostname should point to the loopback adress. False otherwise.
744+
* @see <a href="https://github.com/netty/netty/issues/5386">Issue 5386</a>
745+
* @see <a href="https://github.com/netty/netty/issues/11142">Issue 11142</a>
746+
* @see <a href="https://github.com/netty/netty/issues/16744">Issue 16744</a>
747+
* @see <a href="https://www.rfc-editor.org/rfc/rfc6761.html#section-6.3">RFC 6761</a>
739748
*/
740-
private static boolean isLocalWindowsHost(String hostname) {
741-
return PlatformDependent.isWindows() &&
742-
(LOCALHOST.equalsIgnoreCase(hostname) ||
743-
(WINDOWS_HOST_NAME != null && WINDOWS_HOST_NAME.equalsIgnoreCase(hostname)));
749+
private static boolean isLocalHostAddress(String hostname) {
750+
if (PlatformDependent.isWindows() && WINDOWS_HOST_NAME != null &&
751+
WINDOWS_HOST_NAME.equalsIgnoreCase(hostname)) {
752+
return true;
753+
}
754+
755+
if (hostname.endsWith(".")) {
756+
hostname = hostname.substring(0, hostname.length() - 1);
757+
}
758+
return hostname.equalsIgnoreCase(LOCALHOST) || hostname.toLowerCase(Locale.US).endsWith(DOT_LOCALHOST);
759+
}
760+
761+
private InetAddress getLocalHostAddress() {
762+
switch (resolvedAddressTypes) {
763+
case IPV4_ONLY:
764+
case IPV4_PREFERRED:
765+
return NetUtil.LOCALHOST4;
766+
case IPV6_ONLY:
767+
case IPV6_PREFERRED:
768+
return NetUtil.LOCALHOST6;
769+
default:
770+
throw new IllegalStateException("Unknown ResolvedAddressTypes " + resolvedAddressTypes);
771+
}
744772
}
745773

746774
/**

resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
import org.junit.jupiter.api.Test;
7474
import org.junit.jupiter.api.Timeout;
7575
import org.junit.jupiter.api.function.Executable;
76+
import org.junit.jupiter.params.ParameterizedTest;
77+
import org.junit.jupiter.params.provider.Arguments;
78+
import org.junit.jupiter.params.provider.EnumSource;
79+
import org.junit.jupiter.params.provider.MethodSource;
7680

7781
import java.io.IOException;
7882
import java.io.InputStream;
@@ -113,9 +117,6 @@
113117
import java.util.concurrent.atomic.AtomicInteger;
114118
import java.util.concurrent.atomic.AtomicReference;
115119

116-
import org.junit.jupiter.params.ParameterizedTest;
117-
import org.junit.jupiter.params.provider.EnumSource;
118-
119120
import static io.netty.handler.codec.dns.DnsRecordType.A;
120121
import static io.netty.handler.codec.dns.DnsRecordType.AAAA;
121122
import static io.netty.handler.codec.dns.DnsRecordType.CNAME;
@@ -864,6 +865,46 @@ public void testResolveHostNameIpv6(DnsNameResolverChannelStrategy strategy) {
864865
testResolve0(strategy, ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, WINDOWS_HOST_NAME);
865866
}
866867

868+
private static List<Arguments> testResolveLocalhostWithoutDNSArgs() {
869+
DnsNameResolverChannelStrategy[] strategies = DnsNameResolverChannelStrategy.values();
870+
List<String> names = asList("localhost", "localhost.", "test.localhost", "TEsT.LOCalhost", "test.localhost.");
871+
872+
List<Arguments> output = new ArrayList<>();
873+
for (DnsNameResolverChannelStrategy strategy : strategies) {
874+
for (String name : names) {
875+
output.add(Arguments.of(strategy, ResolvedAddressTypes.IPV4_ONLY, NetUtil.LOCALHOST4, name));
876+
output.add(Arguments.of(strategy, ResolvedAddressTypes.IPV6_ONLY, NetUtil.LOCALHOST6, name));
877+
}
878+
}
879+
880+
return output;
881+
}
882+
883+
@ParameterizedTest
884+
@MethodSource("testResolveLocalhostWithoutDNSArgs")
885+
public void testResolveLocalhostWithoutDNSOrHostsFile(DnsNameResolverChannelStrategy strategy,
886+
ResolvedAddressTypes addressTypes, InetAddress expectedAddr,
887+
String name) {
888+
DnsNameResolver resolver = newResolver(strategy, addressTypes)
889+
.hostsFileEntriesResolver(new HostsFileEntriesResolver() {
890+
@Override
891+
public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) {
892+
// The hosts file should not be required to resolve localhost addresses.
893+
return null;
894+
}
895+
})
896+
.build();
897+
try {
898+
InetAddress address = resolver.resolve(name).syncUninterruptibly().getNow();
899+
assertEquals(expectedAddr, address);
900+
901+
// We are resolving the local address, so we shouldn't make any queries.
902+
assertNoQueriesMade(resolver);
903+
} finally {
904+
resolver.close();
905+
}
906+
}
907+
867908
@ParameterizedTest
868909
@EnumSource(DnsNameResolverChannelStrategy.class)
869910
public void testResolveNullIpv4(DnsNameResolverChannelStrategy strategy) {

0 commit comments

Comments
 (0)