@@ -1,299 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.RouteInfo;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Random;
-import java.util.TreeMap;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-import de.measite.minidns.Client;
-import de.measite.minidns.DNSMessage;
-import de.measite.minidns.Record;
-import de.measite.minidns.Record.CLASS;
-import de.measite.minidns.Record.TYPE;
-import de.measite.minidns.record.A;
-import de.measite.minidns.record.AAAA;
-import de.measite.minidns.record.Data;
-import de.measite.minidns.record.SRV;
-import de.measite.minidns.util.NameUtil;
-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");
-
- protected static Client client = new Client();
-
- protected static Context context;
-
- public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException {
- DNSHelper.context = context;
- final String host = jid.getDomainpart();
- final List<InetAddress> servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop();
- Bundle b = new Bundle();
- boolean interrupted = false;
- for(InetAddress server : servers) {
- if (Thread.currentThread().isInterrupted()) {
- interrupted = true;
- break;
- }
- b = queryDNS(host, server);
- if (b.containsKey("values")) {
- return b;
- }
- }
- if (!b.containsKey("values")) {
- Log.d(Config.LOGTAG,(interrupted ? "Thread interrupted during DNS query" :"all dns queries failed") + ". provide fallback A record");
- ArrayList<Parcelable> values = new ArrayList<>();
- values.add(createNamePortBundle(host, 5222, false));
- b.putParcelableArrayList("values",values);
- }
- return b;
- }
-
- @TargetApi(21)
- private static List<InetAddress> getDnsServers(Context context) {
- List<InetAddress> servers = new ArrayList<>();
- ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
- if (networks == null) {
- return getDnsServersPreLollipop();
- }
- for(int i = 0; i < networks.length; ++i) {
- LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]);
- if (linkProperties != null) {
- if (hasDefaultRoute(linkProperties)) {
- servers.addAll(0, getIPv4First(linkProperties.getDnsServers()));
- } else {
- servers.addAll(getIPv4First(linkProperties.getDnsServers()));
- }
- }
- }
- if (servers.size() > 0) {
- Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks");
- }
- return servers.size() > 0 ? servers : getDnsServersPreLollipop();
- }
-
- private static List<InetAddress> getIPv4First(List<InetAddress> in) {
- List<InetAddress> out = new ArrayList<>();
- for(InetAddress addr : in) {
- if (addr instanceof Inet4Address) {
- out.add(0, addr);
- } else {
- out.add(addr);
- }
- }
- return out;
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private static boolean hasDefaultRoute(LinkProperties linkProperties) {
- for(RouteInfo route: linkProperties.getRoutes()) {
- if (route.isDefaultRoute()) {
- return true;
- }
- }
- return false;
- }
-
- private static List<InetAddress> getDnsServersPreLollipop() {
- List<InetAddress> servers = new ArrayList<>();
- String[] dns = client.findDNS();
- for(int i = 0; i < dns.length; ++i) {
- try {
- servers.add(InetAddress.getByName(dns[i]));
- } catch (UnknownHostException e) {
- //ignore
- }
- }
- return servers;
- }
-
- private static class TlsSrv {
- private final SRV srv;
- private final boolean tls;
-
- public TlsSrv(SRV srv, boolean tls) {
- this.srv = srv;
- this.tls = tls;
- }
- }
-
- private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException {
- final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
- for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
- for (Record rr : rrset) {
- Data d = rr.getPayload();
- final String name = rr.getName() != null ? rr.getName().toLowerCase(Locale.US) : null;
- if (d instanceof SRV && NameUtil.idnEquals(qname, name)) {
- SRV srv = (SRV) d;
- if (!priorities.containsKey(srv.getPriority())) {
- priorities.put(srv.getPriority(),new ArrayList<TlsSrv>());
- }
- priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls));
- } else if (d instanceof SRV) {
- Log.d(Config.LOGTAG,"found unrecognized SRV record with name: "+name);
- }
- if (d instanceof A) {
- A a = (A) d;
- if (!ips4.containsKey(name)) {
- ips4.put(name, new ArrayList<String>());
- }
- ips4.get(name).add(a.toString());
- }
- if (d instanceof AAAA) {
- AAAA aaaa = (AAAA) d;
- if (!ips6.containsKey(name)) {
- ips6.put(name, new ArrayList<String>());
- }
- ips6.get(name).add("[" + aaaa.toString() + "]");
- }
- }
- }
- }
-
- public static Bundle queryDNS(String host, InetAddress dnsServer) {
- Bundle bundle = new Bundle();
- try {
- client.setTimeout(Config.SOCKET_TIMEOUT * 1000);
- final String qname = "_xmpp-client._tcp." + host.toLowerCase(Locale.US);
- final String tlsQname = "_xmpps-client._tcp." + host.toLowerCase(Locale.US);
- Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
-
- final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>();
- final Map<String, List<String>> ips4 = new TreeMap<>();
- final Map<String, List<String>> ips6 = new TreeMap<>();
-
- fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false);
- fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true);
-
- final List<TlsSrv> result = new ArrayList<>();
- for (final List<TlsSrv> s : priorities.values()) {
- result.addAll(s);
- }
-
- final ArrayList<Bundle> values = new ArrayList<>();
- if (result.size() == 0) {
- DNSMessage response;
- try {
- 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(), false));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
- }
- try {
- 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(), false));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
- }
- values.add(createNamePortBundle(host, 5222, false));
- bundle.putParcelableArrayList("values", values);
- return bundle;
- }
- for (final TlsSrv tlsSrv : result) {
- final SRV srv = tlsSrv.srv;
- final String name = srv.getName() != null ? srv.getName().toLowerCase(Locale.US) : null;
- if (ips6.containsKey(name)) {
- values.add(createNamePortBundle(name,srv.getPort(),ips6, tlsSrv.tls));
- } else {
- try {
- DNSMessage response = client.query(name, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(name, srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
- }
- }
- if (ips4.containsKey(name)) {
- values.add(createNamePortBundle(name,srv.getPort(),ips4, tlsSrv.tls));
- } else {
- DNSMessage response = client.query(name, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
- for(int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(name,srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls));
- }
- }
- values.add(createNamePortBundle(name, srv.getPort(), tlsSrv.tls));
- }
- bundle.putParcelableArrayList("values", values);
- } catch (SocketTimeoutException e) {
- bundle.putString("error", "timeout");
- } catch (Exception e) {
- bundle.putString("error", "unhandled");
- }
- return bundle;
- }
-
- private static Bundle createNamePortBundle(String name, int port, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- return namePort;
- }
-
- private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- if (ips!=null) {
- List<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, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- 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;
- }
-
- public static boolean isIp(final String server) {
- return server != null && (
- PATTERN_IPV4.matcher(server).matches()
- || PATTERN_IPV6.matcher(server).matches()
- || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
- || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
- || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
- }
-}
@@ -0,0 +1,163 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import de.measite.minidns.DNSClient;
+import de.measite.minidns.DNSName;
+import de.measite.minidns.Question;
+import de.measite.minidns.Record;
+import de.measite.minidns.hla.DnssecResolverApi;
+import de.measite.minidns.hla.ResolverResult;
+import de.measite.minidns.record.A;
+import de.measite.minidns.record.AAAA;
+import de.measite.minidns.record.Data;
+import de.measite.minidns.record.InternetAddressRR;
+import de.measite.minidns.record.SRV;
+import eu.siacs.conversations.Config;
+
+public class Resolver {
+
+ private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
+ private static final String STARTTLS_SERICE = "_xmpp-client";
+
+
+
+
+ public static void registerLookupMechanism(Context context) {
+ DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(context));
+ }
+
+ public static List<Result> resolve(String domain) {
+ List<Result> results = new ArrayList<>();
+ try {
+ results.addAll(resolveSrv(domain,true));
+ } catch (IOException e) {
+ //ignore
+ }
+ try {
+ results.addAll(resolveSrv(domain,false));
+ } catch (IOException e) {
+ //ignore
+ }
+ if (results.size() == 0) {
+ results.add(Result.createDefault(domain));
+ }
+ Collections.sort(results);
+ return results;
+ }
+
+ private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
+ Question question = new Question((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE)+"._tcp."+domain,Record.TYPE.SRV);
+ ResolverResult<Data> result = DnssecResolverApi.INSTANCE.resolve(question);
+ List<Result> results = new ArrayList<>();
+ for(Data record : result.getAnswersOrEmptySet()) {
+ if (record instanceof SRV) {
+ SRV srvRecord = (SRV) record;
+ boolean added = results.addAll(resolveIp(srvRecord,A.class,result.isAuthenticData(),directTls));
+ added |= results.addAll(resolveIp(srvRecord,AAAA.class,result.isAuthenticData(),directTls));
+ if (!added) {
+ Result resolverResult = Result.fromRecord(srvRecord, directTls);
+ resolverResult.authenticated = resolverResult.isAuthenticated();
+ results.add(resolverResult);
+ }
+ }
+ }
+ return results;
+ }
+
+ private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
+ List<Result> list = new ArrayList<>();
+ try {
+ ResolverResult<D> results = DnssecResolverApi.INSTANCE.resolve(srv.name, type);
+ for (D record : results.getAnswersOrEmptySet()) {
+ Result resolverResult = Result.fromRecord(srv, directTls);
+ resolverResult.authenticated = results.isAuthenticData() && authenticated;
+ resolverResult.ip = record.getInetAddress();
+ list.add(resolverResult);
+ }
+ } catch (IOException e) {
+ Log.d(Config.LOGTAG,e.getMessage());
+ //ignore. will add default record later
+ }
+ return list;
+ }
+
+ public static class Result implements Comparable<Result> {
+ private InetAddress ip;
+ private DNSName hostname;
+ private int port = 5222;
+ private boolean directTls = false;
+ private boolean authenticated =false;
+ private int priority;
+
+ public InetAddress getIp() {
+ return ip;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public DNSName getHostname() {
+ return hostname;
+ }
+
+ public boolean isDirectTls() {
+ return directTls;
+ }
+
+ public boolean isAuthenticated() {
+ return authenticated;
+ }
+
+ @Override
+ public String toString() {
+ return "Result{" +
+ "ip='" + ip + '\'' +
+ ", hostame='" + hostname.toString() + '\'' +
+ ", port=" + port +
+ ", directTls=" + directTls +
+ ", authenticated=" + authenticated +
+ ", priority=" + priority +
+ '}';
+ }
+
+ @Override
+ public int compareTo(@NonNull Result result) {
+ if (result.priority == priority) {
+ if (directTls == result.directTls) {
+ return 0;
+ } else {
+ return directTls ? 1 : -1;
+ }
+ } else {
+ return priority - result.priority;
+ }
+ }
+
+ public static Result fromRecord(SRV srv, boolean directTls) {
+ Result result = new Result();
+ result.port = srv.port;
+ result.hostname = srv.name;
+ result.directTls = directTls;
+ result.priority = srv.priority;
+ return result;
+ }
+
+ public static Result createDefault(String domain) {
+ Result result = new Result();
+ result.port = 5222;
+ result.hostname = DNSName.from(domain);
+ return result;
+ }
+ }
+
+}
@@ -2,8 +2,6 @@ package eu.siacs.conversations.xmpp;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.os.Bundle;
-import android.os.Parcelable;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
@@ -20,7 +18,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.ConnectException;
-import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -67,8 +64,9 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.services.NotificationService;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.DNSHelper;
+import eu.siacs.conversations.utils.IP;
import eu.siacs.conversations.utils.Patterns;
+import eu.siacs.conversations.utils.Resolver;
import eu.siacs.conversations.utils.SSLSocketHelper;
import eu.siacs.conversations.utils.SocksSocketFactory;
import eu.siacs.conversations.xml.Element;
@@ -329,7 +327,7 @@ public class XmppConnection implements Runnable {
} catch (Exception e) {
throw new IOException(e.getMessage());
}
- } else if (DNSHelper.isIp(account.getServer().toString())) {
+ } else if (IP.matches(account.getServer().toString())) {
localSocket = new Socket();
try {
localSocket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
@@ -345,37 +343,28 @@ public class XmppConnection implements Runnable {
throw new IOException(e.getMessage());
}
} else {
- final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
- final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
- for (Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext(); ) {
+ List<Resolver.Result> results = Resolver.resolve(account.getJid().getDomainpart());
+ Log.d(Config.LOGTAG,"results: "+results);
+ for (Iterator<Resolver.Result> iterator = results.iterator(); iterator.hasNext(); ) {
+ final Resolver.Result result = iterator.next();
if (Thread.currentThread().isInterrupted()) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Thread was interrupted");
return;
}
- final Bundle namePort = (Bundle) iterator.next();
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");
// if tls is true, encryption is implied and must not be started
- features.encryptionEnabled = namePort.getBoolean("tls");
+ features.encryptionEnabled = result.isDirectTls();
final InetSocketAddress addr;
- if (srvIpServer != null) {
- addr = new InetSocketAddress(srvIpServer, srvRecordPort);
+ if (result.getIp() != null) {
+ addr = new InetSocketAddress(result.getIp(), result.getPort());
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns " + srvRecordServer
- + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
+ + ": using values from dns " + result.getHostname().toString()
+ + "[" + result.getIp().toString() + "]:" + result.getPort() + " tls: " + features.encryptionEnabled);
} else {
- addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
+ addr = new InetSocketAddress(result.getHostname().toString(), result.getPort());
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
+ + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled);
}
if (!features.encryptionEnabled) {