Skip to content

Commit

Permalink
Fix for initial routers DNS resolution
Browse files Browse the repository at this point in the history
This update fixes the following issue: neo4j#833

The driver will resolve all initial router domain names to IPs and will try them all until one is successful.

Additional items:
- a new config option to make the domain name resolution configurable, which is currently used for testing
- the testkit backend has been updated to support the domain name resolution configuration (a new test has been added to testkit to cover the issue described above)
- the testkit backend has been updated to support connection timeout driver configuration
- several tests have been updated
  • Loading branch information
injectives committed Mar 10, 2021
1 parent 47db433 commit 371e202
Show file tree
Hide file tree
Showing 29 changed files with 467 additions and 177 deletions.
30 changes: 30 additions & 0 deletions driver/src/main/java/org/neo4j/driver/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import org.neo4j.driver.internal.DefaultDomainNameResolver;
import org.neo4j.driver.internal.RevocationStrategy;
import org.neo4j.driver.internal.SecuritySettings;
import org.neo4j.driver.internal.async.pool.PoolSettings;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.net.DomainNameResolver;
import org.neo4j.driver.net.ServerAddressResolver;
import org.neo4j.driver.util.Immutable;

Expand Down Expand Up @@ -88,6 +90,7 @@ public class Config
private final int connectionTimeoutMillis;
private final RetrySettings retrySettings;
private final ServerAddressResolver resolver;
private final DomainNameResolver domainNameResolver;

private final boolean isMetricsEnabled;
private final int eventLoopThreads;
Expand All @@ -112,6 +115,7 @@ private Config( ConfigBuilder builder )
this.routingTablePurgeDelayMillis = builder.routingTablePurgeDelayMillis;
this.retrySettings = builder.retrySettings;
this.resolver = builder.resolver;
this.domainNameResolver = builder.domainNameResolver;
this.fetchSize = builder.fetchSize;

this.eventLoopThreads = builder.eventLoopThreads;
Expand Down Expand Up @@ -202,6 +206,16 @@ public ServerAddressResolver resolver()
return resolver;
}

/**
* Domain name resolver.
*
* @return the resolver to use.
*/
public DomainNameResolver domainNameResolver()
{
return domainNameResolver;
}

/**
* Start building a {@link Config} object using a newly created builder.
*
Expand Down Expand Up @@ -283,6 +297,7 @@ public static class ConfigBuilder
private int connectionTimeoutMillis = (int) TimeUnit.SECONDS.toMillis( 30 );
private RetrySettings retrySettings = RetrySettings.DEFAULT;
private ServerAddressResolver resolver;
private DomainNameResolver domainNameResolver = new DefaultDomainNameResolver();
private boolean isMetricsEnabled = false;
private long fetchSize = FetchSizeUtil.DEFAULT_FETCH_SIZE;
private int eventLoopThreads = 0;
Expand Down Expand Up @@ -695,8 +710,23 @@ public ConfigBuilder withResolver( ServerAddressResolver resolver )
return this;
}

/**
* Specify a custom domain name resolver used by the driver to resolve domain names.
* <p>
* Default implementation uses the {@link InetAddress#getAllByName(String)}.
*
* @param domainNameResolver the resolver to use.
* @return this builder.
*/
public ConfigBuilder withDomainNameResolver( DomainNameResolver domainNameResolver )
{
this.domainNameResolver = Objects.requireNonNull( domainNameResolver, "domainNameResolver" );
return this;
}

/**
* Enable driver metrics. The metrics can be obtained afterwards via {@link Driver#metrics()}.
*
* @return this builder.
*/
public ConfigBuilder withDriverMetrics()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,11 @@
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import org.neo4j.driver.net.ServerAddress;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

/**
* Holds a host and port pair that denotes a Bolt server address.
Expand All @@ -44,8 +40,6 @@ public class BoltServerAddress implements ServerAddress
private final int port;
private final String stringValue;

private InetAddress resolved;

public BoltServerAddress( String address )
{
this( uriFrom( address ) );
Expand All @@ -57,16 +51,10 @@ public BoltServerAddress( URI uri )
}

public BoltServerAddress( String host, int port )
{
this( host, null, port );
}

private BoltServerAddress( String host, InetAddress resolved, int port )
{
this.host = requireNonNull( host, "host" );
this.resolved = resolved;
this.port = requireValidPort( port );
this.stringValue = resolved != null ? String.format( "%s(%s):%d", host, resolved.getHostAddress(), port ) : String.format( "%s:%d", host, port );
this.stringValue = String.format( "%s:%d", host, port );
}

public static BoltServerAddress from( ServerAddress address )
Expand Down Expand Up @@ -112,33 +100,7 @@ public String toString()
*/
public SocketAddress toSocketAddress()
{
return resolved == null ? new InetSocketAddress( host, port ) : new InetSocketAddress( resolved, port );
}

/**
* Resolve the host name down to an IP address
*
* @return a new address instance
* @throws UnknownHostException if no IP address for the host could be found
* @see InetAddress#getByName(String)
*/
public BoltServerAddress resolve() throws UnknownHostException
{
return new BoltServerAddress( host, InetAddress.getByName( host ), port );
}

/**
* Resolve the host name down to all IP addresses that can be resolved to
*
* @return an array of new address instances that holds resolved addresses
* @throws UnknownHostException if no IP address for the host could be found
* @see InetAddress#getAllByName(String)
*/
public List<BoltServerAddress> resolveAll() throws UnknownHostException
{
return Stream.of( InetAddress.getAllByName( host ) )
.map( address -> new BoltServerAddress( host, address, port ) )
.collect( toList() );
return new InetSocketAddress( host, port );
}

