diff --git a/src/java/org/jivesoftware/multiplexer/ConnectionWorkerThread.java b/src/java/org/jivesoftware/multiplexer/ConnectionWorkerThread.java index 7a59c06..51cc13d 100644 --- a/src/java/org/jivesoftware/multiplexer/ConnectionWorkerThread.java +++ b/src/java/org/jivesoftware/multiplexer/ConnectionWorkerThread.java @@ -34,6 +34,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Iterator; +import java.util.List; import java.util.Random; /** @@ -142,21 +143,24 @@ } } else { - try { - // Get the real hostname to connect to using DNS lookup of the specified hostname - DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(serverName, port); - realHostname = address.getHost(); - Log.debug("CM - Trying to connect to " + serverName + ":" + port + - "(DNS lookup: " + realHostname + ":" + port + ")"); - // Establish a TCP connection to the Receiving Server - socket.connect(new InetSocketAddress(realHostname, port), 20000); - Log.debug("CM - Plain connection to " + serverName + ":" + port + " successful"); + // Get the real hostname to connect to using DNS lookup of the specified hostname + List addresses = DNSUtil.resolveXMPPDomain(serverName, port); + for (Iterator it = addresses.iterator(); it.hasNext(); ) { + try { + realHostname = it.next().getHost(); + Log.debug("CM - Trying to connect to " + serverName + ":" + port + + "(DNS lookup: " + realHostname + ":" + port + ")"); + // Establish a TCP connection to the Receiving Server + socket.connect(new InetSocketAddress(realHostname, port), 20000); + Log.debug("CM - Plain connection to " + serverName + ":" + port + " successful"); + return true; + } + catch (Exception e) { + Log.error("Error trying to connect to server: " + serverName + + "(DNS lookup: " + realHostname + ":" + port + ")", e); + } } - catch (Exception e) { - Log.error("Error trying to connect to server: " + serverName + - "(DNS lookup: " + realHostname + ":" + port + ")", e); - return false; - } + return false; } try { diff --git a/src/java/org/jivesoftware/multiplexer/net/DNSUtil.java b/src/java/org/jivesoftware/multiplexer/net/DNSUtil.java index 0c78759..abb6c89 100644 --- a/src/java/org/jivesoftware/multiplexer/net/DNSUtil.java +++ b/src/java/org/jivesoftware/multiplexer/net/DNSUtil.java @@ -14,11 +14,20 @@ import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.Log; +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.Hashtable; +import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -49,7 +58,7 @@ } } catch (Exception e) { - Log.error(e); + Log.error("Can't initialize DNS context!", e); } } @@ -69,47 +78,54 @@ * @param defaultPort default port to return if the DNS look up fails. * @return a HostAddress, which encompasses the hostname and port that the XMPP * server can be reached at for the specified domain. + * @deprecated replaced with support for multiple srv records, see + * {@link #resolveXMPPDomain(String, int)} */ + @Deprecated public static HostAddress resolveXMPPServerDomain(String domain, int defaultPort) { + return resolveXMPPDomain(domain, defaultPort).get(0); + } + + /** + * Returns a sorted list of host names and ports that the specified XMPP domain + * can be reached at for server-to-server communication. A DNS lookup for a SRV + * record in the form "_xmpp-server._tcp.example.com" is attempted, according + * to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form + * of "_jabber._tcp.example.com" is attempted since servers that implement an + * older version of the protocol may be listed using that notation. If that + * lookup fails as well, it's assumed that the XMPP server lives at the + * host resolved by a DNS lookup at the specified domain on the specified default port.

+ * + * As an example, a lookup for "example.com" may return "im.example.com:5269". + * + * @param domain the domain. + * @param defaultPort default port to return if the DNS look up fails. + * @return a list of HostAddresses, which encompasses the hostname and port that the XMPP + * server can be reached at for the specified domain. + */ + public static List resolveXMPPDomain(String domain, int defaultPort) { // Check if there is an entry in the internal DNS for the specified domain + List results = null; if (dnsOverride != null) { HostAddress hostAddress = dnsOverride.get(domain); if (hostAddress != null) { - return hostAddress; + results = new ArrayList(); + results.add(hostAddress); + return results; } } - if (context == null) { - return new HostAddress(domain, defaultPort); + + // Attempt the SRV lookup. + results = srvLookup("_xmpp-server._tcp." + domain); + if (results == null || results.isEmpty()) { + results = srvLookup("_jabber._tcp." + domain); } - String host = domain; - int port = defaultPort; - try { - Attributes dnsLookup = - context.getAttributes("_xmpp-server._tcp." + domain, new String[]{"SRV"}); - String srvRecord = (String)dnsLookup.get("SRV").get(); - String [] srvRecordEntries = srvRecord.split(" "); - port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]); - host = srvRecordEntries[srvRecordEntries.length-1]; + + // Use domain and default port as fallback. + if (results.isEmpty()) { + results.add(new HostAddress(domain, defaultPort)); } - catch (Exception e) { - // Attempt lookup with older "jabber" name. - try { - Attributes dnsLookup = - context.getAttributes("_jabber._tcp." + domain, new String[]{"SRV"}); - String srvRecord = (String)dnsLookup.get("SRV").get(); - String [] srvRecordEntries = srvRecord.split(" "); - port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]); - host = srvRecordEntries[srvRecordEntries.length-1]; - } - catch (Exception e2) { - // Do nothing - } - } - // Host entries in DNS should end with a ".". - if (host.endsWith(".")) { - host = host.substring(0, host.length()-1); - } - return new HostAddress(host, port); + return results; } /** @@ -163,16 +179,52 @@ return answer; } + private static List srvLookup(String lookup) { + if (lookup == null) { + throw new NullPointerException("DNS lookup can't be null"); + } + try { + Attributes dnsLookup = + context.getAttributes(lookup, new String[]{"SRV"}); + Attribute srvRecords = dnsLookup.get("SRV"); + if (srvRecords == null) { + Log.debug("No SRV record found for domain: " + lookup); + return new ArrayList(); + } + HostAddress[] hosts = new WeightedHostAddress[srvRecords.size()]; + for (int i = 0; i < srvRecords.size(); i++) { + hosts[i] = new WeightedHostAddress(((String)srvRecords.get(i)).split(" ")); + } + if (srvRecords.size() > 1) { + Arrays.sort(hosts, new SrvRecordWeightedPriorityComparator()); + } + return Arrays.asList(hosts); + } + catch (NameNotFoundException e) { + Log.debug("No SRV record found for: " + lookup, e); + } + catch (NamingException e) { + Log.error("Can't process DNS lookup!", e); + } + return new ArrayList(); + } + /** * Encapsulates a hostname and port. */ public static class HostAddress { - private String host; - private int port; + private final String host; + private final int port; private HostAddress(String host, int port) { - this.host = host; + // Host entries in DNS should end with a ".". + if (host.endsWith(".")) { + this.host = host.substring(0, host.length()-1); + } + else { + this.host = host; + } this.port = port; } @@ -198,4 +250,64 @@ return host + ":" + port; } } + + /** + * The representation of weighted address. + */ + public static class WeightedHostAddress extends HostAddress { + + private final int priority; + private final int weight; + + private WeightedHostAddress(String [] srvRecordEntries) { + super(srvRecordEntries[srvRecordEntries.length-1], + Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2])); + weight = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-3]); + priority = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-4]); + } + + private WeightedHostAddress(String host, int port, int priority, int weight) { + super(host, port); + this.priority = priority; + this.weight = weight; + } + + /** + * Returns the priority. + * + * @return the priority. + */ + public int getPriority() { + return priority; + } + + /** + * Returns the weight. + * + * @return the weight. + */ + public int getWeight() { + return weight; + } + } + + /** + * A comparator for sorting multiple weighted host addresses according to RFC 2782. + */ + public static class SrvRecordWeightedPriorityComparator implements Comparator, Serializable { + private static final long serialVersionUID = -9207293572898848260L; + + public int compare(HostAddress o1, HostAddress o2) { + if (o1 instanceof WeightedHostAddress && o2 instanceof WeightedHostAddress) { + WeightedHostAddress srv1 = (WeightedHostAddress) o1; + WeightedHostAddress srv2 = (WeightedHostAddress) o2; + // 16 bit unsigned priority is more important as the 16 bit weight + return ((srv1.priority << 15) - (srv2.priority << 15)) + (srv2.weight - srv1.weight); + } + else { + // This shouldn't happen but if we don't have priorities we sort the addresses + return o1.toString().compareTo(o2.toString()); + } + } + } } \ No newline at end of file