diff --git a/docs/modules/ROOT/pages/http-client.adoc b/docs/modules/ROOT/pages/http-client.adoc index ff5ed11a6c..9eb4125778 100644 --- a/docs/modules/ROOT/pages/http-client.adoc +++ b/docs/modules/ROOT/pages/http-client.adoc @@ -534,6 +534,8 @@ Default: 0. | `completeOncePreferredResolved` | When this setting is enabled, the resolver notifies as soon as all queries for the preferred address type are complete. When this setting is disabled, the resolver notifies when all possible address types are complete. This configuration is applicable for `DnsNameResolver#resolveAll(String)`. By default, this setting is enabled. +| `datagramChannelStrategy` | Sets the strategy that is used to determine how a `DatagramChannel` is used by the resolver +for sending queries over UDP protocol. Default to {nettyjavadoc}/io/netty/resolver/dns/DnsNameResolverChannelStrategy.html#ChannelPerResolver[`DnsNameResolverChannelStrategy#ChannelPerResolver`] | `disableOptionalRecord` | Disables the automatic inclusion of an optional record that tries to give a hint to the remote DNS server about how much data the resolver can read per response. By default, this setting is enabled. | `disableRecursionDesired` | Specifies whether this resolver has to send a DNS query with the recursion desired (RD) flag set. diff --git a/docs/modules/ROOT/pages/tcp-client.adoc b/docs/modules/ROOT/pages/tcp-client.adoc index fbc95a79c6..008988dcfa 100644 --- a/docs/modules/ROOT/pages/tcp-client.adoc +++ b/docs/modules/ROOT/pages/tcp-client.adoc @@ -383,6 +383,8 @@ Additionally, https://tools.ietf.org/html/rfc7766[`TCP fallback`] is enabled by | `completeOncePreferredResolved` | When this setting is enabled, the resolver notifies as soon as all queries for the preferred address type are complete. When this setting is disabled, the resolver notifies when all possible address types are complete. This configuration is applicable for `DnsNameResolver#resolveAll(String)`. By default, this setting is enabled. +| `datagramChannelStrategy` | Sets the strategy that is used to determine how a `DatagramChannel` is used by the resolver +for sending queries over UDP protocol. Default to {nettyjavadoc}/io/netty/resolver/dns/DnsNameResolverChannelStrategy.html#ChannelPerResolver[`DnsNameResolverChannelStrategy#ChannelPerResolver`] | `disableOptionalRecord` | Disables the automatic inclusion of an optional record that tries to give a hint to the remote DNS server about how much data the resolver can read per response. By default, this setting is enabled. | `disableRecursionDesired` | Specifies whether this resolver has to send a DNS query with the recursion desired (RD) flag set. diff --git a/reactor-netty-core/build.gradle b/reactor-netty-core/build.gradle index 3ace78c86a..e427df37bb 100644 --- a/reactor-netty-core/build.gradle +++ b/reactor-netty-core/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2024 VMware, Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2025 VMware, Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -266,6 +266,7 @@ task japicmp(type: JapicmpTask) { compatibilityChangeExcludes = [ "METHOD_NEW_DEFAULT" ] methodExcludes = [ + 'reactor.netty.transport.NameResolverProvider$NameResolverSpec#datagramChannelStrategy(io.netty.resolver.dns.DnsNameResolverChannelStrategy)' ] classExcludes = [ diff --git a/reactor-netty-core/src/main/java/reactor/netty/transport/NameResolverProvider.java b/reactor-netty-core/src/main/java/reactor/netty/transport/NameResolverProvider.java index ae80bbab27..38ff8b9d70 100644 --- a/reactor-netty-core/src/main/java/reactor/netty/transport/NameResolverProvider.java +++ b/reactor-netty-core/src/main/java/reactor/netty/transport/NameResolverProvider.java @@ -27,6 +27,7 @@ import io.netty.resolver.dns.DnsCache; import io.netty.resolver.dns.DnsNameResolver; import io.netty.resolver.dns.DnsNameResolverBuilder; +import io.netty.resolver.dns.DnsNameResolverChannelStrategy; import io.netty.resolver.dns.DnsQueryLifecycleObserverFactory; import io.netty.resolver.dns.LoggingDnsQueryLifeCycleObserverFactory; import io.netty.resolver.dns.RoundRobinDnsAddressResolverGroup; @@ -115,6 +116,17 @@ public interface NameResolverSpec { */ NameResolverSpec completeOncePreferredResolved(boolean enable); + /** + * Sets the strategy that is used to determine how a {@link DatagramChannel} is used by the resolver for sending + * queries over UDP protocol. + * Default to {@link DnsNameResolverChannelStrategy#ChannelPerResolver} + * + * @param datagramChannelStrategy the {@link DnsNameResolverChannelStrategy} to use when doing queries over UDP protocol + * @return {@code this} + * @since 1.2.3 + */ + NameResolverSpec datagramChannelStrategy(DnsNameResolverChannelStrategy datagramChannelStrategy); + /** * Disables the automatic inclusion of an optional record that tries to hint the remote DNS server about * how much data the resolver can read per response. By default, this is enabled. @@ -329,6 +341,17 @@ public Duration cacheNegativeTimeToLive() { return cacheNegativeTimeToLive; } + /** + * Returns the configured custom {@link DnsNameResolverChannelStrategy} or null. + * + * @return the configured custom {@link DnsNameResolverChannelStrategy} or null + * @since 1.2.3 + */ + @Nullable + public DnsNameResolverChannelStrategy datagramChannelStrategy() { + return datagramChannelStrategy; + } + /** * Returns the configured custom provider of {@link DnsAddressResolverGroup} or null. * @@ -500,6 +523,7 @@ public boolean equals(Object o) { cacheMinTimeToLive.equals(that.cacheMinTimeToLive) && cacheNegativeTimeToLive.equals(that.cacheNegativeTimeToLive) && completeOncePreferredResolved == that.completeOncePreferredResolved && + datagramChannelStrategy == that.datagramChannelStrategy && disableOptionalRecord == that.disableOptionalRecord && disableRecursionDesired == that.disableRecursionDesired && Objects.equals(dnsAddressResolverGroupProvider, that.dnsAddressResolverGroupProvider) && @@ -526,6 +550,7 @@ public int hashCode() { result = 31 * result + Objects.hashCode(cacheMinTimeToLive); result = 31 * result + Objects.hashCode(cacheNegativeTimeToLive); result = 31 * result + Boolean.hashCode(completeOncePreferredResolved); + result = 31 * result + Objects.hashCode(datagramChannelStrategy); result = 31 * result + Boolean.hashCode(disableOptionalRecord); result = 31 * result + Boolean.hashCode(disableRecursionDesired); result = 31 * result + Objects.hashCode(dnsAddressResolverGroupProvider); @@ -573,6 +598,7 @@ public DnsAddressResolverGroup newNameResolverGroup(LoopResources defaultLoopRes .ndots(ndots) .queryTimeoutMillis(queryTimeout.toMillis()) .eventLoop(group.next()) + .datagramChannelStrategy(datagramChannelStrategy) .datagramChannelFactory(() -> loop.onChannel(DatagramChannel.class, group)) .socketChannelFactory(() -> loop.onChannel(SocketChannel.class, group), retryTcpOnTimeout); if (bindAddressSupplier != null) { @@ -606,6 +632,7 @@ public DnsAddressResolverGroup newNameResolverGroup(LoopResources defaultLoopRes final Duration cacheMinTimeToLive; final Duration cacheNegativeTimeToLive; final boolean completeOncePreferredResolved; + final DnsNameResolverChannelStrategy datagramChannelStrategy; final boolean disableOptionalRecord; final boolean disableRecursionDesired; final Function dnsAddressResolverGroupProvider; @@ -629,6 +656,7 @@ public DnsAddressResolverGroup newNameResolverGroup(LoopResources defaultLoopRes this.cacheMinTimeToLive = build.cacheMinTimeToLive; this.cacheNegativeTimeToLive = build.cacheNegativeTimeToLive; this.completeOncePreferredResolved = build.completeOncePreferredResolved; + this.datagramChannelStrategy = build.datagramChannelStrategy; this.disableOptionalRecord = build.disableOptionalRecord; this.disableRecursionDesired = build.disableRecursionDesired; this.dnsAddressResolverGroupProvider = build.dnsAddressResolverGroupProvider; @@ -652,6 +680,7 @@ static final class Build implements NameResolverSpec { static final Duration DEFAULT_CACHE_MIN_TIME_TO_LIVE = Duration.ofSeconds(0); static final Duration DEFAULT_CACHE_NEGATIVE_TIME_TO_LIVE = Duration.ofSeconds(0); static final boolean DEFAULT_COMPLETE_ONCE_PREFERRED_RESOLVED = true; + static final DnsNameResolverChannelStrategy DEFAULT_DATAGRAM_CHANNEL_STRATEGY = DnsNameResolverChannelStrategy.ChannelPerResolver; static final int DEFAULT_MAX_PAYLOAD_SIZE = 4096; static final int DEFAULT_MAX_QUERIES_PER_RESOLVE = 16; static final int DEFAULT_NDOTS = -1; @@ -662,6 +691,7 @@ static final class Build implements NameResolverSpec { Duration cacheMinTimeToLive = DEFAULT_CACHE_MIN_TIME_TO_LIVE; Duration cacheNegativeTimeToLive = DEFAULT_CACHE_NEGATIVE_TIME_TO_LIVE; boolean completeOncePreferredResolved = DEFAULT_COMPLETE_ONCE_PREFERRED_RESOLVED; + DnsNameResolverChannelStrategy datagramChannelStrategy = DEFAULT_DATAGRAM_CHANNEL_STRATEGY; boolean disableOptionalRecord; boolean disableRecursionDesired; Function dnsAddressResolverGroupProvider; @@ -711,6 +741,12 @@ public NameResolverSpec completeOncePreferredResolved(boolean enable) { return this; } + @Override + public NameResolverSpec datagramChannelStrategy(DnsNameResolverChannelStrategy datagramChannelStrategy) { + this.datagramChannelStrategy = Objects.requireNonNull(datagramChannelStrategy); + return this; + } + @Override public NameResolverSpec disableOptionalRecord(boolean disable) { this.disableOptionalRecord = disable; diff --git a/reactor-netty-core/src/test/java/reactor/netty/transport/NameResolverProviderTest.java b/reactor-netty-core/src/test/java/reactor/netty/transport/NameResolverProviderTest.java index bd1a187e7f..cbeced2d57 100644 --- a/reactor-netty-core/src/test/java/reactor/netty/transport/NameResolverProviderTest.java +++ b/reactor-netty-core/src/test/java/reactor/netty/transport/NameResolverProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2024 VMware, Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2025 VMware, Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import io.netty.resolver.dns.DnsCache; import io.netty.resolver.dns.DnsCacheEntry; import io.netty.resolver.dns.DnsNameResolverBuilder; +import io.netty.resolver.dns.DnsNameResolverChannelStrategy; import io.netty.resolver.dns.DnsServerAddressStreamProviders; import io.netty.resolver.dns.RoundRobinDnsAddressResolverGroup; import io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider; @@ -48,6 +49,7 @@ import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_CACHE_MAX_TIME_TO_LIVE; import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_CACHE_MIN_TIME_TO_LIVE; import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_CACHE_NEGATIVE_TIME_TO_LIVE; +import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_DATAGRAM_CHANNEL_STRATEGY; import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_MAX_PAYLOAD_SIZE; import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_MAX_QUERIES_PER_RESOLVE; import static reactor.netty.transport.NameResolverProvider.Build.DEFAULT_NDOTS; @@ -145,6 +147,20 @@ void completeOncePreferredResolved() { assertThat(builder.build().isCompleteOncePreferredResolved()).isFalse(); } + @Test + void datagramChannelStrategy() { + assertThat(builder.build().datagramChannelStrategy()).isEqualTo(DEFAULT_DATAGRAM_CHANNEL_STRATEGY); + + builder.datagramChannelStrategy(DnsNameResolverChannelStrategy.ChannelPerResolution); + assertThat(builder.build().datagramChannelStrategy()).isEqualTo(DnsNameResolverChannelStrategy.ChannelPerResolution); + } + + @Test + void datagramChannelStrategyBadValues() { + assertThatExceptionOfType(NullPointerException.class) + .isThrownBy(() -> builder.cacheNegativeTimeToLive(null)); + } + @Test void disableOptionalRecord() { assertThat(builder.build().isDisableOptionalRecord()).isFalse();