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); }