Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions driver-core/src/main/com/mongodb/ConnectionString.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import java.util.stream.Stream;

import static com.mongodb.MongoCredential.ALLOWED_HOSTS_KEY;
import static com.mongodb.internal.connection.DomainNameUtils.normalizeSrvAllowedHostsSuffix;
import static com.mongodb.internal.connection.OidcAuthenticator.OidcValidator.validateCreateOidcCredential;
import static java.lang.String.format;
import static java.util.Arrays.asList;
Expand Down Expand Up @@ -265,6 +266,8 @@
* <ul>
* <li>{@code srvServiceName=string}: The SRV service name. See {@link ClusterSettings#getSrvServiceName()} for details.</li>
* <li>{@code srvMaxHosts=number}: The maximum number of hosts from the SRV record to connect to.</li>
* <li>{@code srvAllowedHostsSuffix=string}: The hostname suffix used to validate hosts returned via SRV lookup, replacing the domain
* inferred from the SRV host name. Only valid with the mongodb+srv protocol.</li>
* </ul>
* <p>General configuration:</p>
* <ul>
Expand Down Expand Up @@ -302,6 +305,7 @@ public class ConnectionString {

private Integer srvMaxHosts;
private String srvServiceName;
private String srvAllowedHostsSuffix;
private Boolean directConnection;
private Boolean loadBalanced;
private ReadPreference readPreference;
Expand Down Expand Up @@ -479,6 +483,14 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
throw new IllegalArgumentException("srvServiceName can only be specified with mongodb+srv protocol");
}

if (!isSrvProtocol && srvAllowedHostsSuffix != null) {
throw new IllegalArgumentException("srvAllowedHostsSuffix can only be specified with mongodb+srv protocol");
}

if (srvAllowedHostsSuffix != null) {
srvAllowedHostsSuffix = normalizeSrvAllowedHostsSuffix(srvAllowedHostsSuffix);
}

if (directConnection != null && directConnection) {
if (isSrvProtocol) {
throw new IllegalArgumentException("Direct connections are not supported when using mongodb+srv protocol");
Expand Down Expand Up @@ -568,6 +580,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient

GENERAL_OPTIONS_KEYS.add("srvmaxhosts");
GENERAL_OPTIONS_KEYS.add("srvservicename");
GENERAL_OPTIONS_KEYS.add("srvallowedhostssuffix");

COMPRESSOR_KEYS.add("compressors");
COMPRESSOR_KEYS.add("zlibcompressionlevel");
Expand Down Expand Up @@ -724,6 +737,9 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
case "srvservicename":
srvServiceName = value;
break;
case "srvallowedhostssuffix":
srvAllowedHostsSuffix = value;
break;
default:
break;
}
Expand Down Expand Up @@ -1355,6 +1371,22 @@ public String getSrvServiceName() {
return srvServiceName;
}

/**
* Gets the SRV allowed hosts suffix.
*
* <p>If present, its value is used as the domain for SRV host name validation, replacing the domain inferred from
* the SRV host name. The value is normalized: a leading {@code "."} is prepended if absent, so the returned value
* always begins with {@code "."}.</p>
*
* @return the normalized SRV allowed hosts suffix, always beginning with {@code "."}. Defaults to null.
* @since 5.9
* @see ClusterSettings#getSrvAllowedHostsSuffix()
*/
@Nullable
public String getSrvAllowedHostsSuffix() {
return srvAllowedHostsSuffix;
}

/**
* Gets the list of hosts
*
Expand Down Expand Up @@ -1820,7 +1852,8 @@ public boolean equals(final Object o) {
&& Objects.equals(compressorList, that.compressorList)
&& Objects.equals(uuidRepresentation, that.uuidRepresentation)
&& Objects.equals(srvServiceName, that.srvServiceName)
&& Objects.equals(srvMaxHosts, that.srvMaxHosts);
&& Objects.equals(srvMaxHosts, that.srvMaxHosts)
&& Objects.equals(srvAllowedHostsSuffix, that.srvAllowedHostsSuffix);
}

@Override
Expand All @@ -1829,7 +1862,7 @@ public int hashCode() {
writeConcern, retryWrites, retryReads, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled,
sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency,
serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost,
proxyPort, proxyUsername, proxyPassword);
serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts,
srvAllowedHostsSuffix, proxyHost, proxyPort, proxyUsername, proxyPassword);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import static com.mongodb.assertions.Assertions.isTrueArgument;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.connection.DomainNameUtils.normalizeSrvAllowedHostsSuffix;
import static com.mongodb.internal.connection.ServerAddressHelper.createServerAddress;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
Expand All @@ -50,6 +51,7 @@ public final class ClusterSettings {
private final String srvHost;
private final Integer srvMaxHosts;
private final String srvServiceName;
private final String srvAllowedHostsSuffix;
private final List<ServerAddress> hosts;
private final ClusterConnectionMode mode;
private final ClusterType requiredClusterType;
Expand Down Expand Up @@ -88,6 +90,7 @@ public static final class Builder {
private String srvHost;
private Integer srvMaxHosts;
private String srvServiceName = "mongodb";
private String srvAllowedHostsSuffix;
private List<ServerAddress> hosts = DEFAULT_HOSTS;
private ClusterConnectionMode mode;
private ClusterType requiredClusterType = ClusterType.UNKNOWN;
Expand All @@ -114,6 +117,7 @@ public Builder applySettings(final ClusterSettings clusterSettings) {
srvHost = clusterSettings.srvHost;
srvServiceName = clusterSettings.srvServiceName;
srvMaxHosts = clusterSettings.srvMaxHosts;
srvAllowedHostsSuffix = clusterSettings.srvAllowedHostsSuffix;
hosts = clusterSettings.hosts;
mode = clusterSettings.mode;
requiredReplicaSetName = clusterSettings.requiredReplicaSetName;
Expand Down Expand Up @@ -185,6 +189,26 @@ public Builder srvServiceName(final String srvServiceName) {
return this;
}

/**
* Sets the SRV allowed hosts suffix used to validate hosts returned via SRV lookup.
*
* <p>If set, its value is used as the domain for SRV host name validation, replacing the domain inferred from
* the SRV host name. The value is normalized: a leading {@code "."} is prepended if absent, so
* {@link #getSrvAllowedHostsSuffix()} always returns a value beginning with {@code "."}. This setting is only
* used with SRV. Specifying an overly broad suffix (for example a bare TLD) weakens SRV host name validation and
* is the responsibility of the caller.</p>
*
* @param srvAllowedHostsSuffix the SRV allowed hosts suffix; may not be null or empty
* @return this
* @since 5.9
* @see #getSrvAllowedHostsSuffix()
*/
public Builder srvAllowedHostsSuffix(final String srvAllowedHostsSuffix) {
notNull("srvAllowedHostsSuffix", srvAllowedHostsSuffix);
this.srvAllowedHostsSuffix = normalizeSrvAllowedHostsSuffix(srvAllowedHostsSuffix);
return this;
}

/**
* Sets the hosts for the cluster. Any duplicate server addresses are removed from the list.
*
Expand Down Expand Up @@ -328,6 +352,7 @@ public Builder applyConnectionString(final ConnectionString connectionString) {
mode(ClusterConnectionMode.LOAD_BALANCED);
if (connectionString.isSrvProtocol()) {
srvHost(connectionString.getHosts().get(0));
applySrvConnectionStringOptions(connectionString);
} else {
hosts(singletonList(createServerAddress(connectionString.getHosts().get(0))));
}
Expand All @@ -338,10 +363,7 @@ public Builder applyConnectionString(final ConnectionString connectionString) {
if (srvMaxHosts != null) {
srvMaxHosts(srvMaxHosts);
}
String srvServiceName = connectionString.getSrvServiceName();
if (srvServiceName != null) {
srvServiceName(srvServiceName);
}
applySrvConnectionStringOptions(connectionString);
} else if (directConnection != null) {
mode(directConnection ? ClusterConnectionMode.SINGLE : ClusterConnectionMode.MULTIPLE);
List<String> hosts = directConnection ? singletonList(connectionString.getHosts().get(0)) : connectionString.getHosts();
Expand All @@ -367,6 +389,19 @@ public Builder applyConnectionString(final ConnectionString connectionString) {
return this;
}

// Applies the SRV options shared by the load-balanced and multi-server SRV paths. srvMaxHosts is intentionally
// not applied here, as it is only valid for the multi-server path.
private void applySrvConnectionStringOptions(final ConnectionString connectionString) {
String srvServiceName = connectionString.getSrvServiceName();
if (srvServiceName != null) {
srvServiceName(srvServiceName);
}
String srvAllowedHostsSuffix = connectionString.getSrvAllowedHostsSuffix();
if (srvAllowedHostsSuffix != null) {
srvAllowedHostsSuffix(srvAllowedHostsSuffix);
}
}

/**
* Build the settings from the builder.
*
Expand Down Expand Up @@ -421,6 +456,21 @@ public String getSrvServiceName() {
return srvServiceName;
}

/**
* Gets the SRV allowed hosts suffix used to validate hosts returned via SRV lookup.
*
* <p>If present, its value is used as the domain for SRV host name validation, replacing the domain inferred from
* the SRV host name. The value is normalized to always begin with {@code "."}.</p>
*
* @return the normalized SRV allowed hosts suffix, always beginning with {@code "."}. Defaults to null.
* @since 5.9
* @see Builder#srvAllowedHostsSuffix(String)
*/
@Nullable
public String getSrvAllowedHostsSuffix() {
return srvAllowedHostsSuffix;
}

/**
* Gets the seed list of hosts for the cluster.
*
Expand Down Expand Up @@ -554,6 +604,7 @@ public boolean equals(final Object o) {
&& Objects.equals(srvHost, that.srvHost)
&& Objects.equals(srvMaxHosts, that.srvMaxHosts)
&& srvServiceName.equals(that.srvServiceName)
&& Objects.equals(srvAllowedHostsSuffix, that.srvAllowedHostsSuffix)
&& hosts.equals(that.hosts)
&& mode == that.mode
&& requiredClusterType == that.requiredClusterType
Expand All @@ -564,8 +615,8 @@ public boolean equals(final Object o) {

@Override
public int hashCode() {
return Objects.hash(srvHost, srvMaxHosts, srvServiceName, hosts, mode, requiredClusterType, requiredReplicaSetName, serverSelector,
localThresholdMS, serverSelectionTimeoutMS, clusterListeners);
return Objects.hash(srvHost, srvMaxHosts, srvServiceName, srvAllowedHostsSuffix, hosts, mode, requiredClusterType,
requiredReplicaSetName, serverSelector, localThresholdMS, serverSelectionTimeoutMS, clusterListeners);
}

@Override
Expand All @@ -575,6 +626,7 @@ public String toString() {
+ (srvHost == null ? "" : ", srvHost=" + srvHost)
+ (srvServiceName == null ? "" : ", srvServiceName=" + srvServiceName)
+ (srvMaxHosts == null ? "" : ", srvMaxHosts=" + srvMaxHosts)
+ (srvAllowedHostsSuffix == null ? "" : ", srvAllowedHostsSuffix=" + srvAllowedHostsSuffix)
+ ", mode=" + mode
+ ", requiredClusterType=" + requiredClusterType
+ ", requiredReplicaSetName='" + requiredReplicaSetName + '\''
Expand Down Expand Up @@ -624,6 +676,7 @@ private ClusterSettings(final Builder builder) {
srvHost = builder.srvHost;
srvMaxHosts = builder.srvMaxHosts;
srvServiceName = builder.srvServiceName;
srvAllowedHostsSuffix = builder.srvAllowedHostsSuffix;
hosts = builder.hosts;
requiredReplicaSetName = builder.requiredReplicaSetName;
if (builder.mode != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.mongodb.internal.diagnostics.logging.Logger;
import com.mongodb.internal.diagnostics.logging.Loggers;
import com.mongodb.internal.dns.DnsResolver;
import com.mongodb.lang.Nullable;

import java.util.Collections;
import java.util.HashSet;
Expand All @@ -38,18 +39,22 @@ class DefaultDnsSrvRecordMonitor implements DnsSrvRecordMonitor {

private final String hostName;
private final String srvServiceName;
@Nullable
private final String srvAllowedHostsSuffix;
private final long rescanFrequencyMillis;
private final long noRecordsRescanFrequencyMillis;
private final DnsSrvRecordInitializer dnsSrvRecordInitializer;
private final DnsResolver dnsResolver;
private final Thread monitorThread;
private volatile boolean isClosed;

DefaultDnsSrvRecordMonitor(final String hostName, final String srvServiceName, final long rescanFrequencyMillis, final long noRecordsRescanFrequencyMillis,
DefaultDnsSrvRecordMonitor(final String hostName, final String srvServiceName, @Nullable final String srvAllowedHostsSuffix,
final long rescanFrequencyMillis, final long noRecordsRescanFrequencyMillis,
final DnsSrvRecordInitializer dnsSrvRecordInitializer, final ClusterId clusterId,
final DnsResolver dnsResolver) {
this.hostName = hostName;
this.srvServiceName = srvServiceName;
this.srvAllowedHostsSuffix = srvAllowedHostsSuffix;
this.rescanFrequencyMillis = rescanFrequencyMillis;
this.noRecordsRescanFrequencyMillis = noRecordsRescanFrequencyMillis;
this.dnsSrvRecordInitializer = dnsSrvRecordInitializer;
Expand Down Expand Up @@ -78,7 +83,8 @@ public void run() {
try {
while (!isClosed && shouldContinueMonitoring()) {
try {
List<String> resolvedHostNames = dnsResolver.resolveHostFromSrvRecords(hostName, srvServiceName);
List<String> resolvedHostNames = dnsResolver.resolveHostFromSrvRecords(hostName, srvServiceName,
srvAllowedHostsSuffix);
Set<ServerAddress> hosts = createServerAddressSet(resolvedHostNames);

if (isClosed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ public DefaultDnsSrvRecordMonitorFactory(final ClusterId clusterId, final Server
}

@Override
public DnsSrvRecordMonitor create(final String hostName, final String srvServiceName, final DnsSrvRecordInitializer dnsSrvRecordInitializer) {
return new DefaultDnsSrvRecordMonitor(hostName, srvServiceName, DEFAULT_RESCAN_FREQUENCY_MILLIS, noRecordsRescanFrequency,
dnsSrvRecordInitializer, clusterId, new DefaultDnsResolver(dnsClient));
public DnsSrvRecordMonitor create(final String hostName, final String srvServiceName,
@Nullable final String srvAllowedHostsSuffix, final DnsSrvRecordInitializer dnsSrvRecordInitializer) {
return new DefaultDnsSrvRecordMonitor(hostName, srvServiceName, srvAllowedHostsSuffix, DEFAULT_RESCAN_FREQUENCY_MILLIS,
noRecordsRescanFrequency, dnsSrvRecordInitializer, clusterId, new DefaultDnsResolver(dnsClient));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public DnsMultiServerCluster(final ClusterId clusterId, final ClusterSettings se
final DnsSrvRecordMonitorFactory dnsSrvRecordMonitorFactory) {
super(clusterId, settings, serverFactory, clientMetadata);
dnsSrvRecordMonitor = dnsSrvRecordMonitorFactory.create(assertNotNull(settings.getSrvHost()), settings.getSrvServiceName(),
settings.getSrvAllowedHostsSuffix(),
new DnsSrvRecordInitializer() {
private volatile boolean initialized;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

package com.mongodb.internal.connection;

import com.mongodb.lang.Nullable;

/**
* <p>This class is not part of the public API and may be removed or changed at any time</p>
*/
public interface DnsSrvRecordMonitorFactory {
DnsSrvRecordMonitor create(String hostName, String srvServiceName, DnsSrvRecordInitializer dnsSrvRecordInitializer);
DnsSrvRecordMonitor create(String hostName, String srvServiceName, @Nullable String srvAllowedHostsSuffix,
DnsSrvRecordInitializer dnsSrvRecordInitializer);
}
Loading