From 47e1f43c23c8eb861e64d68be96c670c2db53c06 Mon Sep 17 00:00:00 2001 From: Thomas Kountis Date: Fri, 12 Jan 2024 08:38:28 -0800 Subject: [PATCH] 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. --- .../resolver/dns/DnsErrorCauseException.java | 74 +++++++++++++++++++ .../netty/resolver/dns/DnsResolveContext.java | 25 ++++++- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 resolver-dns/src/main/java/io/netty/resolver/dns/DnsErrorCauseException.java diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsErrorCauseException.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsErrorCauseException.java new file mode 100644 index 000000000000..ef3969380c7c --- /dev/null +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsErrorCauseException.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.resolver.dns; + +import io.netty.handler.codec.dns.DnsResponseCode; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SuppressJava6Requirement; +import io.netty.util.internal.ThrowableUtil; + +import java.net.UnknownHostException; + +/** + * A metadata carrier exception, to propagate {@link DnsResponseCode} information as an enrichment + * within the {@link UnknownHostException} cause. + */ +public final class DnsErrorCauseException extends RuntimeException { + + private static final long serialVersionUID = 7485145036717494533L; + + private final DnsResponseCode code; + + private DnsErrorCauseException(String message, DnsResponseCode code) { + super(message); + this.code = code; + } + + @SuppressJava6Requirement(reason = "uses Java 7+ Exception.(String, Throwable, boolean, boolean)" + + " but is guarded by version checks") + private DnsErrorCauseException(String message, DnsResponseCode code, boolean shared) { + super(message, null, false, true); + this.code = code; + assert shared; + } + + // Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the + // Classloader. + @Override + public Throwable fillInStackTrace() { + return this; + } + + /** + * Returns the DNS error-code that caused the {@link UnknownHostException}. + * + * @return the DNS error-code that caused the {@link UnknownHostException}. + */ + public DnsResponseCode getCode() { + return code; + } + + static DnsErrorCauseException newStatic(String message, DnsResponseCode code, Class clazz, String method) { + final DnsErrorCauseException exception; + if (PlatformDependent.javaVersion() >= 7) { + exception = new DnsErrorCauseException(message, code, true); + } else { + exception = new DnsErrorCauseException(message, code); + } + return ThrowableUtil.unknownStackTrace(exception, clazz, method); + } +} diff --git a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java index 10ff7ca9c604..3361440204b8 100644 --- a/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java +++ b/resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java @@ -60,6 +60,8 @@ import java.util.NoSuchElementException; import java.util.Set; +import static io.netty.handler.codec.dns.DnsResponseCode.NXDOMAIN; +import static io.netty.handler.codec.dns.DnsResponseCode.SERVFAIL; import static io.netty.resolver.dns.DnsAddressDecoder.decodeAddress; import static java.lang.Math.min; @@ -81,6 +83,12 @@ abstract class DnsResolveContext { private static final RuntimeException NAME_SERVERS_EXHAUSTED_EXCEPTION = DnsResolveContextException.newStatic("No name servers returned an answer", DnsResolveContext.class, "tryToFinishResolve(..)"); + private static final RuntimeException SERVFAIL_QUERY_FAILED_EXCEPTION = + DnsErrorCauseException.newStatic("Query failed with SERVFAIL", SERVFAIL, + DnsResolveContext.class, "onResponse(..)"); + private static final RuntimeException NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION = + DnsErrorCauseException.newStatic("Query failed with NXDOMAIN", NXDOMAIN, + DnsResolveContext.class, "onResponse(..)"); final DnsNameResolver parent; private final Channel channel; @@ -636,7 +644,7 @@ private void onResponse(final DnsServerAddressStream nameServerAddrStream, final // Retry with the next server if the server did not tell us that the domain does not exist. if (code != DnsResponseCode.NXDOMAIN) { query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, - queryLifecycleObserver.queryNoAnswer(code), true, promise, null); + queryLifecycleObserver.queryNoAnswer(code), true, promise, cause(code)); } else { queryLifecycleObserver.queryFailed(NXDOMAIN_QUERY_FAILED_EXCEPTION); @@ -661,6 +669,10 @@ private void onResponse(final DnsServerAddressStream nameServerAddrStream, final if (!res.isAuthoritativeAnswer()) { query(nameServerAddrStream, nameServerAddrStreamIndex + 1, question, newDnsQueryLifecycleObserver(question), true, promise, null); + } else { + // Failed with NX cause - distinction between an authoritative NXDOMAIN vs a timeout + tryToFinishResolve(nameServerAddrStream, nameServerAddrStreamIndex, question, + queryLifecycleObserver, promise, NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION); } } } finally { @@ -716,6 +728,17 @@ private boolean handleRedirect( return false; } + private static Throwable cause(final DnsResponseCode code) { + assert code != null; + if (SERVFAIL.intValue() == code.intValue()) { + return SERVFAIL_QUERY_FAILED_EXCEPTION; + } else if (NXDOMAIN.intValue() == code.intValue()) { + return NXDOMAIN_CAUSE_QUERY_FAILED_EXCEPTION; + } + + return null; + } + private static final class DnsAddressStreamList extends AbstractList { private final DnsServerAddressStream duplicate;