Commit cd51acc7 authored by Günther Niess's avatar Günther Niess Committed by niess

OF-44: Support multiple SRV records

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@11221 b35dd754-fafc-0310-a699-88a17e54d16e
parent 975a2d0f
...@@ -15,11 +15,18 @@ package org.jivesoftware.openfire.net; ...@@ -15,11 +15,18 @@ package org.jivesoftware.openfire.net;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes; import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext; import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext; import javax.naming.directory.InitialDirContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
...@@ -70,47 +77,62 @@ public class DNSUtil { ...@@ -70,47 +77,62 @@ public class DNSUtil {
* @param defaultPort default port to return if the DNS look up fails. * @param defaultPort default port to return if the DNS look up fails.
* @return a HostAddress, which encompasses the hostname and port that the XMPP * @return a HostAddress, which encompasses the hostname and port that the XMPP
* server can be reached at for the specified domain. * 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) { 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.<p>
*
* 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<HostAddress> resolveXMPPDomain(String domain, int defaultPort) {
// Check if there is an entry in the internal DNS for the specified domain // Check if there is an entry in the internal DNS for the specified domain
List<HostAddress> results = null;
if (dnsOverride != null) { if (dnsOverride != null) {
HostAddress hostAddress = dnsOverride.get(domain); HostAddress hostAddress = dnsOverride.get(domain);
if (hostAddress != null) { if (hostAddress != null) {
return hostAddress; results = new ArrayList<HostAddress>();
results.add(hostAddress);
return results;
} }
} }
if (context == null) {
return new HostAddress(domain, defaultPort); // Attempt the SRV lookup.
}
String host = domain;
int port = defaultPort;
try { try {
Attributes dnsLookup = results = srvLookup("_xmpp-server._tcp." + domain);
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];
} }
catch (Exception e) { catch (Exception e) {
// Attempt lookup with older "jabber" name. // Attempt lookup with older "jabber" name.
try { try {
Attributes dnsLookup = results = srvLookup("_jabber._tcp." + domain);
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) { catch (Exception e2) {
// Do nothing // Do nothing
} }
} }
// Host entries in DNS should end with a ".".
if (host.endsWith(".")) { // Use domain and default port as fallback.
host = host.substring(0, host.length()-1); if (results == null || results.isEmpty()) {
results.add(new HostAddress(domain, defaultPort));
} }
return new HostAddress(host, port); return results;
} }
/** /**
...@@ -164,6 +186,20 @@ public class DNSUtil { ...@@ -164,6 +186,20 @@ public class DNSUtil {
return answer; return answer;
} }
private static List<HostAddress> srvLookup(String lookup) throws NamingException {
Attributes dnsLookup =
context.getAttributes(lookup, new String[]{"SRV"});
Attribute srvRecords = dnsLookup.get("SRV");
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);
}
/** /**
* Encapsulates a hostname and port. * Encapsulates a hostname and port.
*/ */
...@@ -173,7 +209,13 @@ public class DNSUtil { ...@@ -173,7 +209,13 @@ public class DNSUtil {
private int port; private int port;
private HostAddress(String host, 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; this.port = port;
} }
...@@ -199,4 +241,62 @@ public class DNSUtil { ...@@ -199,4 +241,62 @@ public class DNSUtil {
return host + ":" + port; return host + ":" + port;
} }
} }
/**
* The representation of weighted address.
*/
public static class WeightedHostAddress extends HostAddress {
private int priority;
private 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<HostAddress> {
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
...@@ -39,6 +39,8 @@ import org.xmpp.packet.StreamError; ...@@ -39,6 +39,8 @@ import org.xmpp.packet.StreamError;
import java.io.*; import java.io.*;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
...@@ -181,17 +183,27 @@ public class ServerDialback { ...@@ -181,17 +183,27 @@ public class ServerDialback {
int realPort = port; int realPort = port;
try { try {
// Establish a TCP connection to the Receiving Server // Establish a TCP connection to the Receiving Server
// Get the real hostname to connect to using DNS lookup of the specified hostname
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname, port);
realHostname = address.getHost();
realPort = address.getPort();
Log.debug("ServerDialback: OS - Trying to connect to " + hostname + ":" + port +
"(DNS lookup: " + realHostname + ":" + realPort + ")");
// Connect to the remote server
Socket socket = new Socket(); Socket socket = new Socket();
socket.connect(new InetSocketAddress(realHostname, realPort), // Get a list of real hostnames to connect to using DNS lookup of the specified hostname
RemoteServerManager.getSocketTimeout()); List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port);
Log.debug("ServerDialback: OS - Connection to " + hostname + ":" + port + " successful"); for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
try {
DNSUtil.HostAddress address = it.next();
realHostname = address.getHost();
realPort = address.getPort();
Log.debug("ServerDialback: OS - Trying to connect to " + hostname + ":" + port +
"(DNS lookup: " + realHostname + ":" + realPort + ")");
// Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(realHostname, realPort),
RemoteServerManager.getSocketTimeout());
Log.debug("ServerDialback: OS - Connection to " + hostname + ":" + port + " successful");
break;
}
catch (Exception e) {
Log.warn("Error trying to connect to remote server: " + hostname +
"(DNS lookup: " + realHostname + ":" + realPort + ")", e);
}
}
connection = connection =
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket, new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
false); false);
...@@ -487,12 +499,37 @@ public class ServerDialback { ...@@ -487,12 +499,37 @@ public class ServerDialback {
else { else {
String key = doc.getTextTrim(); String key = doc.getTextTrim();
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname, // Get a list of real hostnames and try to connect using DNS lookup of the specified domain
List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname,
RemoteServerManager.getPortForServer(hostname)); RemoteServerManager.getPortForServer(hostname));
Socket socket = new Socket();
String realHostname = null;
int realPort;
for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
try {
DNSUtil.HostAddress address = it.next();
realHostname = address.getHost();
realPort = address.getPort();
Log.debug("ServerDialback: RS - Trying to connect to Authoritative Server: " + hostname +
"(DNS lookup: " + realHostname + ":" + realPort + ")");
// Establish a TCP connection to the Receiving Server
socket.connect(new InetSocketAddress(realHostname, realPort),
RemoteServerManager.getSocketTimeout());
Log.debug("ServerDialback: RS - Connection to AS: " + hostname + " successful");
break;
}
catch (Exception e) {
Log.warn("Error trying to connect to remote server: " + hostname +
"(DNS lookup: " + realHostname + ")", e);
}
}
if (!socket.isConnected()) {
Log.warn("No server available for verifying key of remote server: " + hostname);
return false;
}
try { try {
boolean valid = verifyKey(key, streamID.toString(), recipient, hostname, boolean valid = verifyKey(key, streamID.toString(), recipient, hostname, socket);
address.getHost(), address.getPort());
Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname); Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname);
sb = new StringBuilder(); sb = new StringBuilder();
...@@ -540,19 +577,12 @@ public class ServerDialback { ...@@ -540,19 +577,12 @@ public class ServerDialback {
* Verifies the key with the Authoritative Server. * Verifies the key with the Authoritative Server.
*/ */
private boolean verifyKey(String key, String streamID, String recipient, String hostname, private boolean verifyKey(String key, String streamID, String recipient, String hostname,
String host, int port) throws IOException, XmlPullParserException, Socket socket) throws IOException, XmlPullParserException,
RemoteConnectionFailedException { RemoteConnectionFailedException {
XMPPPacketReader reader; XMPPPacketReader reader;
Writer writer = null; Writer writer = null;
// Establish a TCP connection back to the domain name asserted by the Originating Server
Log.debug("ServerDialback: RS - Trying to connect to Authoritative Server: " + hostname + ":" + port +
"(DNS lookup: " + host + ":" + port + ")");
// Connect to the Authoritative server
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), RemoteServerManager.getSocketTimeout());
// Set a read timeout // Set a read timeout
socket.setSoTimeout(RemoteServerManager.getSocketTimeout()); socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
Log.debug("ServerDialback: RS - Connection to AS: " + hostname + ":" + port + " successful");
try { try {
reader = new XMPPPacketReader(); reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY); reader.setXPPFactory(FACTORY);
......
...@@ -43,6 +43,7 @@ import java.util.Collection; ...@@ -43,6 +43,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
...@@ -242,21 +243,27 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing ...@@ -242,21 +243,27 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
String realHostname = null; String realHostname = null;
int realPort = port; int realPort = port;
Socket socket = new Socket(); Socket socket = new Socket();
try { // Get a list of real hostnames to connect to using DNS lookup of the specified hostname
// Get the real hostname to connect to using DNS lookup of the specified hostname List<DNSUtil.HostAddress> hosts = DNSUtil.resolveXMPPDomain(hostname, port);
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname, port); for (Iterator<DNSUtil.HostAddress> it = hosts.iterator(); it.hasNext();) {
realHostname = address.getHost(); try {
realPort = address.getPort(); DNSUtil.HostAddress address = it.next();
Log.debug("LocalOutgoingServerSession: OS - Trying to connect to " + hostname + ":" + port + realHostname = address.getHost();
"(DNS lookup: " + realHostname + ":" + realPort + ")"); realPort = address.getPort();
// Establish a TCP connection to the Receiving Server Log.debug("LocalOutgoingServerSession: OS - Trying to connect to " + hostname + ":" + port +
socket.connect(new InetSocketAddress(realHostname, realPort), "(DNS lookup: " + realHostname + ":" + realPort + ")");
RemoteServerManager.getSocketTimeout()); // Establish a TCP connection to the Receiving Server
Log.debug("LocalOutgoingServerSession: OS - Plain connection to " + hostname + ":" + port + " successful"); socket.connect(new InetSocketAddress(realHostname, realPort),
RemoteServerManager.getSocketTimeout());
Log.debug("LocalOutgoingServerSession: OS - Plain connection to " + hostname + ":" + port + " successful");
break;
}
catch (Exception e) {
Log.warn("Error trying to connect to remote server: " + hostname +
"(DNS lookup: " + realHostname + ":" + realPort + ")", e);
}
} }
catch (Exception e) { if (!socket.isConnected()) {
Log.error("Error trying to connect to remote server: " + hostname +
"(DNS lookup: " + realHostname + ":" + realPort + ")", e);
return null; return null;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment