upgrade minidns. refactored dns utils
This commit is contained in:
parent
5d9d725446
commit
2e380ed792
|
@ -33,7 +33,7 @@ dependencies {
|
||||||
compile 'org.gnu.inet:libidn:1.15'
|
compile 'org.gnu.inet:libidn:1.15'
|
||||||
compile 'com.google.zxing:core:3.2.1'
|
compile 'com.google.zxing:core:3.2.1'
|
||||||
compile 'com.google.zxing:android-integration:3.2.1'
|
compile 'com.google.zxing:android-integration:3.2.1'
|
||||||
compile 'de.measite.minidns:minidns:0.1.7'
|
compile 'de.measite.minidns:minidns-hla:0.2.1'
|
||||||
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
||||||
compile 'me.leolin:ShortcutBadger:1.1.12@aar'
|
compile 'me.leolin:ShortcutBadger:1.1.12@aar'
|
||||||
compile 'com.kyleduo.switchbutton:library:1.2.8'
|
compile 'com.kyleduo.switchbutton:library:1.2.8'
|
||||||
|
|
|
@ -114,6 +114,7 @@ import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
|
||||||
import eu.siacs.conversations.utils.PRNGFixes;
|
import eu.siacs.conversations.utils.PRNGFixes;
|
||||||
import eu.siacs.conversations.utils.PhoneHelper;
|
import eu.siacs.conversations.utils.PhoneHelper;
|
||||||
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
|
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
|
||||||
|
import eu.siacs.conversations.utils.Resolver;
|
||||||
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
|
@ -983,6 +984,7 @@ public class XmppConnectionService extends Service {
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
ExceptionHelper.init(getApplicationContext());
|
ExceptionHelper.init(getApplicationContext());
|
||||||
PRNGFixes.apply();
|
PRNGFixes.apply();
|
||||||
|
Resolver.registerLookupMechanism(this);
|
||||||
this.mRandom = new SecureRandom();
|
this.mRandom = new SecureRandom();
|
||||||
updateMemorizingTrustmanager();
|
updateMemorizingTrustmanager();
|
||||||
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
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 java.net.Inet4Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
|
||||||
|
import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
|
||||||
|
|
||||||
|
public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
protected AndroidUsingLinkProperties(Context context) {
|
||||||
|
super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1);
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TargetApi(21)
|
||||||
|
public String[] getDnsServerAddresses() {
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
|
||||||
|
if (networks == null) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
List<String> servers = new ArrayList<>();
|
||||||
|
for(Network network : networks) {
|
||||||
|
LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
|
||||||
|
if (linkProperties != null) {
|
||||||
|
if (hasDefaultRoute(linkProperties)) {
|
||||||
|
servers.addAll(0, getIPv4First(linkProperties.getDnsServers()));
|
||||||
|
} else {
|
||||||
|
servers.addAll(getIPv4First(linkProperties.getDnsServers()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return servers.toArray(new String[servers.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getIPv4First(List<InetAddress> in) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
for(InetAddress addr : in) {
|
||||||
|
if (addr instanceof Inet4Address) {
|
||||||
|
out.add(0, addr.toString());
|
||||||
|
} else {
|
||||||
|
out.add(addr.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,22 @@
|
||||||
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class IP {
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
public static boolean matches(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.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
@ -20,7 +18,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.IDN;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
@ -67,8 +64,9 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
||||||
import eu.siacs.conversations.generator.IqGenerator;
|
import eu.siacs.conversations.generator.IqGenerator;
|
||||||
import eu.siacs.conversations.services.NotificationService;
|
import eu.siacs.conversations.services.NotificationService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
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.Patterns;
|
||||||
|
import eu.siacs.conversations.utils.Resolver;
|
||||||
import eu.siacs.conversations.utils.SSLSocketHelper;
|
import eu.siacs.conversations.utils.SSLSocketHelper;
|
||||||
import eu.siacs.conversations.utils.SocksSocketFactory;
|
import eu.siacs.conversations.utils.SocksSocketFactory;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
@ -329,7 +327,7 @@ public class XmppConnection implements Runnable {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException(e.getMessage());
|
throw new IOException(e.getMessage());
|
||||||
}
|
}
|
||||||
} else if (DNSHelper.isIp(account.getServer().toString())) {
|
} else if (IP.matches(account.getServer().toString())) {
|
||||||
localSocket = new Socket();
|
localSocket = new Socket();
|
||||||
try {
|
try {
|
||||||
localSocket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
|
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());
|
throw new IOException(e.getMessage());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
|
List<Resolver.Result> results = Resolver.resolve(account.getJid().getDomainpart());
|
||||||
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
|
Log.d(Config.LOGTAG,"results: "+results);
|
||||||
for (Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext(); ) {
|
for (Iterator<Resolver.Result> iterator = results.iterator(); iterator.hasNext(); ) {
|
||||||
|
final Resolver.Result result = iterator.next();
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Thread was interrupted");
|
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Thread was interrupted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Bundle namePort = (Bundle) iterator.next();
|
|
||||||
try {
|
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
|
// if tls is true, encryption is implied and must not be started
|
||||||
features.encryptionEnabled = namePort.getBoolean("tls");
|
features.encryptionEnabled = result.isDirectTls();
|
||||||
final InetSocketAddress addr;
|
final InetSocketAddress addr;
|
||||||
if (srvIpServer != null) {
|
if (result.getIp() != null) {
|
||||||
addr = new InetSocketAddress(srvIpServer, srvRecordPort);
|
addr = new InetSocketAddress(result.getIp(), result.getPort());
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
||||||
+ ": using values from dns " + srvRecordServer
|
+ ": using values from dns " + result.getHostname().toString()
|
||||||
+ "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
|
+ "[" + result.getIp().toString() + "]:" + result.getPort() + " tls: " + features.encryptionEnabled);
|
||||||
} else {
|
} else {
|
||||||
addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
|
addr = new InetSocketAddress(result.getHostname().toString(), result.getPort());
|
||||||
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
|
||||||
+ ": using values from dns "
|
+ ": using values from dns "
|
||||||
+ srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
|
+ result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!features.encryptionEnabled) {
|
if (!features.encryptionEnabled) {
|
||||||
|
|
Loading…
Reference in New Issue