@Override
Expand All @@ -153,11 +115,6 @@ public int port()
return port;
}

public boolean isResolved()
{
return resolved != null;
}

private static String hostFrom( URI uri )
{
String host = uri.getHost();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed 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
*
* http://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 org.neo4j.driver.internal;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.neo4j.driver.net.DomainNameResolver;

public class DefaultDomainNameResolver implements DomainNameResolver
{
@Override
public InetAddress[] resolve( String name ) throws UnknownHostException
{
return InetAddress.getAllByName( name );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ protected static MetricsProvider createDriverMetrics( Config config, Clock clock
protected ChannelConnector createConnector( ConnectionSettings settings, SecurityPlan securityPlan,
Config config, Clock clock, RoutingContext routingContext )
{
return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext );
return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext, config.domainNameResolver() );
}

private InternalDriver createDriver( URI uri, SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool,
Expand Down Expand Up @@ -210,7 +210,7 @@ protected LoadBalancer createLoadBalancer( BoltServerAddress address, Connection
LoadBalancingStrategy loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy( connectionPool, config.logging() );
ServerAddressResolver resolver = createResolver( config );
return new LoadBalancer( address, routingSettings, connectionPool, eventExecutorGroup, createClock(),
config.logging(), loadBalancingStrategy, resolver );
config.logging(), loadBalancingStrategy, resolver, config.domainNameResolver() );
}

private static ServerAddressResolver createResolver( Config config )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.resolver.AddressResolverGroup;

import java.util.Map;
import java.net.InetSocketAddress;

import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Logging;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.internal.ConnectionSettings;
import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler;
import org.neo4j.driver.internal.cluster.RoutingContext;
import org.neo4j.driver.internal.security.InternalAuthToken;
import org.neo4j.driver.internal.security.SecurityPlan;
import org.neo4j.driver.internal.util.Clock;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Logging;
import org.neo4j.driver.Value;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.net.DomainNameResolver;

import static java.util.Objects.requireNonNull;

Expand All @@ -52,15 +53,17 @@ public class ChannelConnectorImpl implements ChannelConnector
private final int connectTimeoutMillis;
private final Logging logging;
private final Clock clock;
private final AddressResolverGroup<InetSocketAddress> addressResolverGroup;

public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan securityPlan, Logging logging,
Clock clock, RoutingContext routingContext )
Clock clock, RoutingContext routingContext, DomainNameResolver domainNameResolver )
{
this( connectionSettings, securityPlan, new ChannelPipelineBuilderImpl(), logging, clock, routingContext );
this( connectionSettings, securityPlan, new ChannelPipelineBuilderImpl(), logging, clock, routingContext, domainNameResolver );
}

public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan securityPlan,
ChannelPipelineBuilder pipelineBuilder, Logging logging, Clock clock, RoutingContext routingContext )
ChannelPipelineBuilder pipelineBuilder, Logging logging, Clock clock, RoutingContext routingContext,
DomainNameResolver domainNameResolver )
{
this.userAgent = connectionSettings.userAgent();
this.authToken = requireValidAuthToken( connectionSettings.authToken() );
Expand All @@ -70,13 +73,15 @@ public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan
this.pipelineBuilder = pipelineBuilder;
this.logging = requireNonNull( logging );
this.clock = requireNonNull( clock );
this.addressResolverGroup = new NettyDomainNameResolverGroup( requireNonNull( domainNameResolver ) );
}

@Override
public ChannelFuture connect( BoltServerAddress address, Bootstrap bootstrap )
{
bootstrap.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis );
bootstrap.handler( new NettyChannelInitializer( address, securityPlan, connectTimeoutMillis, clock, logging ) );
bootstrap.resolver( addressResolverGroup );

ChannelFuture channelConnected = bootstrap.connect( address.toSocketAddress() );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed 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
*
* http://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 org.neo4j.driver.internal.async.connection;

import io.netty.resolver.InetNameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Promise;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;

import org.neo4j.driver.net.DomainNameResolver;

public class NettyDomainNameResolver extends InetNameResolver
{
private final DomainNameResolver domainNameResolver;

public NettyDomainNameResolver( EventExecutor executor, DomainNameResolver domainNameResolver )
{
super( executor );
this.domainNameResolver = domainNameResolver;
}

@Override
protected void doResolve( String inetHost, Promise<InetAddress> promise )
{
try
{
promise.setSuccess( domainNameResolver.resolve( inetHost )[0] );
}
catch ( UnknownHostException e )
{
promise.setFailure( e );
}
}

@Override
protected void doResolveAll( String inetHost, Promise<List<InetAddress>> promise )
{
try
{
promise.setSuccess( Arrays.asList( domainNameResolver.resolve( inetHost ) ) );
}
catch ( UnknownHostException e )
{
promise.setFailure( e );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed 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
*
* http://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 org.neo4j.driver.internal.async.connection;

import io.netty.resolver.AddressResolver;
import io.netty.resolver.AddressResolverGroup;
import io.netty.util.concurrent.EventExecutor;

import java.net.InetSocketAddress;

import org.neo4j.driver.net.DomainNameResolver;

public class NettyDomainNameResolverGroup extends AddressResolverGroup<InetSocketAddress>
{
private final DomainNameResolver domainNameResolver;

public NettyDomainNameResolverGroup( DomainNameResolver domainNameResolver )
{
this.domainNameResolver = domainNameResolver;
}

@Override
protected AddressResolver<InetSocketAddress> newResolver( EventExecutor executor ) throws Exception
{
return new NettyDomainNameResolver( executor, domainNameResolver ).asAddressResolver();
}
}
Loading

0 comments on commit 371e202

Please sign in to comment.