Skip to content

Commit e7f0289

Browse files
committed
chore: Create a new DnsResolver class that wraps the clumsy Java DNS API. Part of #2043.
1 parent d76e892 commit e7f0289

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.core;
18+
19+
import java.util.Collection;
20+
import javax.naming.NameNotFoundException;
21+
22+
interface DnsResolver {
23+
Collection<DnsSrvRecord> resolveSrv(String domainName) throws NameNotFoundException;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.core;
18+
19+
import java.util.Objects;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
public class DnsSrvRecord {
24+
private static final Pattern RECORD_FORMAT=Pattern.compile("(\\d+) +(\\d+) +(\\d+) +(.*)");
25+
private final int priority;
26+
private final int weight;
27+
private final int port;
28+
private final String target;
29+
30+
DnsSrvRecord(String record) {
31+
Matcher m = RECORD_FORMAT.matcher(record);
32+
if(! m.find()) {
33+
throw new IllegalArgumentException("Malformed SRV record: "+record);
34+
}
35+
36+
this.priority = Integer.parseInt(m.group(1));
37+
this.weight = Integer.parseInt(m.group(2));
38+
this.port = Integer.parseInt(m.group(3));
39+
this.target = m.group(4);
40+
}
41+
42+
public int getPriority() {
43+
return priority;
44+
}
45+
46+
public int getWeight() {
47+
return weight;
48+
}
49+
50+
public int getPort() {
51+
return port;
52+
}
53+
54+
public String getTarget() {
55+
return target;
56+
}
57+
58+
@Override
59+
public boolean equals(Object o) {
60+
if (this == o)
61+
return true;
62+
if (!(o instanceof DnsSrvRecord))
63+
return false;
64+
DnsSrvRecord that = (DnsSrvRecord) o;
65+
return priority == that.priority && weight == that.weight && port == that.port && target.equals(
66+
that.target);
67+
}
68+
69+
@Override
70+
public int hashCode() {
71+
return Objects.hash(priority, weight, port, target);
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.core;
18+
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.Comparator;
22+
import java.util.stream.Collectors;
23+
import javax.naming.NameNotFoundException;
24+
import javax.naming.NamingException;
25+
import javax.naming.directory.Attribute;
26+
import javax.naming.directory.InitialDirContext;
27+
28+
/**
29+
* Implements DnsResolver using the Java JNDI built-in DNS directory.
30+
*/
31+
class JndiDnsResolver implements DnsResolver {
32+
private final String jndiPrefix;
33+
34+
/**
35+
* Creates a resolver using the system DNS settings.
36+
*/
37+
JndiDnsResolver() {
38+
this.jndiPrefix = "dns:";
39+
}
40+
41+
/**
42+
* Creates a DNS resolver that uses a specific DNS server.
43+
* @param dnsServer the DNS server hostname
44+
* @param port the DNS server port (DNS servers usually use port 53)
45+
*/
46+
JndiDnsResolver(String dnsServer, int port) {
47+
this.jndiPrefix = "dns://"+dnsServer+":"+port+"/";
48+
}
49+
50+
/**
51+
* Returns DNS records for a domain name, sorted by priority, then target alphabetically.
52+
* @param domainName the domain name to lookup
53+
* @return the list of record
54+
* @throws javax.naming.NameNotFoundException when the domain name did not resolve.
55+
*/
56+
@Override
57+
public Collection<DnsSrvRecord> resolveSrv(String domainName) throws javax.naming.NameNotFoundException{
58+
try {
59+
// Notice: This is old Java 1.2 style code. It uses the ancient JNDI DNS Provider api.
60+
// See https://docs.oracle.com/javase/7/docs/technotes/guides/jndi/jndi-dns.html
61+
Attribute attr =
62+
new InitialDirContext()
63+
.getAttributes(jndiPrefix+domainName, new String[] {"SRV"})
64+
.get("SRV");
65+
// attr.getAll() returns a Vector containing strings, one for each record returned by dns.
66+
return Collections.list(attr.getAll()).stream()
67+
.map((Object v) -> new DnsSrvRecord((String) v))
68+
.sorted(Comparator.comparing(DnsSrvRecord::getPriority))
69+
.collect(Collectors.toList());
70+
} catch (NameNotFoundException e) {
71+
throw e;
72+
}
73+
catch (NamingException e) {
74+
throw new RuntimeException("Unable to look up domain name "+domainName, e);
75+
}
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.google.cloud.sql.core;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertThrows;
5+
6+
import org.junit.Test;
7+
8+
public class DnsSrvRecordTest {
9+
10+
@Test
11+
public void testValidSrvRecord() {
12+
DnsSrvRecord r = new DnsSrvRecord("0 10 3307 sample-project:us-central1:my-database.");
13+
assertThat(r.getTarget()).isEqualTo("sample-project:us-central1:my-database.");
14+
assertThat(r.getPort()).isEqualTo(3307);
15+
assertThat(r.getWeight()).isEqualTo(10);
16+
assertThat(r.getPriority()).isEqualTo(0);
17+
}
18+
19+
@Test
20+
public void testInvalidSrvRecordThrows() {
21+
assertThrows(IllegalArgumentException.class, ()-> new DnsSrvRecord("bad record format"));
22+
}
23+
}

0 commit comments

Comments
 (0)