Skip to content

Commit 47e1f43

Browse files
authored
Expose DNS error codes through the UnknownHostException (#13721)
Motivation: UnknownHostException are covering a broader range of discovery failures, where the API consumer has no visibility of the underlying reason. Modification: Expose initial-cause for UnknownHostExceptions to enrich the result. Result: The consumer now can decide whether the exception is due to failures or due an authoritative NXDOMAIN.
1 parent 4dea3f7 commit 47e1f43

2 files changed

Lines changed: 98 additions & 1 deletion

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2023 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+
17+
package io.netty.resolver.dns;
18+
19+
import io.netty.handler.codec.dns.DnsResponseCode;
20+
import io.netty.util.internal.PlatformDependent;
21+
import io.netty.util.internal.SuppressJava6Requirement;
22+
import io.netty.util.internal.ThrowableUtil;
23+
24+
import java.net.UnknownHostException;
25+
26+
/**
27+
* A metadata carrier exception, to propagate {@link DnsResponseCode} information as an enrichment
28+
* within the {@link UnknownHostException} cause.
29+
*/
30+
public final class DnsErrorCauseException extends RuntimeException {
31+
32+
private static final long serialVersionUID = 7485145036717494533L;
33+
34+
private final DnsResponseCode code;
35+
36+
private DnsErrorCauseException(String message, DnsResponseCode code) {
37+
super(message);
38+
this.code = code;
39+
}
40+
41+
@SuppressJava6Requirement(reason = "uses Java 7+ Exception.<init>(String, Throwable, boolean, boolean)" +
42+
" but is guarded by version checks")
43+
private DnsErrorCauseException(String message, DnsResponseCode code, boolean shared) {
44+
super(message, null, false, true);
45+
this.code = code;
46+
assert shared;
47+
}
48+
49+
// Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
50+
// Classloader.
51+
@Override
52+
public Throwable fillInStackTrace() {
53+
return this;
54+
}
55+
56+
/**
57+
* Returns the DNS error-code that caused the {@link UnknownHostException}.
58+
*
59+
* @return the DNS error-code that caused the {@link UnknownHostException}.
60+
*/
61+
public DnsResponseCode getCode() {
62+
return code;
63+
}
64+
65+
static DnsErrorCauseException newStatic(String message, DnsResponseCode code, Class<?> clazz, String method) {
66+
final DnsErrorCauseException exception;
67+
if (PlatformDependent.javaVersion() >= 7) {
68+
exception = new DnsErrorCauseException(message, code, true);
69+
} else {
70+
exception = new DnsErrorCauseException(message, code);
71+
}
72+
return ThrowableUtil.unknownStackTrace(exception, clazz, method);
73+
}
74+
}

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
import java.util.NoSuchElementException;
6161
import java.util.Set;
6262

63+
import static io.netty.handler.codec.dns.DnsResponseCode.NXDOMAIN;
64+
import static io.netty.handler.codec.dns.DnsResponseCode.SERVFAIL;
6365
import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress;
6466
import static java.lang.Math.min;
6567

@@ -81,6 +83,12 @@ abstract class DnsResolveContext<T> {
8183
private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION =
8284
DnsResolveContextException.newStatic("No name servers returned an answer",
8385
DnsResolveContext.class, "tryToFinishResolve(..)");
86+
private static final RuntimeException SERVFAIL_QUERY_FAILED_EXCEPTION =
87+
DnsErrorCauseException.newStatic("Query failed with SERVFAIL", SERVFAIL,
88+
DnsResolveContext.class, "onResponse(..)");
89+
private static final RuntimeException NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION =
90+
DnsErrorCauseException.newStatic("Query failed with NXDOMAIN", NXDOMAIN,
91+
DnsResolveContext.class, "onResponse(..)");
8492

8593
final DnsNameResolver parent;
8694
private final Channel channel;
@@ -636,7 +644,7 @@ private void onResponse(final DnsServerAddressStream nameServerAddrStream, final
636644
// Retry with the next server if the server did not tell us that the domain does not exist.
637645
if (code != DnsResponseCode.NXDOMAIN) {
638646
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
639-
queryLifecycleObserver.queryNoAnswer(code), true, promise, null);
647+
queryLifecycleObserver.queryNoAnswer(code), true, promise, cause(code));
640648
} else {
641649
queryLifecycleObserver.queryFailed(NXDOMAIN_QUERY_FAILED_EXCEPTION);
642650

@@ -661,6 +669,10 @@ private void onResponse(final DnsServerAddressStream nameServerAddrStream, final
661669
if (!res.isAuthoritativeAnswer()) {
662670
query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question,
663671
newDnsQueryLifecycleObserver(question), true, promise, null);
672+
} else {
673+
// Failed with NX cause - distinction between an authoritative NXDOMAIN vs a timeout
674+
tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question,
675+
queryLifecycleObserver, promise, NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION);
664676
}
665677
}
666678
} finally {
@@ -716,6 +728,17 @@ private boolean handleRedirect(
716728
return false;
717729
}
718730

731+
private static Throwable cause(final DnsResponseCode code) {
732+
assert code != null;
733+
if (SERVFAIL.intValue() == code.intValue()) {
734+
return SERVFAIL_QUERY_FAILED_EXCEPTION;
735+
} else if (NXDOMAIN.intValue() == code.intValue()) {
736+
return NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION;
737+
}
738+
739+
return null;
740+
}
741+
719742
private static final class DnsAddressStreamList extends AbstractList<InetSocketAddress> {
720743

721744
private final DnsServerAddressStream duplicate;

0 commit comments

Comments
 (0)