From 3625cc24cf18a425008ad07e819c72b3428b28a9 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Fri, 25 Sep 2026 08:13:09 +0000 Subject: [PATCH] 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 --- .../ts3j/protocol/JndiSrvLookup.java | 38 +++++++++++ .../manevolent/ts3j/protocol/TS3DNS.java | 65 ++++++++++++------- 2 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/github/manevolent/ts3j/protocol/JndiSrvLookup.java diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/JndiSrvLookup.java b/src/main/java/com/github/manevolent/ts3j/protocol/JndiSrvLookup.java new file mode 100644 index 0000000..be7dee5 --- /dev/null +++ b/src/main/java/com/github/manevolent/ts3j/protocol/JndiSrvLookup.java @@ -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 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 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; + } +} diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/TS3DNS.java b/src/main/java/com/github/manevolent/ts3j/protocol/TS3DNS.java index 1356391..e6b8725 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/TS3DNS.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/TS3DNS.java @@ -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 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 lookupSRV(String domain) throws IOException { - Attribute attribute; + List 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 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 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 lookup(String domain) throws IOException { return lookupSRV(DNS_PREFIX_UDP + domain); }