Look up SRV records without requiring JNDI

TS3DNS resolved SRV records through JNDI's DNS provider, which Android
does not have. The JNDI code moves into its own class that is only
loaded where JNDI exists; elsewhere a server is reached by its address,
as when it publishes no SRV record. TS3DNS.setSrvLookup lets a platform
supply its own resolver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 08:13:09 +00:00
co-authored by Claude Opus 5.5
parent 2018c1c593
commit 3625cc24cf
2 changed files with 80 additions and 23 deletions
@@ -0,0 +1,38 @@
package com.github.manevolent.ts3j.protocol;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.InitialDirContext;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
/**
* SRV lookups through JNDI's DNS provider, which desktop JVMs ship. Kept apart from
* {@link TS3DNS} so runtimes without JNDI (Android) never load it.
*/
final class JndiSrvLookup implements TS3DNS.SrvLookup {
@Override
public List<String> lookup(String name) throws IOException {
Attribute attribute;
try {
Attributes attributes = new InitialDirContext().getAttributes("dns:/" + name, new String[] { "SRV" });
attribute = attributes.get("SRV");
} catch (NamingException e) {
throw new UnknownHostException("Host not found: " + name);
}
List<String> records = new ArrayList<>();
if (attribute == null) return records;
try {
for (int i = 0; i < attribute.size(); i++)
records.add((String) attribute.get(i));
} catch (NamingException e) {
throw new IOException(e);
}
return records;
}
}
@@ -1,9 +1,5 @@
package com.github.manevolent.ts3j.protocol;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.InitialDirContext;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@@ -18,8 +14,28 @@ public class TS3DNS {
private static final String DNS_PREFIX_TCP = "_tsdns._tcp.";
private static final String DNS_PREFIX_UDP = "_ts3._udp.";
/** Looks up DNS SRV records; see {@link #setSrvLookup}. */
public interface SrvLookup {
/**
* @return each record as "priority weight port target", empty if there are none
* @throws UnknownHostException if the name does not exist
*/
List<String> lookup(String name) throws IOException;
}
/** Null until first use, when JNDI's DNS provider becomes the default. */
private static volatile SrvLookup srvLookup;
/**
* SRV record, as returned by JNDI: "priority weight port target."
* Replaces how SRV records are looked up. The default goes through JNDI, which not every
* runtime has (Android doesn't); without it, servers are reached by address only.
*/
public static void setSrvLookup(SrvLookup lookup) {
srvLookup = lookup;
}
/**
* SRV record, as {@link SrvLookup} returns it: "priority weight port target."
*/
private static final class SrvRecord {
private final int priority, weight, port;
@@ -40,25 +56,12 @@ public class TS3DNS {
}
private static List<InetSocketAddress> lookupSRV(String domain) throws IOException {
Attribute attribute;
List<String> found = srvLookup().lookup(domain);
if (found.isEmpty()) throw new UnknownHostException("Host not found");
try {
Attributes attributes = new InitialDirContext().getAttributes("dns:/" + domain, new String[] { "SRV" });
attribute = attributes.get("SRV");
} catch (NamingException e) {
throw new UnknownHostException("Host not found: " + domain);
}
if (attribute == null || attribute.size() <= 0) throw new UnknownHostException("Host not found");
List<SrvRecord> records = new ArrayList<>(attribute.size());
try {
for (int i = 0; i < attribute.size(); i++)
records.add(new SrvRecord((String) attribute.get(i)));
} catch (NamingException e) {
throw new IOException(e);
}
List<SrvRecord> records = new ArrayList<>(found.size());
for (String record : found)
records.add(new SrvRecord(record));
// Loose sorting
records.sort(Comparator.comparingInt((SrvRecord x) -> x.priority).thenComparingInt(x -> x.weight));
@@ -70,6 +73,22 @@ public class TS3DNS {
return addresses;
}
private static SrvLookup srvLookup() {
SrvLookup lookup = srvLookup;
if (lookup != null) return lookup;
try {
Class.forName("javax.naming.directory.InitialDirContext");
lookup = new JndiSrvLookup();
} catch (ClassNotFoundException e) {
// No JNDI on this runtime: behave as if no server published an SRV record.
lookup = name -> {
throw new UnknownHostException("No SRV lookup available for " + name);
};
}
srvLookup = lookup;
return lookup;
}
public static List<InetSocketAddress> lookup(String domain) throws IOException {
return lookupSRV(DNS_PREFIX_UDP + domain);
}