Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DNS discovery fixes #855

Merged
merged 8 commits into from
Mar 23, 2021
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ enterprise/server-enterprise/neo4j-home
integrationtests/data
dependency-reduced-pom.xml
venv
testkit-backend/bin/
testkit/CAs/
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,13 @@
*/
package org.neo4j.driver.internal;

import java.net.InetAddress;
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 @@ -40,12 +34,11 @@ public class BoltServerAddress implements ServerAddress
public static final int DEFAULT_PORT = 7687;
public static final BoltServerAddress LOCAL_DEFAULT = new BoltServerAddress( "localhost", DEFAULT_PORT );

private final String host; // This could either be the same as originalHost or it is an IP address resolved from the original host.
private final int port;
protected final String host; // Host or IP address.
private final String connectionHost; // Either is equal to the host or is explicitly provided on creation and is expected to be a resolved IP address.
protected final int port;
private final String stringValue;

private InetAddress resolved;

public BoltServerAddress( String address )
{
this( uriFrom( address ) );
Expand All @@ -58,15 +51,17 @@ public BoltServerAddress( URI uri )

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

private BoltServerAddress( String host, InetAddress resolved, int port )
public BoltServerAddress( String host, String connectionHost, int port )
{
this.host = requireNonNull( host, "host" );
this.resolved = resolved;
this.connectionHost = requireNonNull( connectionHost, "connectionHost" );
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 = host.equals( connectionHost )
? String.format( "%s:%d", host, port )
: String.format( "%s(%s):%d", host, connectionHost, port );
}

public static BoltServerAddress from( ServerAddress address )
Expand All @@ -87,14 +82,14 @@ public boolean equals( Object o )
{
return false;
}
BoltServerAddress that = (BoltServerAddress) o;
return port == that.port && host.equals( that.host );
BoltServerAddress address = (BoltServerAddress) o;
return port == address.port && host.equals( address.host ) && connectionHost.equals( address.connectionHost );
}

@Override
public int hashCode()
{
return Objects.hash( host, port );
return Objects.hash( host, connectionHost, port );
}

@Override
Expand All @@ -103,44 +98,6 @@ public String toString()
return stringValue;
}

/**
* Create a {@link SocketAddress} from this bolt address. This method always attempts to resolve the hostname into
* an {@link InetAddress}.
*
* @return new socket address.
* @see InetSocketAddress
*/
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() );
}

@Override
public String host()
{
Expand All @@ -153,9 +110,21 @@ public int port()
return port;
}

public boolean isResolved()
public String connectionHost()
{
return connectionHost;
}

/**
* Create a stream of unicast addresses.
* <p>
* While this implementation just returns a stream of itself, the subclasses may provide multiple addresses.
*
* @return stream of unicast addresses.
*/
public Stream<BoltServerAddress> unicastStream()
{
return resolved != null;
return Stream.of( this );
}

private static String hostFrom( URI uri )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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;

public class DefaultDomainNameResolver implements DomainNameResolver
{
private static final DefaultDomainNameResolver INSTANCE = new DefaultDomainNameResolver();

public static DefaultDomainNameResolver getInstance()
{
return INSTANCE;
}

private DefaultDomainNameResolver()
{
}

@Override
public InetAddress[] resolve( String name ) throws UnknownHostException
{
return InetAddress.getAllByName( name );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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;

/**
* A resolver function used by the driver to resolve domain names.
*/
@FunctionalInterface
public interface DomainNameResolver
{
/**
* Resolve the given domain name to a set of addresses.
*
* @param name the name to resolve.
* @return the resolved addresses.
* @throws UnknownHostException must be thrown if the given name can not be resolved to at least one address.
*/
InetAddress[] resolve( String name ) throws UnknownHostException;
}
15 changes: 13 additions & 2 deletions driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
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, getDomainNameResolver() );
}

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, getDomainNameResolver() );
}

private static ServerAddressResolver createResolver( Config config )
Expand Down Expand Up @@ -271,6 +271,17 @@ protected Bootstrap createBootstrap( EventLoopGroup eventLoopGroup )
return BootstrapFactory.newBootstrap( eventLoopGroup );
}

/**
* Provides an instance of {@link DomainNameResolver} that is used for domain name resolution.
* <p>
* <b>This method is protected only for testing</b>
*
* @return the instance of {@link DomainNameResolver}.
*/
protected DomainNameResolver getDomainNameResolver()
{
return DefaultDomainNameResolver.getInstance();
}

private static void assertNoRoutingContext( URI uri, RoutingSettings routingSettings )
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

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

/**
* An explicitly resolved version of {@link BoltServerAddress} that always contains one or more resolved IP addresses.
*/
public class ResolvedBoltServerAddress extends BoltServerAddress
{
private static final String HOST_ADDRESSES_FORMAT = "%s%s:%d";
private static final int MAX_HOST_ADDRESSES_IN_STRING_VALUE = 5;
private static final String HOST_ADDRESS_DELIMITER = ",";
private static final String HOST_ADDRESSES_PREFIX = "(";
private static final String HOST_ADDRESSES_SUFFIX = ")";
private static final String TRIMMED_HOST_ADDRESSES_SUFFIX = ",..." + HOST_ADDRESSES_SUFFIX;

private final Set<InetAddress> resolvedAddresses;
private final String stringValue;

public ResolvedBoltServerAddress( String host, int port, InetAddress[] resolvedAddressesArr )
{
super( host, port );
requireNonNull( resolvedAddressesArr, "resolvedAddressesArr" );
if ( resolvedAddressesArr.length == 0 )
{
throw new IllegalArgumentException(
"The resolvedAddressesArr must not be empty, check your DomainNameResolver is compliant with the interface contract" );
}
resolvedAddresses = Collections.unmodifiableSet( new LinkedHashSet<>( Arrays.asList( resolvedAddressesArr ) ) );
stringValue = createStringRepresentation();
}

/**
* Create a stream of unicast addresses.
* <p>
* The stream is created from the list of resolved IP addresses. Each unicast address is given a unique IP address as the connectionHost value.
*
* @return stream of unicast addresses.
*/
@Override
public Stream<BoltServerAddress> unicastStream()
{
return resolvedAddresses.stream().map( address -> new BoltServerAddress( host, address.getHostAddress(), port ) );
}

@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || getClass() != o.getClass() )
{
return false;
}
if ( !super.equals( o ) )
{
return false;
}
ResolvedBoltServerAddress that = (ResolvedBoltServerAddress) o;
return resolvedAddresses.equals( that.resolvedAddresses );
}

@Override
public int hashCode()
{
return Objects.hash( super.hashCode(), resolvedAddresses );
}

@Override
public String toString()
{
return stringValue;
}

private String createStringRepresentation()
{
String hostAddresses = resolvedAddresses.stream()
.limit( MAX_HOST_ADDRESSES_IN_STRING_VALUE )
.map( InetAddress::getHostAddress )
.collect( joining( HOST_ADDRESS_DELIMITER, HOST_ADDRESSES_PREFIX,
resolvedAddresses.size() > MAX_HOST_ADDRESSES_IN_STRING_VALUE
? TRIMMED_HOST_ADDRESSES_SUFFIX
: HOST_ADDRESSES_SUFFIX ) );
return String.format( HOST_ADDRESSES_FORMAT, host, hostAddresses, port );
}
}
Loading