2017-06-21 23:28:01 +02:00
package eu.siacs.conversations.utils ;
2018-01-21 20:41:30 +01:00
import android.content.ContentValues ;
import android.database.Cursor ;
2017-06-21 23:28:01 +02:00
import android.support.annotation.NonNull ;
import android.util.Log ;
import java.io.IOException ;
2018-09-08 11:30:15 +02:00
import java.lang.reflect.Field ;
2017-06-21 23:28:01 +02:00
import java.net.InetAddress ;
2018-11-10 23:56:09 +01:00
import java.net.InetSocketAddress ;
import java.net.Socket ;
2018-01-21 20:41:30 +01:00
import java.net.UnknownHostException ;
2017-06-21 23:28:01 +02:00
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.List ;
2018-09-08 11:30:15 +02:00
import de.measite.minidns.AbstractDNSClient ;
2017-06-21 23:28:01 +02:00
import de.measite.minidns.DNSClient ;
import de.measite.minidns.DNSName ;
2017-09-21 21:46:11 +02:00
import de.measite.minidns.Question ;
import de.measite.minidns.Record ;
2017-06-25 22:46:56 +02:00
import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException ;
2017-09-21 10:31:21 +02:00
import de.measite.minidns.dnsserverlookup.AndroidUsingExec ;
2017-06-21 23:28:01 +02:00
import de.measite.minidns.hla.DnssecResolverApi ;
2017-06-25 22:46:56 +02:00
import de.measite.minidns.hla.ResolverApi ;
2017-06-21 23:28:01 +02:00
import de.measite.minidns.hla.ResolverResult ;
2018-09-08 11:30:15 +02:00
import de.measite.minidns.iterative.ReliableDNSClient ;
2017-06-21 23:28:01 +02:00
import de.measite.minidns.record.A ;
import de.measite.minidns.record.AAAA ;
2017-07-10 08:49:22 +02:00
import de.measite.minidns.record.CNAME ;
2017-06-25 22:46:56 +02:00
import de.measite.minidns.record.Data ;
2017-06-21 23:28:01 +02:00
import de.measite.minidns.record.InternetAddressRR ;
import de.measite.minidns.record.SRV ;
import eu.siacs.conversations.Config ;
2017-07-10 09:59:25 +02:00
import eu.siacs.conversations.R ;
import eu.siacs.conversations.services.XmppConnectionService ;
2017-06-21 23:28:01 +02:00
public class Resolver {
2018-09-08 11:30:15 +02:00
private static final String DIRECT_TLS_SERVICE = " _xmpps-client " ;
2018-11-10 23:56:09 +01:00
private static final String STARTTLS_SERVICE = " _xmpp-client " ;
2018-09-08 11:30:15 +02:00
private static XmppConnectionService SERVICE = null ;
public static void init ( XmppConnectionService service ) {
Resolver . SERVICE = service ;
DNSClient . removeDNSServerLookupMechanism ( AndroidUsingExec . INSTANCE ) ;
DNSClient . addDnsServerLookupMechanism ( AndroidUsingExecLowPriority . INSTANCE ) ;
DNSClient . addDnsServerLookupMechanism ( new AndroidUsingLinkProperties ( service ) ) ;
final AbstractDNSClient client = ResolverApi . INSTANCE . getClient ( ) ;
if ( client instanceof ReliableDNSClient ) {
disableHardcodedDnsServers ( ( ReliableDNSClient ) client ) ;
}
}
private static void disableHardcodedDnsServers ( ReliableDNSClient reliableDNSClient ) {
try {
final Field dnsClientField = ReliableDNSClient . class . getDeclaredField ( " dnsClient " ) ;
dnsClientField . setAccessible ( true ) ;
final DNSClient dnsClient = ( DNSClient ) dnsClientField . get ( reliableDNSClient ) ;
dnsClient . getDataSource ( ) . setTimeout ( 3000 ) ;
final Field useHardcodedDnsServers = DNSClient . class . getDeclaredField ( " useHardcodedDnsServers " ) ;
useHardcodedDnsServers . setAccessible ( true ) ;
useHardcodedDnsServers . setBoolean ( dnsClient , false ) ;
} catch ( NoSuchFieldException | IllegalAccessException e ) {
Log . e ( Config . LOGTAG , " Unable to disable hardcoded DNS servers " , e ) ;
}
}
2018-10-01 17:07:37 +02:00
public static List < Result > fromHardCoded ( String hostname , int port ) {
Result result = new Result ( ) ;
result . hostname = DNSName . from ( hostname ) ;
result . port = port ;
result . directTls = port = = 443 | | port = = 5223 ;
result . authenticated = true ;
return Collections . singletonList ( result ) ;
}
2018-09-08 11:30:15 +02:00
public static List < Result > resolve ( String domain ) {
2018-10-01 17:07:37 +02:00
final List < Result > ipResults = fromIpAddress ( domain ) ;
if ( ipResults . size ( ) > 0 ) {
return ipResults ;
}
2018-09-08 11:30:15 +02:00
final List < Result > results = new ArrayList < > ( ) ;
final List < Result > fallbackResults = new ArrayList < > ( ) ;
Thread [ ] threads = new Thread [ 3 ] ;
threads [ 0 ] = new Thread ( ( ) - > {
try {
final List < Result > list = resolveSrv ( domain , true ) ;
synchronized ( results ) {
results . addAll ( list ) ;
}
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving SRV record (direct TLS) " , throwable ) ;
}
} ) ;
threads [ 1 ] = new Thread ( ( ) - > {
try {
final List < Result > list = resolveSrv ( domain , false ) ;
synchronized ( results ) {
results . addAll ( list ) ;
}
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving SRV record (STARTTLS) " , throwable ) ;
}
} ) ;
threads [ 2 ] = new Thread ( ( ) - > {
List < Result > list = resolveNoSrvRecords ( DNSName . from ( domain ) , true ) ;
synchronized ( fallbackResults ) {
fallbackResults . addAll ( list ) ;
}
} ) ;
for ( Thread thread : threads ) {
thread . start ( ) ;
}
try {
threads [ 0 ] . join ( ) ;
threads [ 1 ] . join ( ) ;
if ( results . size ( ) > 0 ) {
threads [ 2 ] . interrupt ( ) ;
2018-09-20 20:29:21 +02:00
synchronized ( results ) {
Collections . sort ( results ) ;
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : " + results . toString ( ) ) ;
return new ArrayList < > ( results ) ;
}
2018-09-08 11:30:15 +02:00
} else {
threads [ 2 ] . join ( ) ;
2018-09-20 20:29:21 +02:00
synchronized ( fallbackResults ) {
Collections . sort ( fallbackResults ) ;
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : " + fallbackResults . toString ( ) ) ;
return new ArrayList < > ( fallbackResults ) ;
}
2018-09-08 11:30:15 +02:00
}
} catch ( InterruptedException e ) {
2018-10-01 17:07:37 +02:00
for ( Thread thread : threads ) {
2018-09-26 10:18:56 +02:00
thread . interrupt ( ) ;
}
2018-09-26 14:39:04 +02:00
return Collections . emptyList ( ) ;
2018-09-08 11:30:15 +02:00
}
}
2018-10-01 17:07:37 +02:00
private static List < Result > fromIpAddress ( String domain ) {
if ( ! IP . matches ( domain ) ) {
return Collections . emptyList ( ) ;
}
try {
Result result = new Result ( ) ;
result . ip = InetAddress . getByName ( domain ) ;
result . port = 5222 ;
return Collections . singletonList ( result ) ;
} catch ( UnknownHostException e ) {
return Collections . emptyList ( ) ;
}
}
2018-09-08 11:30:15 +02:00
private static List < Result > resolveSrv ( String domain , final boolean directTls ) throws IOException {
2018-11-10 23:56:09 +01:00
DNSName dnsName = DNSName . from ( ( directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE ) + " ._tcp. " + domain ) ;
2018-09-08 11:30:15 +02:00
ResolverResult < SRV > result = resolveWithFallback ( dnsName , SRV . class ) ;
final List < Result > results = new ArrayList < > ( ) ;
final List < Thread > threads = new ArrayList < > ( ) ;
for ( SRV record : result . getAnswersOrEmptySet ( ) ) {
if ( record . name . length ( ) = = 0 & & record . priority = = 0 ) {
continue ;
}
threads . add ( new Thread ( ( ) - > {
2018-11-10 23:56:09 +01:00
final List < Result > ipv6s = resolveIp ( record , AAAA . class , result . isAuthenticData ( ) , directTls ) ;
2018-09-08 11:30:15 +02:00
synchronized ( results ) {
2018-11-10 23:56:09 +01:00
results . addAll ( ipv6s ) ;
2018-09-08 11:30:15 +02:00
}
} ) ) ;
threads . add ( new Thread ( ( ) - > {
2018-11-10 23:56:09 +01:00
final List < Result > ipv4s = resolveIp ( record , A . class , result . isAuthenticData ( ) , directTls ) ;
2018-09-08 11:30:15 +02:00
synchronized ( results ) {
2018-11-10 23:56:09 +01:00
results . addAll ( ipv4s ) ;
2018-09-08 11:30:15 +02:00
}
2018-11-10 23:56:09 +01:00
2018-09-08 11:30:15 +02:00
} ) ) ;
}
for ( Thread thread : threads ) {
thread . start ( ) ;
}
for ( Thread thread : threads ) {
try {
thread . join ( ) ;
2018-11-10 23:56:09 +01:00
if ( results . size ( ) = = 0 ) {
for ( SRV record : result . getAnswersOrEmptySet ( ) ) {
if ( record . name . length ( ) = = 0 & & record . priority = = 0 ) {
continue ;
}
Result resolverResult = Result . fromRecord ( record , directTls ) ;
resolverResult . authenticated = resolverResult . isAuthenticated ( ) ;
results . add ( resolverResult ) ;
}
}
2018-09-08 11:30:15 +02:00
} catch ( InterruptedException e ) {
return Collections . emptyList ( ) ;
}
}
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 = resolveWithFallback ( srv . name , type , authenticated ) ;
for ( D record : results . getAnswersOrEmptySet ( ) ) {
Result resolverResult = Result . fromRecord ( srv , directTls ) ;
resolverResult . authenticated = results . isAuthenticData ( ) & & authenticated ;
resolverResult . ip = record . getInetAddress ( ) ;
2018-11-10 23:56:09 +01:00
resolverResult . rtt = rttTo ( resolverResult . ip , resolverResult . port ) ;
2018-09-08 11:30:15 +02:00
list . add ( resolverResult ) ;
}
} catch ( Throwable t ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " " + t . getMessage ( ) ) ;
}
return list ;
}
private static List < Result > resolveNoSrvRecords ( DNSName dnsName , boolean withCnames ) {
List < Result > results = new ArrayList < > ( ) ;
try {
for ( AAAA aaaa : resolveWithFallback ( dnsName , AAAA . class , false ) . getAnswersOrEmptySet ( ) ) {
results . add ( Result . createDefault ( dnsName , aaaa . getInetAddress ( ) ) ) ;
}
2018-11-10 23:56:09 +01:00
for ( A a : resolveWithFallback ( dnsName , A . class , false ) . getAnswersOrEmptySet ( ) ) {
results . add ( Result . createDefault ( dnsName , a . getInetAddress ( ) ) ) ;
}
2018-09-08 11:30:15 +02:00
if ( results . size ( ) = = 0 & & withCnames ) {
for ( CNAME cname : resolveWithFallback ( dnsName , CNAME . class , false ) . getAnswersOrEmptySet ( ) ) {
results . addAll ( resolveNoSrvRecords ( cname . name , false ) ) ;
}
}
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " error resolving fallback records " , throwable ) ;
}
results . add ( Result . createDefault ( dnsName ) ) ;
return results ;
}
private static < D extends Data > ResolverResult < D > resolveWithFallback ( DNSName dnsName , Class < D > type ) throws IOException {
return resolveWithFallback ( dnsName , type , validateHostname ( ) ) ;
}
private static < D extends Data > ResolverResult < D > resolveWithFallback ( DNSName dnsName , Class < D > type , boolean validateHostname ) throws IOException {
final Question question = new Question ( dnsName , Record . TYPE . getType ( type ) ) ;
if ( ! validateHostname ) {
return ResolverApi . INSTANCE . resolve ( question ) ;
}
try {
return DnssecResolverApi . INSTANCE . resolveDnssecReliable ( question ) ;
} catch ( DNSSECResultNotAuthenticException e ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " with DNSSEC. Trying DNS instead. " , e ) ;
} catch ( IOException e ) {
throw e ;
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " with DNSSEC. Trying DNS instead. " , throwable ) ;
}
return ResolverApi . INSTANCE . resolve ( question ) ;
}
2018-11-10 23:56:09 +01:00
private static long rttTo ( InetAddress ip , int port ) {
long time = System . currentTimeMillis ( ) ;
try ( Socket s = new Socket ( ) ) {
s . connect ( new InetSocketAddress ( ip , port ) , 200 ) ;
s . close ( ) ;
return System . currentTimeMillis ( ) - time ;
} catch ( Exception e ) {
Log . e ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error testing connection to " + ( ip = = null ? null : ip . getHostAddress ( ) ) + " : " + port ) ;
return - 1 ;
}
}
2018-09-08 11:30:15 +02:00
private static boolean validateHostname ( ) {
return SERVICE ! = null & & SERVICE . getBooleanPreference ( " validate_hostname " , R . bool . validate_hostname ) ;
}
public static class Result implements Comparable < Result > {
public static final String DOMAIN = " domain " ;
public static final String IP = " ip " ;
public static final String HOSTNAME = " hostname " ;
public static final String PORT = " port " ;
public static final String PRIORITY = " priority " ;
public static final String DIRECT_TLS = " directTls " ;
public static final String AUTHENTICATED = " authenticated " ;
private InetAddress ip ;
private DNSName hostname ;
private int port = 5222 ;
private boolean directTls = false ;
private boolean authenticated = false ;
private int priority ;
2018-11-10 23:56:09 +01:00
private long rtt = - 1 ;
2018-09-08 11:30:15 +02:00
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 ;
}
static Result createDefault ( DNSName hostname , InetAddress ip ) {
Result result = new Result ( ) ;
result . port = 5222 ;
result . hostname = hostname ;
result . ip = ip ;
2018-11-10 23:56:09 +01:00
if ( ip ! = null ) {
result . rtt = rttTo ( result . ip , result . port ) ;
}
2018-09-08 11:30:15 +02:00
return result ;
}
static Result createDefault ( DNSName hostname ) {
return createDefault ( hostname , null ) ;
}
public static Result fromCursor ( Cursor cursor ) {
final Result result = new Result ( ) ;
try {
result . ip = InetAddress . getByAddress ( cursor . getBlob ( cursor . getColumnIndex ( IP ) ) ) ;
} catch ( UnknownHostException e ) {
result . ip = null ;
}
2018-10-13 16:13:33 +02:00
final String hostname = cursor . getString ( cursor . getColumnIndex ( HOSTNAME ) ) ;
result . hostname = hostname = = null ? null : DNSName . from ( hostname ) ;
2018-09-08 11:30:15 +02:00
result . port = cursor . getInt ( cursor . getColumnIndex ( PORT ) ) ;
result . priority = cursor . getInt ( cursor . getColumnIndex ( PRIORITY ) ) ;
result . authenticated = cursor . getInt ( cursor . getColumnIndex ( AUTHENTICATED ) ) > 0 ;
result . directTls = cursor . getInt ( cursor . getColumnIndex ( DIRECT_TLS ) ) > 0 ;
return result ;
}
@Override
public boolean equals ( Object o ) {
if ( this = = o ) return true ;
if ( o = = null | | getClass ( ) ! = o . getClass ( ) ) return false ;
Result result = ( Result ) o ;
if ( port ! = result . port ) return false ;
if ( directTls ! = result . directTls ) return false ;
if ( authenticated ! = result . authenticated ) return false ;
if ( priority ! = result . priority ) return false ;
if ( ip ! = null ? ! ip . equals ( result . ip ) : result . ip ! = null ) return false ;
return hostname ! = null ? hostname . equals ( result . hostname ) : result . hostname = = null ;
}
@Override
public int hashCode ( ) {
int result = ip ! = null ? ip . hashCode ( ) : 0 ;
result = 31 * result + ( hostname ! = null ? hostname . hashCode ( ) : 0 ) ;
result = 31 * result + port ;
result = 31 * result + ( directTls ? 1 : 0 ) ;
result = 31 * result + ( authenticated ? 1 : 0 ) ;
result = 31 * result + priority ;
return result ;
}
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 = = null ? null : ip . getHostAddress ( ) ) + '\'' +
" , hostame=' " + hostname . toString ( ) + '\'' +
" , port= " + port +
" , directTls= " + directTls +
" , authenticated= " + authenticated +
" , priority= " + priority +
2018-11-10 23:56:09 +01:00
" , rtt= " + rtt +
2018-09-08 11:30:15 +02:00
'}' ;
}
@Override
public int compareTo ( @NonNull Result result ) {
if ( result . priority = = priority ) {
if ( directTls = = result . directTls ) {
if ( ip = = null & & result . ip = = null ) {
return 0 ;
} else if ( ip ! = null & & result . ip ! = null ) {
2018-11-10 23:56:09 +01:00
if ( rtt = = - 1 | | result . rtt = = - 1 ) {
Log . e ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : unable to read round trip time servers on compare between " + toString ( ) + " and " + result . toString ( ) ) ;
2018-09-08 11:30:15 +02:00
}
2018-11-10 23:56:09 +01:00
return rtt < result . rtt ? - 1 : 1 ;
2018-09-08 11:30:15 +02:00
} else {
return ip ! = null ? - 1 : 1 ;
}
} else {
return directTls ? - 1 : 1 ;
}
} else {
return priority - result . priority ;
}
}
public ContentValues toContentValues ( ) {
final ContentValues contentValues = new ContentValues ( ) ;
contentValues . put ( IP , ip = = null ? null : ip . getAddress ( ) ) ;
2018-10-13 16:13:33 +02:00
contentValues . put ( HOSTNAME , hostname = = null ? null : hostname . toString ( ) ) ;
2018-09-08 11:30:15 +02:00
contentValues . put ( PORT , port ) ;
contentValues . put ( PRIORITY , priority ) ;
contentValues . put ( DIRECT_TLS , directTls ? 1 : 0 ) ;
contentValues . put ( AUTHENTICATED , authenticated ? 1 : 0 ) ;
return contentValues ;
}
}
2017-06-21 23:28:01 +02:00
}