diff options
Diffstat (limited to 'src/main/java')
3 files changed, 151 insertions, 208 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java b/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java new file mode 100644 index 00000000..3bc79c4f --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java @@ -0,0 +1,53 @@ +package de.thedevstack.conversationsplus.dto; + +/** + * An SRV record as it is currently used in Conversations Plus. + * The weight of the SRV record is skipped. + */ +public class SrvRecord implements Comparable<SrvRecord> { + private int priority; + private String name; + private int port; + + public SrvRecord(int priority, String name, int port) { + this.priority = priority; + this.name = name; + this.port = port; + } + + /** + * Compares this record to the specified record to determine their relative + * order. + * + * @param another the object to compare to this instance. + * @return a negative integer if the priority of this record is lower than the priority of {@code another}; + * a positive integer if the priority of this record is higher than + * {@code another}; 0 if the priority of this record is equal to the priority of + * {@code another}. + */ + @Override + public int compareTo(SrvRecord another) { + return this.getPriority() < another.getPriority() ? -1 : (this.getPriority() == another.getPriority() ? 0 : 1); + } + + @Override + public String toString() { + return "SrvRecord{" + + "priority=" + priority + + ", name='" + name + '\'' + + ", port=" + port + + '}'; + } + + public String getName() { + return name; + } + + public int getPort() { + return port; + } + + public int getPriority() { + return priority; + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index f755b74b..79a8c854 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -1,176 +1,98 @@ package eu.siacs.conversations.utils; -import java.io.IOException; -import java.net.InetAddress; -import java.net.SocketTimeoutException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Random; -import java.util.TreeMap; -import java.util.regex.Pattern; - -import android.os.Bundle; - import de.measite.minidns.Client; import de.measite.minidns.DNSMessage; import de.measite.minidns.Record; import de.measite.minidns.Record.TYPE; import de.measite.minidns.Record.CLASS; import de.measite.minidns.record.SRV; -import de.measite.minidns.record.A; -import de.measite.minidns.record.AAAA; import de.measite.minidns.record.Data; import de.measite.minidns.util.NameUtil; +import java.io.IOException; +import java.net.InetAddress; +import java.util.TreeSet; +import java.util.regex.Pattern; + import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.dto.SrvRecord; import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.jid.Jid; public class DNSHelper { - - public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); - public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); + private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp."; + private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); + private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); protected static Client client = new Client(); - public static Bundle getSRVRecord(final Jid jid) throws IOException { - final String host = jid.getDomainpart(); - String dns[] = client.findDNS(); - - if (dns != null) { - for (String dnsserver : dns) { - InetAddress ip = InetAddress.getByName(dnsserver); - Bundle b = queryDNS(host, ip); - if (b.containsKey("values")) { - return b; - } - } - } - return queryDNS(host, InetAddress.getByName("8.8.8.8")); - } - - public static Bundle queryDNS(String host, InetAddress dnsServer) { - Bundle bundle = new Bundle(); - try { - String qname = "_xmpp-client._tcp." + host; - Logging.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host); - DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress()); - - TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<>(); - TreeMap<String, ArrayList<String>> ips4 = new TreeMap<>(); - TreeMap<String, ArrayList<String>> ips6 = new TreeMap<>(); - - for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) { - for (Record rr : rrset) { - Data d = rr.getPayload(); - if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { - SRV srv = (SRV) d; - if (!priorities.containsKey(srv.getPriority())) { - priorities.put(srv.getPriority(),new ArrayList<SRV>()); - } - priorities.get(srv.getPriority()).add(srv); - } - if (d instanceof A) { - A a = (A) d; - if (!ips4.containsKey(rr.getName())) { - ips4.put(rr.getName(), new ArrayList<String>()); - } - ips4.get(rr.getName()).add(a.toString()); - } - if (d instanceof AAAA) { - AAAA aaaa = (AAAA) d; - if (!ips6.containsKey(rr.getName())) { - ips6.put(rr.getName(), new ArrayList<String>()); - } - ips6.get(rr.getName()).add("[" + aaaa.toString() + "]"); - } - } - } - - ArrayList<SRV> result = new ArrayList<>(); - for (ArrayList<SRV> s : priorities.values()) { - result.addAll(s); - } - - ArrayList<Bundle> values = new ArrayList<>(); - if (result.size() == 0) { - DNSMessage response; - response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); - for(int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(host,5222,response.getAnswers()[i].getPayload())); - } - response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); - for(int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(host,5222,response.getAnswers()[i].getPayload())); - } - values.add(createNamePortBundle(host,5222)); - bundle.putParcelableArrayList("values", values); - return bundle; - } - for (SRV srv : result) { - if (ips6.containsKey(srv.getName())) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6)); - } else { - DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); - for(int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload())); - } - } - if (ips4.containsKey(srv.getName())) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4)); - } else { - DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress()); - for(int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload())); - } - } - values.add(createNamePortBundle(srv.getName(), srv.getPort())); - } - bundle.putParcelableArrayList("values", values); - } catch (SocketTimeoutException e) { - bundle.putString("error", "timeout"); - } catch (Exception e) { - e.printStackTrace(); - bundle.putString("error", "unhandled"); - } - return bundle; - } - - private static Bundle createNamePortBundle(String name, int port) { - Bundle namePort = new Bundle(); - namePort.putString("name", name); - namePort.putInt("port", port); - return namePort; - } - - private static Bundle createNamePortBundle(String name, int port, TreeMap<String, ArrayList<String>> ips) { - Bundle namePort = new Bundle(); - namePort.putString("name", name); - namePort.putInt("port", port); - if (ips!=null) { - ArrayList<String> ip = ips.get(name); - Collections.shuffle(ip, new Random()); - namePort.putString("ip", ip.get(0)); - } - return namePort; - } - - private static Bundle createNamePortBundle(String name, int port, Data data) { - Bundle namePort = new Bundle(); - namePort.putString("name", name); - namePort.putInt("port", port); - if (data instanceof A) { - namePort.putString("ip", data.toString()); - } else if (data instanceof AAAA) { - namePort.putString("ip","["+data.toString()+"]"); - } - return namePort; - } - + /** + * Queries the SRV record for the server JID. + * This method uses all available Domain Name Servers. + * @param jid the server JID + * @return TreeSet with SrvRecords. If no SRV record is found for JID an empty TreeSet is returned. + */ + public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) { + String host = jid.getDomainpart(); + String dns[] = client.findDNS(); + TreeSet<SrvRecord> result = new TreeSet<>(); + + if (dns != null) { + for (String dnsserver : dns) { + result = querySrvRecord(host, dnsserver); + if (!result.isEmpty()) { + break; + } + } + } + + return result; + } + + /** + * Queries the SRV record for an host from the given Domain Name Server. + * @param host the host to query for + * @param dnsserver the DNS to query on + * @return TreeSet with SrvRecords. + */ + private static final TreeSet<SrvRecord> querySrvRecord(String host, String dnsserver) { + TreeSet<SrvRecord> result = new TreeSet<>(); + try { + InetAddress dnsServerAddress = InetAddress.getByName(dnsserver); + String qname = CLIENT_SRV_PREFIX + host; + DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerAddress.getHostAddress()); + Record[] rrset = message.getAnswers(); + for (Record rr : rrset) { + Data d = rr.getPayload(); + if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { + SRV srv = (SRV) d; + SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort()); + result.add(srvRecord); + } + } + } catch (IOException e) { + Logging.d("dns", "Error while retrieving SRV record for '" + host + "' from DNS '" + dnsserver + "': " + e.getMessage()); + } + return result; + } + + /** + * Checks whether the given server is an IP address or not. + * The following patterns are treated as valid IP addresses: + * <ul> + * <li>{@link #PATTERN_IPV4}</li> + * <li>{@link #PATTERN_IPV6}</li> + * <li>{@link #PATTERN_IPV6_6HEX4DEC}</li> + * <li>{@link #PATTERN_IPV6_HEX4DECCOMPRESSED}</li> + * <li>{@link #PATTERN_IPV6_HEXCOMPRESSED}</li> + * </ul> + * @param server the string to check + * @return <code>true</code> if one of the patterns is matched <code>false</code> otherwise + */ public static boolean isIp(final String server) { return PATTERN_IPV4.matcher(server).matches() || PATTERN_IPV6.matcher(server).matches() diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 39e3fa50..284787a0 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1,8 +1,6 @@ package eu.siacs.conversations.xmpp; import android.content.Context; -import android.os.Bundle; -import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; @@ -19,7 +17,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.ConnectException; -import java.net.IDN; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; @@ -35,6 +32,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeSet; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -43,6 +41,7 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.dto.SrvRecord; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.sasl.DigestMd5; @@ -75,11 +74,10 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; public class XmppConnection implements Runnable { - + private static final int DEFAULT_PORT = 5222; private static final int PACKET_IQ = 0; private static final int PACKET_MESSAGE = 1; private static final int PACKET_PRESENCE = 2; - private final Context applicationContext; protected Account account; private final WakeLock wakeLock; private Socket socket; @@ -121,7 +119,6 @@ public class XmppConnection implements Runnable { PowerManager.PARTIAL_WAKE_LOCK, account.getJid().toBareJid().toString()); tagWriter = new TagWriter(); mXmppConnectionService = service; - applicationContext = service.getApplicationContext(); } protected void changeStatus(final Account.State nextStatus) { @@ -157,59 +154,30 @@ public class XmppConnection implements Runnable { if (DNSHelper.isIp(account.getServer().toString())) { socket = new Socket(); try { - socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000); + socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000); } catch (IOException e) { throw new UnknownHostException(); } } else { - final Bundle result = DNSHelper.getSRVRecord(account.getServer()); - final ArrayList<Parcelable> values = result.getParcelableArrayList("values"); - if ("timeout".equals(result.getString("error"))) { - throw new IOException("timeout in dns"); - } else if (values != null) { - int i = 0; - boolean socketError = true; - while (socketError && values.size() > i) { - final Bundle namePort = (Bundle) values.get(i); - try { - String srvRecordServer; - try { - srvRecordServer = IDN.toASCII(namePort.getString("name")); - } catch (final IllegalArgumentException e) { - // TODO: Handle me?` - srvRecordServer = ""; - } - final int srvRecordPort = namePort.getInt("port"); - final String srvIpServer = namePort.getString("ip"); - final InetSocketAddress addr; - if (srvIpServer != null) { - addr = new InetSocketAddress(srvIpServer, srvRecordPort); - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": using values from dns " + srvRecordServer - + "[" + srvIpServer + "]:" + srvRecordPort); - } else { - addr = new InetSocketAddress(srvRecordServer, srvRecordPort); - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": using values from dns " - + srvRecordServer + ":" + srvRecordPort); - } - socket = new Socket(); - socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - socketError = false; - } catch (final UnknownHostException e) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); - i++; - } catch (final IOException e) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); - i++; - } - } - if (socketError) { - throw new UnknownHostException(); - } - } else { - throw new IOException("unhandled exception in DNS resolver"); - } + socket = new Socket(); + TreeSet<SrvRecord> result = DNSHelper.querySrvRecord(account.getServer()); + if (!result.isEmpty()) { + for (SrvRecord record : result) { + try { + socket.connect(new InetSocketAddress(record.getName(), record.getPort()), Config.SOCKET_TIMEOUT * 1000); + break; + } catch (IOException e) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); + } + } + } + if (result.isEmpty() || !socket.isConnected()){ + try { + socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000); + } catch (IOException e) { + throw new UnknownHostException(); + } + } } final OutputStream out = socket.getOutputStream(); tagWriter.setOutputStream(out); |