Skip to content

Commit e37cbdd

Browse files
authored
feat: support config AddressResolverGroup in r2dbc-mysql (#279)
Motivation: Currently,`AddressResolverGroup` can't be configured. The DnsResolver default start address listen to "0.0.0.0", which may have some security risks. also see netty/netty#11061 Modification: Add `AddressResolverGroup` in Client's connect method --------- Signed-off-by: ZhangJian He <shoothzj@gmail.com>
1 parent 508d6c3 commit e37cbdd

File tree

6 files changed

+104
-34
lines changed

6 files changed

+104
-34
lines changed

r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfiguration.java

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.asyncer.r2dbc.mysql.extension.Extension;
2323
import io.asyncer.r2dbc.mysql.internal.util.InternalArrays;
2424
import io.netty.handler.ssl.SslContextBuilder;
25+
import io.netty.resolver.AddressResolverGroup;
2526
import org.jetbrains.annotations.Nullable;
2627
import org.reactivestreams.Publisher;
2728
import reactor.netty.resources.LoopResources;
@@ -127,6 +128,9 @@ public final class MySqlConnectionConfiguration {
127128
@Nullable
128129
private final Publisher<String> passwordPublisher;
129130

131+
@Nullable
132+
private final AddressResolverGroup<?> resolver;
133+
130134
private MySqlConnectionConfiguration(
131135
boolean isHost, String domain, int port, MySqlSslConfiguration ssl,
132136
boolean tcpKeepAlive, boolean tcpNoDelay, @Nullable Duration connectTimeout,
@@ -141,7 +145,8 @@ private MySqlConnectionConfiguration(
141145
int queryCacheSize, int prepareCacheSize,
142146
Set<CompressionAlgorithm> compressionAlgorithms, int zstdCompressionLevel,
143147
@Nullable LoopResources loopResources,
144-
Extensions extensions, @Nullable Publisher<String> passwordPublisher
148+
Extensions extensions, @Nullable Publisher<String> passwordPublisher,
149+
@Nullable AddressResolverGroup<?> resolver
145150
) {
146151
this.isHost = isHost;
147152
this.domain = domain;
@@ -171,6 +176,7 @@ private MySqlConnectionConfiguration(
171176
this.loopResources = loopResources == null ? TcpResources.get() : loopResources;
172177
this.extensions = extensions;
173178
this.passwordPublisher = passwordPublisher;
179+
this.resolver = resolver;
174180
}
175181

176182
/**
@@ -301,6 +307,11 @@ Publisher<String> getPasswordPublisher() {
301307
return passwordPublisher;
302308
}
303309

310+
@Nullable
311+
AddressResolverGroup<?> getResolver() {
312+
return resolver;
313+
}
314+
304315
@Override
305316
public boolean equals(Object o) {
306317
if (this == o) {
@@ -337,7 +348,8 @@ public boolean equals(Object o) {
337348
zstdCompressionLevel == that.zstdCompressionLevel &&
338349
Objects.equals(loopResources, that.loopResources) &&
339350
extensions.equals(that.extensions) &&
340-
Objects.equals(passwordPublisher, that.passwordPublisher);
351+
Objects.equals(passwordPublisher, that.passwordPublisher) &&
352+
Objects.equals(resolver, that.resolver);
341353
}
342354

343355
@Override
@@ -352,52 +364,41 @@ public int hashCode() {
352364
loadLocalInfilePath, localInfileBufferSize,
353365
queryCacheSize, prepareCacheSize,
354366
compressionAlgorithms, zstdCompressionLevel,
355-
loopResources, extensions, passwordPublisher);
367+
loopResources, extensions, passwordPublisher, resolver);
356368
}
357369

358370
@Override
359371
public String toString() {
360-
if (isHost) {
361-
return "MySqlConnectionConfiguration{host='" + domain + "', port=" + port + ", ssl=" + ssl +
362-
", tcpNoDelay=" + tcpNoDelay + ", tcpKeepAlive=" + tcpKeepAlive +
363-
", connectTimeout=" + connectTimeout +
372+
return "MySqlConnectionConfiguration{" +
373+
(isHost ? "host='" + domain + "', port=" + port + ", ssl=" + ssl +
374+
", tcpNoDelay=" + tcpNoDelay + ", tcpKeepAlive=" + tcpKeepAlive :
375+
"unixSocket='" + domain + "'") +
376+
buildCommonToStringPart() +
377+
'}';
378+
}
379+
380+
private String buildCommonToStringPart() {
381+
return ", connectTimeout=" + connectTimeout +
364382
", preserveInstants=" + preserveInstants +
365383
", connectionTimeZone=" + connectionTimeZone +
366384
", forceConnectionTimeZoneToSession=" + forceConnectionTimeZoneToSession +
367-
", zeroDateOption=" + zeroDateOption + ", user='" + user + "', password=" + password +
385+
", zeroDateOption=" + zeroDateOption +
386+
", user='" + user + "', password=" + password +
368387
", database='" + database + "', createDatabaseIfNotExist=" + createDatabaseIfNotExist +
369388
", preferPrepareStatement=" + preferPrepareStatement +
370389
", sessionVariables=" + sessionVariables +
371390
", lockWaitTimeout=" + lockWaitTimeout +
372391
", statementTimeout=" + statementTimeout +
373392
", loadLocalInfilePath=" + loadLocalInfilePath +
374393
", localInfileBufferSize=" + localInfileBufferSize +
375-
", queryCacheSize=" + queryCacheSize + ", prepareCacheSize=" + prepareCacheSize +
394+
", queryCacheSize=" + queryCacheSize +
395+
", prepareCacheSize=" + prepareCacheSize +
376396
", compressionAlgorithms=" + compressionAlgorithms +
377397
", zstdCompressionLevel=" + zstdCompressionLevel +
378398
", loopResources=" + loopResources +
379-
", extensions=" + extensions + ", passwordPublisher=" + passwordPublisher + '}';
380-
}
381-
382-
return "MySqlConnectionConfiguration{unixSocket='" + domain +
383-
"', connectTimeout=" + connectTimeout +
384-
", preserveInstants=" + preserveInstants +
385-
", connectionTimeZone=" + connectionTimeZone +
386-
", forceConnectionTimeZoneToSession=" + forceConnectionTimeZoneToSession +
387-
", zeroDateOption=" + zeroDateOption + ", user='" + user + "', password=" + password +
388-
", database='" + database + "', createDatabaseIfNotExist=" + createDatabaseIfNotExist +
389-
", preferPrepareStatement=" + preferPrepareStatement +
390-
", sessionVariables=" + sessionVariables +
391-
", lockWaitTimeout=" + lockWaitTimeout +
392-
", statementTimeout=" + statementTimeout +
393-
", loadLocalInfilePath=" + loadLocalInfilePath +
394-
", localInfileBufferSize=" + localInfileBufferSize +
395-
", queryCacheSize=" + queryCacheSize +
396-
", prepareCacheSize=" + prepareCacheSize +
397-
", compressionAlgorithms=" + compressionAlgorithms +
398-
", zstdCompressionLevel=" + zstdCompressionLevel +
399-
", loopResources=" + loopResources +
400-
", extensions=" + extensions + ", passwordPublisher=" + passwordPublisher + '}';
399+
", extensions=" + extensions +
400+
", passwordPublisher=" + passwordPublisher +
401+
", resolver=" + resolver;
401402
}
402403

403404
/**
@@ -494,6 +495,9 @@ public static final class Builder {
494495
@Nullable
495496
private Publisher<String> passwordPublisher;
496497

498+
@Nullable
499+
private AddressResolverGroup<?> resolver;
500+
497501
/**
498502
* Builds an immutable {@link MySqlConnectionConfiguration} with current options.
499503
*
@@ -528,7 +532,7 @@ public MySqlConnectionConfiguration build() {
528532
loadLocalInfilePath,
529533
localInfileBufferSize, queryCacheSize, prepareCacheSize,
530534
compressionAlgorithms, zstdCompressionLevel, loopResources,
531-
Extensions.from(extensions, autodetectExtensions), passwordPublisher);
535+
Extensions.from(extensions, autodetectExtensions), passwordPublisher, resolver);
532536
}
533537

534538
/**
@@ -1156,6 +1160,21 @@ public Builder passwordPublisher(Publisher<String> passwordPublisher) {
11561160
return this;
11571161
}
11581162

1163+
/**
1164+
* Sets the {@link AddressResolverGroup} for resolving host addresses.
1165+
* <p>
1166+
* This can be used to customize the DNS resolution mechanism, which is particularly useful in environments
1167+
* with specific DNS configuration needs or where a custom DNS resolver is required.
1168+
*
1169+
* @param resolver the resolver group to use for host address resolution.
1170+
* @return this {@link Builder}.
1171+
* @since 1.2.0
1172+
*/
1173+
public Builder resolver(AddressResolverGroup<?> resolver) {
1174+
this.resolver = resolver;
1175+
return this;
1176+
}
1177+
11591178
private SslMode requireSslMode() {
11601179
SslMode sslMode = this.sslMode;
11611180

r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ private static Mono<MySqlConnection> getMySqlConnection(
147147
configuration.isTcpNoDelay(),
148148
context,
149149
configuration.getConnectTimeout(),
150-
configuration.getLoopResources()
150+
configuration.getLoopResources(),
151+
configuration.getResolver()
151152
)).flatMap(client -> {
152153
// Lazy init database after handshake/login
153154
boolean deferDatabase = configuration.isCreateDatabaseIfNotExist();

r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProvider.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.asyncer.r2dbc.mysql.constant.SslMode;
2121
import io.asyncer.r2dbc.mysql.constant.ZeroDateOption;
2222
import io.netty.handler.ssl.SslContextBuilder;
23+
import io.netty.resolver.AddressResolverGroup;
2324
import io.r2dbc.spi.ConnectionFactory;
2425
import io.r2dbc.spi.ConnectionFactoryOptions;
2526
import io.r2dbc.spi.ConnectionFactoryProvider;
@@ -308,6 +309,17 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr
308309
*/
309310
public static final Option<Publisher<String>> PASSWORD_PUBLISHER = Option.valueOf("passwordPublisher");
310311

312+
/**
313+
* Option to set the {@link AddressResolverGroup} for resolving host addresses.
314+
* <p>
315+
* This can be used to customize the DNS resolution mechanism, which is particularly useful in environments
316+
* with specific DNS configuration needs or where a custom DNS resolver is required.
317+
* <p>
318+
*
319+
* @since 1.2.0
320+
*/
321+
public static final Option<AddressResolverGroup<?>> RESOLVER = Option.valueOf("resolver");
322+
311323
@Override
312324
public ConnectionFactory create(ConnectionFactoryOptions options) {
313325
requireNonNull(options, "connectionFactoryOptions must not be null");
@@ -389,6 +401,8 @@ static MySqlConnectionConfiguration setup(ConnectionFactoryOptions options) {
389401
.to(builder::loopResources);
390402
mapper.optional(PASSWORD_PUBLISHER).as(Publisher.class)
391403
.to(builder::passwordPublisher);
404+
mapper.optional(RESOLVER).as(AddressResolverGroup.class)
405+
.to(builder::resolver);
392406
mapper.optional(SESSION_VARIABLES).asArray(
393407
String[].class,
394408
Function.identity(),

r2dbc-mysql/src/main/java/io/asyncer/r2dbc/mysql/client/Client.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.asyncer.r2dbc.mysql.message.server.ServerMessage;
2323
import io.netty.buffer.ByteBufAllocator;
2424
import io.netty.channel.ChannelOption;
25+
import io.netty.resolver.AddressResolverGroup;
2526
import io.netty.util.internal.logging.InternalLogger;
2627
import io.netty.util.internal.logging.InternalLoggerFactory;
2728
import org.jetbrains.annotations.Nullable;
@@ -132,7 +133,7 @@ public interface Client {
132133
*/
133134
static Mono<Client> connect(MySqlSslConfiguration ssl, SocketAddress address, boolean tcpKeepAlive,
134135
boolean tcpNoDelay, ConnectionContext context, @Nullable Duration connectTimeout,
135-
LoopResources loopResources) {
136+
LoopResources loopResources, @Nullable AddressResolverGroup<?> resolver) {
136137
requireNonNull(ssl, "ssl must not be null");
137138
requireNonNull(address, "address must not be null");
138139
requireNonNull(context, "context must not be null");
@@ -150,6 +151,10 @@ static Mono<Client> connect(MySqlSslConfiguration ssl, SocketAddress address, bo
150151
tcpClient = tcpClient.option(ChannelOption.TCP_NODELAY, tcpNoDelay);
151152
}
152153

154+
if (resolver != null) {
155+
tcpClient = tcpClient.resolver(resolver);
156+
}
157+
153158
return tcpClient.remoteAddress(() -> address).connect()
154159
.map(conn -> new ReactorNettyClient(conn, ssl, context));
155160
}

r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionConfigurationTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import io.asyncer.r2dbc.mysql.constant.ZeroDateOption;
2323
import io.asyncer.r2dbc.mysql.extension.Extension;
2424
import io.netty.handler.ssl.SslContextBuilder;
25+
import io.netty.resolver.AddressResolverGroup;
26+
import io.netty.resolver.DefaultAddressResolverGroup;
2527
import org.assertj.core.api.ObjectAssert;
2628
import org.assertj.core.api.ThrowableTypeAssert;
2729
import org.jetbrains.annotations.Nullable;
@@ -207,6 +209,19 @@ void validPasswordSupplier() {
207209
.verifyComplete();
208210
}
209211

212+
@Test
213+
void validResolver() {
214+
final AddressResolverGroup<?> resolver = DefaultAddressResolverGroup.INSTANCE;
215+
AddressResolverGroup<?> resolverGroup = MySqlConnectionConfiguration.builder()
216+
.host(HOST)
217+
.user(USER)
218+
.resolver(resolver)
219+
.autodetectExtensions(false)
220+
.build()
221+
.getResolver();
222+
assertThat(resolverGroup).isSameAs(resolver);
223+
}
224+
210225
private static MySqlConnectionConfiguration unixSocketSslMode(SslMode sslMode) {
211226
return MySqlConnectionConfiguration.builder()
212227
.unixSocket(UNIX_SOCKET)

r2dbc-mysql/src/test/java/io/asyncer/r2dbc/mysql/MySqlConnectionFactoryProviderTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import io.asyncer.r2dbc.mysql.constant.SslMode;
2121
import io.asyncer.r2dbc.mysql.constant.ZeroDateOption;
2222
import io.netty.handler.ssl.SslContextBuilder;
23+
import io.netty.resolver.AddressResolverGroup;
24+
import io.netty.resolver.DefaultAddressResolverGroup;
2325
import io.r2dbc.spi.ConnectionFactories;
2426
import io.r2dbc.spi.ConnectionFactoryOptions;
2527
import io.r2dbc.spi.Option;
@@ -50,6 +52,7 @@
5052
import java.util.stream.Stream;
5153

5254
import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.PASSWORD_PUBLISHER;
55+
import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.RESOLVER;
5356
import static io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider.USE_SERVER_PREPARE_STATEMENT;
5457
import static io.r2dbc.spi.ConnectionFactoryOptions.CONNECT_TIMEOUT;
5558
import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE;
@@ -453,6 +456,19 @@ void validPasswordSupplier() {
453456
assertThat(ConnectionFactories.get(options)).isExactlyInstanceOf(MySqlConnectionFactory.class);
454457
}
455458

459+
@Test
460+
void validResolver() {
461+
final AddressResolverGroup<?> resolver = DefaultAddressResolverGroup.INSTANCE;
462+
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
463+
.option(DRIVER, "mysql")
464+
.option(HOST, "127.0.0.1")
465+
.option(USER, "root")
466+
.option(RESOLVER, resolver)
467+
.build();
468+
469+
assertThat(ConnectionFactories.get(options)).isExactlyInstanceOf(MySqlConnectionFactory.class);
470+
}
471+
456472
@Test
457473
void allConfigurationOptions() {
458474
List<String> exceptConfigs = Arrays.asList(

0 commit comments

Comments
 (0)