2017-06-21 21:28:01 +00:00
package eu.siacs.conversations.utils ;
import android.content.Context ;
import android.support.annotation.NonNull ;
import android.util.Log ;
import java.io.IOException ;
2017-07-09 17:05:23 +00:00
import java.net.Inet4Address ;
2017-06-21 21:28:01 +00:00
import java.net.InetAddress ;
import java.util.ArrayList ;
import java.util.Collections ;
2017-08-02 16:58:51 +00:00
import java.util.HashSet ;
2017-06-21 21:28:01 +00:00
import java.util.List ;
import de.measite.minidns.DNSClient ;
import de.measite.minidns.DNSName ;
2017-06-25 20:46:56 +00:00
import de.measite.minidns.dnssec.DNSSECResultNotAuthenticException ;
2017-09-21 08:31:21 +00:00
import de.measite.minidns.dnsserverlookup.AndroidUsingExec ;
2017-06-21 21:28:01 +00:00
import de.measite.minidns.hla.DnssecResolverApi ;
2017-06-25 20:46:56 +00:00
import de.measite.minidns.hla.ResolverApi ;
2017-06-21 21:28:01 +00:00
import de.measite.minidns.hla.ResolverResult ;
import de.measite.minidns.record.A ;
import de.measite.minidns.record.AAAA ;
2017-07-10 06:49:22 +00:00
import de.measite.minidns.record.CNAME ;
2017-06-25 20:46:56 +00:00
import de.measite.minidns.record.Data ;
2017-06-21 21:28:01 +00:00
import de.measite.minidns.record.InternetAddressRR ;
import de.measite.minidns.record.SRV ;
2017-08-02 16:58:51 +00:00
import de.measite.minidns.util.MultipleIoException ;
2017-06-21 21:28:01 +00:00
import eu.siacs.conversations.Config ;
2017-07-10 07:59:25 +00:00
import eu.siacs.conversations.R ;
import eu.siacs.conversations.services.XmppConnectionService ;
2017-06-21 21:28:01 +00:00
public class Resolver {
private static final String DIRECT_TLS_SERVICE = " _xmpps-client " ;
private static final String STARTTLS_SERICE = " _xmpp-client " ;
2017-08-02 16:58:51 +00:00
private static final String NETWORK_IS_UNREACHABLE = " Network is unreachable " ;
2017-07-10 07:59:25 +00:00
private static XmppConnectionService SERVICE = null ;
2017-06-21 21:28:01 +00:00
2017-07-10 07:59:25 +00:00
2017-09-21 08:31:21 +00:00
public static void init ( XmppConnectionService service ) {
2017-07-10 07:59:25 +00:00
Resolver . SERVICE = service ;
2017-09-21 08:31:21 +00:00
DNSClient . removeDNSServerLookupMechanism ( AndroidUsingExec . INSTANCE ) ;
DNSClient . addDnsServerLookupMechanism ( AndroidUsingExecLowPriority . INSTANCE ) ;
DNSClient . addDnsServerLookupMechanism ( new AndroidUsingLinkProperties ( service ) ) ;
2017-06-21 21:28:01 +00:00
}
2017-08-02 16:58:51 +00:00
public static List < Result > resolve ( String domain ) throws NetworkIsUnreachableException {
2017-06-21 21:28:01 +00:00
List < Result > results = new ArrayList < > ( ) ;
2017-08-02 16:58:51 +00:00
HashSet < String > messages = new HashSet < > ( ) ;
2017-06-21 21:28:01 +00:00
try {
2017-08-02 16:58:51 +00:00
results . addAll ( resolveSrv ( domain , true ) ) ;
} catch ( MultipleIoException e ) {
messages . addAll ( extractMessages ( e ) ) ;
2017-07-11 21:24:09 +00:00
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving SRV record (direct TLS) " , throwable ) ;
2017-06-21 21:28:01 +00:00
}
try {
2017-08-02 16:58:51 +00:00
results . addAll ( resolveSrv ( domain , false ) ) ;
} catch ( MultipleIoException e ) {
messages . addAll ( extractMessages ( e ) ) ;
2017-07-11 21:24:09 +00:00
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving SRV record (STARTTLS) " , throwable ) ;
2017-06-21 21:28:01 +00:00
}
if ( results . size ( ) = = 0 ) {
2017-08-02 16:58:51 +00:00
if ( messages . size ( ) = = 1 & & messages . contains ( NETWORK_IS_UNREACHABLE ) ) {
throw new NetworkIsUnreachableException ( ) ;
}
2017-07-10 10:33:04 +00:00
results . addAll ( resolveNoSrvRecords ( DNSName . from ( domain ) , true ) ) ;
2017-06-21 21:28:01 +00:00
}
Collections . sort ( results ) ;
2017-06-25 20:46:56 +00:00
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : " + results . toString ( ) ) ;
2017-06-21 21:28:01 +00:00
return results ;
}
2017-08-02 16:58:51 +00:00
private static HashSet < String > extractMessages ( MultipleIoException e ) {
HashSet < String > messages = new HashSet < > ( ) ;
for ( Exception inner : e . getExceptions ( ) ) {
if ( inner instanceof MultipleIoException ) {
messages . addAll ( extractMessages ( ( MultipleIoException ) inner ) ) ;
} else {
messages . add ( inner . getMessage ( ) ) ;
}
}
return messages ;
}
2017-06-21 21:28:01 +00:00
private static List < Result > resolveSrv ( String domain , final boolean directTls ) throws IOException {
2017-07-10 06:49:22 +00:00
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
2017-06-25 20:46:56 +00:00
return Collections . emptyList ( ) ;
}
2017-06-25 17:30:03 +00:00
DNSName dnsName = DNSName . from ( ( directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE ) + " ._tcp. " + domain ) ;
2017-06-25 20:46:56 +00:00
ResolverResult < SRV > result = resolveWithFallback ( dnsName , SRV . class ) ;
2017-06-21 21:28:01 +00:00
List < Result > results = new ArrayList < > ( ) ;
2017-06-25 17:30:03 +00:00
for ( SRV record : result . getAnswersOrEmptySet ( ) ) {
2017-06-25 20:46:56 +00:00
final boolean addedIPv4 = results . addAll ( resolveIp ( record , A . class , result . isAuthenticData ( ) , directTls ) ) ;
results . addAll ( resolveIp ( record , AAAA . class , result . isAuthenticData ( ) , directTls ) ) ;
2017-07-10 06:49:22 +00:00
if ( ! addedIPv4 & & ! Thread . currentThread ( ) . isInterrupted ( ) ) {
2017-06-25 17:30:03 +00:00
Result resolverResult = Result . fromRecord ( record , directTls ) ;
resolverResult . authenticated = resolverResult . isAuthenticated ( ) ;
results . add ( resolverResult ) ;
2017-06-21 21:28:01 +00:00
}
}
return results ;
}
private static < D extends InternetAddressRR > List < Result > resolveIp ( SRV srv , Class < D > type , boolean authenticated , boolean directTls ) {
2017-07-10 06:49:22 +00:00
if ( Thread . currentThread ( ) . isInterrupted ( ) ) {
2017-06-25 20:46:56 +00:00
return Collections . emptyList ( ) ;
}
2017-06-21 21:28:01 +00:00
List < Result > list = new ArrayList < > ( ) ;
try {
2017-08-05 13:17:10 +00:00
ResolverResult < D > results = resolveWithFallback ( srv . name , type , authenticated ) ;
2017-06-21 21:28:01 +00:00
for ( D record : results . getAnswersOrEmptySet ( ) ) {
Result resolverResult = Result . fromRecord ( srv , directTls ) ;
resolverResult . authenticated = results . isAuthenticData ( ) & & authenticated ;
resolverResult . ip = record . getInetAddress ( ) ;
list . add ( resolverResult ) ;
}
2017-06-25 16:35:40 +00:00
} catch ( Throwable t ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " " + t . getMessage ( ) ) ;
2017-06-21 21:28:01 +00:00
}
return list ;
}
2017-07-10 10:33:04 +00:00
private static List < Result > resolveNoSrvRecords ( DNSName dnsName , boolean withCnames ) {
2017-07-10 06:49:22 +00:00
List < Result > results = new ArrayList < > ( ) ;
try {
2017-07-10 07:59:25 +00:00
for ( A a : resolveWithFallback ( dnsName , A . class , false ) . getAnswersOrEmptySet ( ) ) {
2017-07-10 06:49:22 +00:00
results . add ( Result . createDefault ( dnsName , a . getInetAddress ( ) ) ) ;
}
2017-07-10 07:59:25 +00:00
for ( AAAA aaaa : resolveWithFallback ( dnsName , AAAA . class , false ) . getAnswersOrEmptySet ( ) ) {
2017-07-10 06:49:22 +00:00
results . add ( Result . createDefault ( dnsName , aaaa . getInetAddress ( ) ) ) ;
}
2017-07-10 10:33:04 +00:00
if ( results . size ( ) = = 0 & & withCnames ) {
2017-07-10 07:59:25 +00:00
for ( CNAME cname : resolveWithFallback ( dnsName , CNAME . class , false ) . getAnswersOrEmptySet ( ) ) {
2017-07-10 10:33:04 +00:00
results . addAll ( resolveNoSrvRecords ( cname . name , false ) ) ;
2017-07-10 06:49:22 +00:00
}
}
2017-07-10 10:33:04 +00:00
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " error resolving fallback records " , throwable ) ;
2017-07-10 06:49:22 +00:00
}
2017-07-12 20:19:45 +00:00
results . add ( Result . createDefault ( dnsName ) ) ;
2017-07-10 06:49:22 +00:00
return results ;
}
2017-06-25 20:46:56 +00:00
private static < D extends Data > ResolverResult < D > resolveWithFallback ( DNSName dnsName , Class < D > type ) throws IOException {
2017-07-10 07:59:25 +00:00
return resolveWithFallback ( dnsName , type , validateHostname ( ) ) ;
2017-07-10 06:49:22 +00:00
}
2017-07-10 07:59:25 +00:00
private static < D extends Data > ResolverResult < D > resolveWithFallback ( DNSName dnsName , Class < D > type , boolean validateHostname ) throws IOException {
if ( ! validateHostname ) {
2017-07-10 06:49:22 +00:00
return ResolverApi . INSTANCE . resolve ( dnsName , type ) ;
}
2017-06-25 20:46:56 +00:00
try {
2017-07-09 16:03:26 +00:00
final ResolverResult < D > r = DnssecResolverApi . INSTANCE . resolveDnssecReliable ( dnsName , type ) ;
if ( r . wasSuccessful ( ) ) {
if ( r . getAnswers ( ) . isEmpty ( ) & & type . equals ( SRV . class ) ) {
2017-07-10 06:49:22 +00:00
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : resolving SRV records of " + dnsName . toString ( ) + " with DNSSEC yielded empty result " ) ;
2017-07-09 16:03:26 +00:00
}
return r ;
}
2017-07-10 06:49:22 +00:00
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " with DNSSEC. Trying DNS instead. " , r . getResolutionUnsuccessfulException ( ) ) ;
2017-06-26 12:03:38 +00:00
} catch ( DNSSECResultNotAuthenticException e ) {
2017-07-10 06:49:22 +00:00
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " with DNSSEC. Trying DNS instead. " , e ) ;
2017-06-26 12:03:38 +00:00
} catch ( IOException e ) {
throw e ;
} catch ( Throwable throwable ) {
2017-07-10 06:49:22 +00:00
Log . d ( Config . LOGTAG , Resolver . class . getSimpleName ( ) + " : error resolving " + type . getSimpleName ( ) + " with DNSSEC. Trying DNS instead. " , throwable ) ;
2017-06-25 20:46:56 +00:00
}
2017-07-09 16:03:26 +00:00
return ResolverApi . INSTANCE . resolve ( dnsName , type ) ;
2017-06-25 20:46:56 +00:00
}
2017-07-10 07:59:25 +00:00
private static boolean validateHostname ( ) {
return SERVICE ! = null & & SERVICE . getBooleanPreference ( " validate_hostname " , R . bool . validate_hostname ) ;
}
2017-06-21 21:28:01 +00:00
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{ " +
2017-06-25 17:30:03 +00:00
" ip=' " + ( ip = = null ? null : ip . getHostAddress ( ) ) + '\'' +
2017-06-21 21:28:01 +00:00
" , 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 ) {
2017-06-25 20:46:56 +00:00
if ( ip = = null & & result . ip = = null ) {
return 0 ;
2017-07-09 17:05:23 +00:00
} else if ( ip ! = null & & result . ip ! = null ) {
if ( ip instanceof Inet4Address & & result . ip instanceof Inet4Address ) {
return 0 ;
} else {
return ip instanceof Inet4Address ? - 1 : 1 ;
}
2017-06-25 20:46:56 +00:00
} else {
return ip ! = null ? - 1 : 1 ;
}
2017-06-21 21:28:01 +00:00
} else {
2017-06-25 20:46:56 +00:00
return directTls ? - 1 : 1 ;
2017-06-21 21:28:01 +00:00
}
} 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 ;
}
2017-07-10 06:49:22 +00:00
public static Result createDefault ( DNSName hostname , InetAddress ip ) {
2017-06-21 21:28:01 +00:00
Result result = new Result ( ) ;
result . port = 5222 ;
2017-07-10 06:49:22 +00:00
result . hostname = hostname ;
result . ip = ip ;
2017-06-21 21:28:01 +00:00
return result ;
}
2017-07-10 06:49:22 +00:00
public static Result createDefault ( DNSName hostname ) {
return createDefault ( hostname , null ) ;
}
2017-06-21 21:28:01 +00:00
}
2017-08-02 16:58:51 +00:00
public static class NetworkIsUnreachableException extends Exception {
}
2017-06-21 21:28:01 +00:00
}