Skip to content

Commit e378737

Browse files
committed
feat: Use configured DNS name to lookup instance IP address
Bug Description When a custom DNS name is used to connect to a Cloud SQL instance, the dialer should first attempt to resolve the custom DNS name to an IP address and use that for the connection. If the lookup fails, the dialer should fall back to using the IP address from the instance metadata. This change modifies the dialer to: Use the configured resolver to look up the host's IP address. Use the IP address from the A record of the custom DNS name if available. Fall back to the IP address from the instance metadata if the A record is not available. See also GoogleCloudPlatform/cloud-sql-go-connector#1053 Fixes: #2243
1 parent 22137c8 commit e378737

File tree

6 files changed

+166
-18
lines changed

6 files changed

+166
-18
lines changed

build.sh

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ function test() {
3939
if [[ "$(uname -s)" == "Darwin" ]]; then
4040
echo "macOS detected. Setting up IP aliases for tests."
4141
echo "You may be prompted for your password to run sudo."
42-
sudo ifconfig lo0 alias 127.0.0.2 up
43-
sudo ifconfig lo0 alias 127.0.0.3 up
42+
if ! ifconfig lo0 | grep -q 127.0.0.2 ; then
43+
sudo ifconfig lo0 alias 127.0.0.2 up
44+
fi
45+
if ! ifconfig lo0 | grep -q 127.0.0.3 ; then
46+
sudo ifconfig lo0 alias 127.0.0.3 up
47+
fi
4448
fi
4549
mvn -P coverage test
4650
}
@@ -91,7 +95,7 @@ function write_e2e_env(){
9195
secret_vars=(
9296
MYSQL_CONNECTION_NAME=MYSQL_CONNECTION_NAME
9397
MYSQL_USER=MYSQL_USER
94-
MYSQL_USER_IAM=MYSQL_USER_IAM_GO
98+
IMPERSONATED_USER=IMPERSONATED_USER
9599
MYSQL_PASS=MYSQL_PASS
96100
MYSQL_DB=MYSQL_DB
97101
MYSQL_MCP_CONNECTION_NAME=MYSQL_MCP_CONNECTION_NAME
@@ -105,8 +109,8 @@ function write_e2e_env(){
105109
POSTGRES_CAS_PASS=POSTGRES_CAS_PASS
106110
POSTGRES_CUSTOMER_CAS_CONNECTION_NAME=POSTGRES_CUSTOMER_CAS_CONNECTION_NAME
107111
POSTGRES_CUSTOMER_CAS_PASS=POSTGRES_CUSTOMER_CAS_PASS
108-
POSTGRES_CUSTOMER_CAS_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
109-
POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
112+
POSTGRES_CUSTOMER_CAS_PASS_VALID_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_DOMAIN_NAME
113+
POSTGRES_CUSTOMER_CAS_PASS_INVALID_DOMAIN_NAME=POSTGRES_CUSTOMER_CAS_INVALID_DOMAIN_NAME
110114
POSTGRES_MCP_CONNECTION_NAME=POSTGRES_MCP_CONNECTION_NAME
111115
POSTGRES_MCP_PASS=POSTGRES_MCP_PASS
112116
SQLSERVER_CONNECTION_NAME=SQLSERVER_CONNECTION_NAME
@@ -131,6 +135,10 @@ function write_e2e_env(){
131135
val=$(gcloud secrets versions access latest --project "$TEST_PROJECT" --secret="$secret_name")
132136
echo "export $env_var_name='$val'"
133137
done
138+
139+
echo "export MYSQL_IAM_USER='$(whoami)'"
140+
echo "export POSTGRES_IAM_USER='$(whoami)@google.com'"
141+
134142
} > "$outfile"
135143

136144
}

core/src/main/java/com/google/cloud/sql/core/Connector.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
2525
import java.io.File;
2626
import java.io.IOException;
27+
import java.net.InetAddress;
2728
import java.net.InetSocketAddress;
2829
import java.net.Socket;
30+
import java.net.UnknownHostException;
2931
import java.security.KeyPair;
32+
import java.util.List;
3033
import java.util.Timer;
3134
import java.util.concurrent.ConcurrentHashMap;
3235
import java.util.concurrent.ExecutionException;
@@ -52,6 +55,7 @@ class Connector {
5255
private final ConnectorConfig config;
5356

5457
private final InstanceConnectionNameResolver instanceNameResolver;
58+
private final DnsResolver dnsResolver;
5559
private final Timer instanceNameResolverTimer;
5660
private final ProtocolHandler mdxProtocolHandler;
5761

@@ -65,9 +69,9 @@ class Connector {
6569
long refreshTimeoutMs,
6670
int serverProxyPort,
6771
InstanceConnectionNameResolver instanceNameResolver,
72+
DnsResolver dnsResolver,
6873
ProtocolHandler mdxProtocolHandler) {
6974
this.config = config;
70-
7175
this.adminApi =
7276
connectionInfoRepositoryFactory.create(instanceCredentialFactory.create(), config);
7377
this.instanceCredentialFactory = instanceCredentialFactory;
@@ -76,6 +80,7 @@ class Connector {
7680
this.minRefreshDelayMs = minRefreshDelayMs;
7781
this.serverProxyPort = serverProxyPort;
7882
this.instanceNameResolver = instanceNameResolver;
83+
this.dnsResolver = dnsResolver;
7984
this.instanceNameResolverTimer = new Timer("InstanceNameResolverTimer", true);
8085
this.mdxProtocolHandler = mdxProtocolHandler;
8186
}
@@ -125,6 +130,40 @@ Socket connect(ConnectionConfig config, long timeoutMs) throws IOException {
125130
try {
126131
ConnectionMetadata metadata = instance.getConnectionMetadata(timeoutMs);
127132
String instanceIp = metadata.getPreferredIpAddress();
133+
134+
// If a domain name was used to connect, resolve it to an IP address
135+
if (!Strings.isNullOrEmpty(instance.getConfig().getDomainName())) {
136+
try {
137+
List<InetAddress> addrs = dnsResolver.resolveHost(instance.getConfig().getDomainName());
138+
if (addrs != null && !addrs.isEmpty()) {
139+
logger.debug(
140+
String.format(
141+
"[%s] custom DNS name %s resolved to %s, using it to connect",
142+
instance.getConfig().getCloudSqlInstance(),
143+
instance.getConfig().getDomainName(),
144+
addrs.get(0).getHostAddress()));
145+
instanceIp = addrs.get(0).getHostAddress();
146+
} else {
147+
logger.debug(
148+
String.format(
149+
"[%s] custom DNS name %s resolved but returned no entries, using %s from"
150+
+ " instance metadata",
151+
instance.getConfig().getCloudSqlInstance(),
152+
instance.getConfig().getDomainName(),
153+
instanceIp));
154+
}
155+
} catch (UnknownHostException e) {
156+
logger.debug(
157+
String.format(
158+
"[%s] custom DNS name %s did not resolve to an IP address: %s, using %s from"
159+
+ " instance metadata",
160+
instance.getConfig().getCloudSqlInstance(),
161+
instance.getConfig().getDomainName(),
162+
e.getMessage(),
163+
instanceIp));
164+
}
165+
}
166+
128167
logger.debug(String.format("[%s] Connecting to instance.", instanceIp));
129168

130169
SSLSocket socket = (SSLSocket) metadata.getSslContext().getSocketFactory().createSocket();

core/src/main/java/com/google/cloud/sql/core/DnsJavaResolver.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616

1717
package com.google.cloud.sql.core;
1818

19+
import java.net.InetAddress;
1920
import java.net.UnknownHostException;
2021
import java.util.Arrays;
2122
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.List;
2225
import java.util.stream.Collectors;
2326
import javax.naming.NameNotFoundException;
27+
import org.xbill.DNS.ARecord;
2428
import org.xbill.DNS.Lookup;
2529
import org.xbill.DNS.Record;
2630
import org.xbill.DNS.SimpleResolver;
@@ -105,4 +109,43 @@ public Collection<String> resolveTxt(String domainName) throws NameNotFoundExcep
105109
throw new RuntimeException("Invalid domain name format: " + domainName, e);
106110
}
107111
}
112+
113+
/**
114+
* Resolve an A record.
115+
* @param hostName the hostname to look up
116+
* @return the resolved IP addresses
117+
* @throws UnknownHostException if no records are found.
118+
*/
119+
@Override
120+
public List<InetAddress> resolveHost(String hostName) throws UnknownHostException {
121+
try {
122+
Lookup lookup = new Lookup(hostName, Type.A);
123+
if (this.resolver != null) {
124+
lookup.setResolver(this.resolver);
125+
}
126+
lookup.run();
127+
128+
int resultCode = lookup.getResult();
129+
if (resultCode == Lookup.HOST_NOT_FOUND) {
130+
throw new UnknownHostException("DNS record not found for " + hostName);
131+
}
132+
if (resultCode != Lookup.SUCCESSFUL) {
133+
throw new UnknownHostException(
134+
"DNS lookup failed for " + hostName + ": " + lookup.getErrorString());
135+
}
136+
137+
Record[] records = lookup.getAnswers();
138+
if (records == null || records.length == 0) {
139+
return Collections.emptyList();
140+
}
141+
142+
return Arrays.stream(records)
143+
.map(r -> (ARecord) r)
144+
.map(ARecord::getAddress)
145+
.collect(Collectors.toList());
146+
147+
} catch (TextParseException e) {
148+
throw new UnknownHostException("Invalid domain name format: " + hostName);
149+
}
150+
}
108151
}

core/src/main/java/com/google/cloud/sql/core/DnsResolver.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
package com.google.cloud.sql.core;
1818

19+
import java.net.InetAddress;
20+
import java.net.UnknownHostException;
1921
import java.util.Collection;
22+
import java.util.List;
2023
import javax.naming.NameNotFoundException;
2124

2225
/** Wraps the Java DNS API. */
2326
interface DnsResolver {
2427
Collection<String> resolveTxt(String domainName) throws NameNotFoundException;
28+
29+
List<InetAddress> resolveHost(String hostName) throws UnknownHostException;
2530
}

core/src/main/java/com/google/cloud/sql/core/InternalConnectorRegistry.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ private Connector createConnector(ConnectorConfig config) {
339339
connectTimeoutMs,
340340
serverProxyPort,
341341
new DnsInstanceConnectionNameResolver(new DnsJavaResolver()),
342+
new DnsJavaResolver(),
342343
this.mdxProtocolHandler);
343344
}
344345

0 commit comments

Comments
 (0)