Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c64b9c7
refactor: remove dead local-DC contact-point compatibility check (DRI…
nikagra Jul 23, 2026
43e7a16
feat: keep contact points unresolved; deprecate RESOLVE_CONTACT_POINT…
nikagra Jul 23, 2026
767674f
feat: add EndPoint.resolveAll() for multi-address DNS expansion (DRIV…
nikagra Jul 23, 2026
023f791
feat: resolve endpoints off the event loop with multi-IP fallback; de…
nikagra Jul 23, 2026
2770480
fix: gate contact-point reconnection fallback by topology monitor and…
nikagra Jul 23, 2026
19359f9
docs: document contact-point DNS expansion in the upgrade guide (DRIV…
nikagra Jul 23, 2026
ecd9497
fix: rotate SNI resolveAll() with a dedicated counter (DRIVER-201)
nikagra Jul 23, 2026
4bf038c
fix: aggregate multi-address connect failures; cover fallback with te…
nikagra Jul 23, 2026
33be476
docs: fix contradictory resolve-contact-points doc; clarify reconnect…
nikagra Jul 23, 2026
d3b3978
fix: bound the DNS-resolver thread pool in ChannelFactory (DRIVER-201)
nikagra Jul 27, 2026
a6c554d
fix: scope protocol-version negotiation history per candidate address…
nikagra Jul 27, 2026
dc40c04
docs: clarify control-reconnection race window and reresolvesNodeAddr…
nikagra Jul 27, 2026
46ba69b
test: remove vestigial RESOLVE_CONTACT_POINTS config from MockResolve…
nikagra Jul 27, 2026
648c824
fix: resolve candidates via Netty's resolver and pin the connected ad…
nikagra Jul 29, 2026
603fa03
test: cover the removed local-DC contact-point check (CUSTOMER-588)
nikagra Jul 29, 2026
b83a8ae
fix: do not pin an endpoint to the address it already holds (DRIVER-201)
nikagra Jul 29, 2026
5b79b63
refactor: make resolution a connection-layer concern, drop the EndPoi…
nikagra Jul 30, 2026
97ad981
test: assert on host strings, not resolved IPs, in ClientRoutesIT (DR…
nikagra Jul 30, 2026
a0328a7
fix: complete the connect future on failures in resolver and bootstra…
nikagra Jul 31, 2026
ea8e1e2
fix: preserve the queried hostname on resolver-returned addresses (DR…
nikagra Jul 31, 2026
092d492
fix: stop trying further addresses after protocol-version rejection (…
nikagra Jul 31, 2026
73f9342
fix: resolve and connect on a single event loop per connection attemp…
nikagra Jul 31, 2026
41f401d
fix: rotate a hostname's addresses with a per-name counter (DRIVER-201)
nikagra Jul 31, 2026
426d950
docs: pin down the afterBootstrapInitialized contract and warn on str…
nikagra Jul 31, 2026
f3b0703
test: drop the stale NETTY_DAEMON stub from ChannelFactoryTestBase (D…
nikagra Jul 31, 2026
00e2785
fix: align pinTo type checks and no-op shortcuts across endpoint impl…
nikagra Jul 31, 2026
1b3aea1
fix: keep trying addresses of an unidentified endpoint after a versio…
nikagra Aug 3, 2026
f1abf32
fix: always preserve the queried hostname on resolver-returned addres…
nikagra Aug 3, 2026
0f6d91a
fix: scope the address rotation counters to the session and bound the…
nikagra Aug 3, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.datastax.oss.protocol.internal.util.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.security.PrivilegedActionException;
Expand Down Expand Up @@ -319,7 +320,7 @@ protected GssApiAuthenticator(
SUPPORTED_MECHANISMS,
options.getAuthorizationId(),
protocol,
((InetSocketAddress) endPoint.resolve()).getAddress().getCanonicalHostName(),
serverName(endPoint),
options.getSaslProperties(),
null);
} catch (LoginException | SaslException e) {
Expand All @@ -328,6 +329,23 @@ protected GssApiAuthenticator(
this.endPoint = endPoint;
}

/**
* The host name to build the Kerberos service principal from.
*
* <p>Prefers the canonical name of the resolved address, which is what Kerberos expects. The
* driver's own endpoints always hand this a resolved address — the channel carries an endpoint
* bound to the address it connected to (see {@code PinnableEndPoint}) — but a custom {@link
* EndPoint} implementation may still yield an unresolved one, in which case {@code
* getAddress()} is null. Fall back to the host string rather than throwing a {@link
* NullPointerException}: the hostname is usually the right service name anyway, and a failed
* reverse lookup should not take authentication down.
*/
private static String serverName(EndPoint endPoint) {
InetSocketAddress address = (InetSocketAddress) endPoint.resolve();
InetAddress inetAddress = address.getAddress();
return inetAddress != null ? inetAddress.getCanonicalHostName() : address.getHostString();
}

@NonNull
@Override
protected ByteBuffer getMechanism() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,8 +701,13 @@ public enum DefaultDriverOption implements DriverOption {
CONTROL_CONNECTION_AGREEMENT_WARN("advanced.control-connection.schema-agreement.warn-on-failure"),

/**
* Whether to forcibly add original contact points held by MetadataManager to the reconnection
* plan, in case there is no live nodes available according to LBP. Experimental.
* Whether to append the original contact points held by MetadataManager to the reconnection plan,
* after the live nodes reported by the load balancing policy. Defaults to {@code true}.
*
* <p>This is also the driver's DNS re-resolution path: contact points are expanded to their
* current DNS IPs at query-plan time, whereas metadata nodes hold an already-resolved endpoint
* that is never re-resolved. Keeping this enabled lets control-connection reconnects re-resolve
* the original hostnames and pick up new IPs once the live-node plan is exhausted.
*
* <p>Value-type: boolean
*/
Expand Down Expand Up @@ -837,7 +842,11 @@ public enum DefaultDriverOption implements DriverOption {
* Whether to resolve the addresses passed to `basic.contact-points`.
*
* <p>Value-type: boolean
*
* @deprecated Contact points are now always kept as unresolved hostnames and expanded to all
* their DNS-mapped IPs lazily at connection time. Setting this option has no effect.
*/
@Deprecated
Comment thread
nikagra marked this conversation as resolved.
RESOLVE_CONTACT_POINTS("advanced.resolve-contact-points"),

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ protected static void fillWithDriverDefaults(OptionsMap map) {
map.put(TypedDriverOption.CONTROL_CONNECTION_AGREEMENT_INTERVAL, Duration.ofMillis(200));
map.put(TypedDriverOption.CONTROL_CONNECTION_AGREEMENT_TIMEOUT, Duration.ofSeconds(10));
map.put(TypedDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN, true);
map.put(TypedDriverOption.CONTROL_CONNECTION_RECONNECT_CONTACT_POINTS, false);
map.put(TypedDriverOption.CONTROL_CONNECTION_RECONNECT_CONTACT_POINTS, true);
map.put(TypedDriverOption.PREPARE_ON_ALL_NODES, true);
map.put(TypedDriverOption.REPREPARE_ENABLED, true);
map.put(TypedDriverOption.REPREPARE_CHECK_SYSTEM_TABLE, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,15 @@ public String toString() {
public static final TypedDriverOption<Boolean> CONTROL_CONNECTION_AGREEMENT_WARN =
new TypedDriverOption<>(
DefaultDriverOption.CONTROL_CONNECTION_AGREEMENT_WARN, GenericType.BOOLEAN);
/** Whether to forcibly try original contacts if no live nodes are available */
/**
* Whether to append the original contact points to the control-connection reconnection plan,
* after the live nodes reported by the load balancing policy (defaults to {@code true}).
*
* <p>Contact points are appended as-is (unresolved hostnames); each is expanded to all of its
* current DNS IPs at connection time, which is also the driver's DNS re-resolution mechanism. The
* append is skipped for topology monitors that re-resolve node addresses themselves (such as the
* cloud/proxy monitors).
*/
public static final TypedDriverOption<Boolean> CONTROL_CONNECTION_RECONNECT_CONTACT_POINTS =
new TypedDriverOption<>(
DefaultDriverOption.CONTROL_CONNECTION_RECONNECT_CONTACT_POINTS, GenericType.BOOLEAN);
Expand Down Expand Up @@ -664,7 +672,13 @@ public String toString() {
/** The coalescer reschedule interval. */
public static final TypedDriverOption<Duration> COALESCER_INTERVAL =
new TypedDriverOption<>(DefaultDriverOption.COALESCER_INTERVAL, GenericType.DURATION);
/** Whether to resolve the addresses passed to `basic.contact-points`. */
/**
* Whether to resolve the addresses passed to `basic.contact-points`.
*
* @deprecated Contact points are now always kept as unresolved hostnames and expanded to all
* their DNS-mapped IPs lazily at connection time. Setting this option has no effect.
*/
@Deprecated
public static final TypedDriverOption<Boolean> RESOLVE_CONTACT_POINTS =
new TypedDriverOption<>(DefaultDriverOption.RESOLVE_CONTACT_POINTS, GenericType.BOOLEAN);
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,44 @@
package com.datastax.oss.driver.api.core.metadata;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
* Encapsulates the information needed to open connections to a node.
*
* <p>By default, the driver assumes plain TCP connections, and this is just a wrapper around an
* {@link InetSocketAddress}. However, more complex deployment scenarios might use a custom
* {@link java.net.InetSocketAddress}. However, more complex deployment scenarios might use a custom
* implementation that contains additional information; for example, if the nodes are accessed
* through a proxy with SNI routing, an SNI server name is needed in addition to the proxy address.
*/
public interface EndPoint {

/**
* Resolves this instance to a socket address.
* Resolves this instance to the socket address connections should be opened to.
*
* <p>This will be called each time the driver opens a new connection to the node. The returned
* address cannot be null.
*
* <p><b>Returning a hostname is fine, and is how multi-address support works.</b> The returned
* address need not be resolved: an {@linkplain java.net.InetSocketAddress#isUnresolved()
* unresolved} {@link java.net.InetSocketAddress} is expanded by the driver to <b>every</b>
* address the name maps to, and each one is tried in turn until a connection succeeds. That is
* what {@link com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint} does for contact
* points backed by a hostname, so a single unreachable IP behind a multi-record name no longer
* fails the connection.
*
* <p><b>Implementations must not resolve names themselves, and must not block.</b> The driver
* calls this from its admin event loop, and it performs the expansion through Netty's configured
* {@code AddressResolverGroup} — the same resolver an unresolved address reaches when it is
* handed to {@code Bootstrap.connect()}. Looking the name up here instead (for example with
* {@link java.net.InetAddress#getAllByName(String)}) would both block that loop and bypass a
* custom resolver installed via {@code NettyOptions#afterBootstrapInitialized(Bootstrap)}.
*
* @apiNote <b>Timeout note:</b> when a name expands to several addresses they are tried in
* sequence, so if every attempt times out the worst-case time before the node is declared
* unreachable is {@code N × advanced.connection.connect-timeout}. In practice DNS round-robin
* entries have only a small number of records, so this is rarely a concern, but it is worth
* bearing in mind when configuring connect timeouts.
*/
@NonNull
SocketAddress resolve();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,15 @@ protected DriverConfigLoader defaultConfigLoader(@Nullable ClassLoader classLoad
* <p>Contact points can also be provided statically in the configuration. If both are specified,
* they will be merged. If both are absent, the driver will default to 127.0.0.1:9042.
*
* <p>Contrary to the configuration, DNS names with multiple A-records will not be handled here.
* If you need that, extract them manually with {@link java.net.InetAddress#getAllByName(String)}
* before calling this method. Similarly, if you need connect addresses to stay unresolved, make
* sure you pass unresolved instances here (see {@code advanced.resolve-contact-points} in the
* configuration for more explanations).
* <p>The driver automatically expands any contact point backed by an unresolved hostname to all
* its DNS-mapped IPs at connection time (through Netty's configured resolver, so a custom {@code
* AddressResolverGroup} still applies), so passing a single hostname is sufficient to try all its
* IPs on initial connect. This applies equally to hostnames provided here programmatically (build
* an unresolved {@link InetSocketAddress} with {@link InetSocketAddress#createUnresolved(String,
* int)} to opt in) and to hostnames specified in the configuration. An already-resolved address
* passed here (the common case when constructing an {@code InetSocketAddress} directly from a
* hostname, which resolves eagerly) is used as provided, with no further expansion. The {@code
* advanced.resolve-contact-points} option is deprecated and has no effect.
*/
@NonNull
public SelfT addContactPoints(@NonNull Collection<InetSocketAddress> contactPoints) {
Expand Down Expand Up @@ -957,11 +961,10 @@ protected final CompletionStage<CqlSession> buildDefaultSessionAsync() {
programmaticArguments = programmaticArgumentsBuilder.build();
}

boolean resolveAddresses =
defaultConfig.getBoolean(DefaultDriverOption.RESOLVE_CONTACT_POINTS, false);

// RESOLVE_CONTACT_POINTS is deprecated: contact points are always kept as unresolved
// hostnames, and expanded to all their DNS IPs at connection time by ChannelFactory.
Set<EndPoint> contactPoints =
ContactPoints.merge(programmaticContactPoints, configContactPoints, resolveAddresses);
ContactPoints.merge(programmaticContactPoints, configContactPoints, false);
Comment thread
dkropachev marked this conversation as resolved.

if (keyspace == null && defaultConfig.isDefined(DefaultDriverOption.SESSION_KEYSPACE)) {
keyspace =
Expand Down
Loading
Loading