@ -30,18 +30,23 @@ import android.text.TextUtils;
import androidx.annotation.NonNull ;
import androidx.annotation.NonNull ;
import org.xbill.DNS.AAAARecord ;
import org.minidns.DnsClient ;
import org.xbill.DNS.ARecord ;
import org.minidns.dnsmessage.DnsMessage ;
import org.xbill.DNS.Lookup ;
import org.minidns.dnsqueryresult.DnsQueryResult ;
import org.xbill.DNS.MXRecord ;
import org.minidns.dnsqueryresult.StandardDnsQueryResult ;
import org.xbill.DNS.Message ;
import org.minidns.dnssec.DnssecValidationFailedException ;
import org.xbill.DNS.NSRecord ;
import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism ;
import org.xbill.DNS.Record ;
import org.minidns.hla.DnssecResolverApi ;
import org.xbill.DNS.SOARecord ;
import org.minidns.hla.ResolverApi ;
import org.xbill.DNS.SRVRecord ;
import org.minidns.hla.ResolverResult ;
import org.xbill.DNS.SimpleResolver ;
import org.minidns.record.A ;
import org.xbill.DNS.TXTRecord ;
import org.minidns.record.AAAA ;
import org.xbill.DNS.Type ;
import org.minidns.record.Data ;
import org.minidns.record.MX ;
import org.minidns.record.NS ;
import org.minidns.record.SRV ;
import org.minidns.record.TXT ;
import org.minidns.source.AbstractDnsDataSource ;
import java.io.IOException ;
import java.io.IOException ;
import java.net.InetAddress ;
import java.net.InetAddress ;
@ -49,6 +54,7 @@ import java.net.UnknownHostException;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.List ;
import java.util.Locale ;
import java.util.Locale ;
import java.util.Set ;
import java.util.concurrent.Executor ;
import java.util.concurrent.Executor ;
import java.util.concurrent.Semaphore ;
import java.util.concurrent.Semaphore ;
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.TimeUnit ;
@ -76,12 +82,12 @@ public class DnsHelper {
}
}
@NonNull
@NonNull
static DnsRecord [ ] lookup ( Context context , String name , String type ) throws UnknownHostException {
static DnsRecord [ ] lookup ( Context context , String name , String type ) {
return lookup ( context , name , type , LOOKUP_TIMEOUT ) ;
return lookup ( context , name , type , LOOKUP_TIMEOUT ) ;
}
}
@NonNull
@NonNull
static DnsRecord [ ] lookup ( Context context , String name , String type , int timeout ) throws UnknownHostException {
static DnsRecord [ ] lookup ( Context context , String name , String type , int timeout ) {
String filter = null ;
String filter = null ;
int colon = type . indexOf ( ':' ) ;
int colon = type . indexOf ( ':' ) ;
if ( colon > 0 ) {
if ( colon > 0 ) {
@ -89,53 +95,46 @@ public class DnsHelper {
type = type . substring ( 0 , colon ) ;
type = type . substring ( 0 , colon ) ;
}
}
int rtype ;
Class < ? extends Data > clazz ;
switch ( type ) {
switch ( type ) {
case "ns" :
case "ns" :
rtype = Type . NS ;
clazz = NS . class ;
break ;
break ;
case "mx" :
case "mx" :
rtype = Type . MX ;
clazz = MX . class ;
break ;
case "soa" :
rtype = Type . SOA ;
break ;
break ;
case "srv" :
case "srv" :
rtype = Type . SRV ;
clazz = SRV . class ;
break ;
break ;
case "txt" :
case "txt" :
rtype = Type . TXT ;
clazz = TXT . class ;
break ;
break ;
case "a" :
case "a" :
rtype = Type . A ;
clazz = A . class ;
break ;
break ;
case "aaaa" :
case "aaaa" :
rtype = Type . AAAA ;
clazz = AAAA . class ;
break ;
break ;
default :
default :
throw new IllegalArgumentException ( type ) ;
throw new IllegalArgumentException ( type ) ;
}
}
try {
try {
SimpleResolver resolver = new SimpleResolver ( getDnsServer ( context ) ) {
ResolverApi resolver = DnssecResolverApi . INSTANCE ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . Q )
resolver . getClient ( ) . setDataSource ( new AbstractDnsDataSource ( ) {
private IOException ex ;
private IOException ex ;
private Message result ;
private DnsQueryResult result ;
@Override
@Override
public Message send ( Message query ) throws IOException {
public DnsQueryResult query ( DnsMessage query , InetAddress address , int port ) throws IOException {
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . Q )
return super . send ( query ) ;
else {
Log . i ( "Using Android DNS resolver" ) ;
Semaphore sem = new Semaphore ( 0 ) ;
Semaphore sem = new Semaphore ( 0 ) ;
DnsResolver resolver = DnsResolver . getInstance ( ) ;
DnsResolver resolver = DnsResolver . getInstance ( ) ;
//OPTRecord optRecord = new OPTRecord(4096, 0, 0, Flags.DO, null);
Log . i ( "Android DNS query=" + query ) ;
//query.addRecord(optRecord, Section.ADDITIONAL);
//query.getHeader().setFlag(Flags.AD);
Log . i ( "DNS query=" + query . toString ( ) ) ;
resolver . rawQuery (
resolver . rawQuery (
null ,
null ,
query . to Wire ( ) ,
query . toArray ( ) ,
DnsResolver . FLAG_EMPTY ,
DnsResolver . FLAG_EMPTY ,
new Executor ( ) {
new Executor ( ) {
@Override
@Override
@ -146,14 +145,19 @@ public class DnsHelper {
null ,
null ,
new DnsResolver . Callback < byte [ ] > ( ) {
new DnsResolver . Callback < byte [ ] > ( ) {
@Override
@Override
public void onAnswer ( @NonNull byte [ ] answer , int rcode ) {
public void onAnswer ( @NonNull byte [ ] bytes , int rcode ) {
try {
try {
if ( rcode = = 0 )
DnsMessage answer = new DnsMessage ( bytes )
result = new Message ( answer ) ;
. asBuilder ( )
else
. setResponseCode ( DnsMessage . RESPONSE_CODE . getResponseCode ( rcode ) )
ex = new IOException ( "rcode=" + rcode ) ;
. build ( ) ;
result = new StandardDnsQueryResult (
address , port ,
DnsQueryResult . QueryMethod . udp ,
query ,
answer ) ;
} catch ( Throwable e ) {
} catch ( Throwable e ) {
ex = new IOException ( e . getMessage ( ) ) ;
ex = new IOException ( e . getMessage ( ) , e );
} finally {
} finally {
sem . release ( ) ;
sem . release ( ) ;
}
}
@ -168,6 +172,7 @@ public class DnsHelper {
}
}
}
}
} ) ;
} ) ;
try {
try {
if ( ! sem . tryAcquire ( timeout , TimeUnit . SECONDS ) )
if ( ! sem . tryAcquire ( timeout , TimeUnit . SECONDS ) )
ex = new IOException ( "timeout" ) ;
ex = new IOException ( "timeout" ) ;
@ -176,53 +181,58 @@ public class DnsHelper {
}
}
if ( ex = = null ) {
if ( ex = = null ) {
//ConnectivityManager cm = getSystemService(context, ConnectivityManager.class);
Log . i ( "Android DNS answer=" + result ) ;
//Network active = (cm == null ? null : cm.getActiveNetwork());
//LinkProperties props = (active == null ? null : cm.getLinkProperties(active));
//Log.i("DNS private=" + (props == null ? null : props.isPrivateDnsActive()));
Log . i ( "DNS answer=" + result . toString ( ) + " flags=" + result . getHeader ( ) . printFlags ( ) ) ;
return result ;
return result ;
} else {
} else {
Log . i ( ex ) ;
Log . i ( ex ) ;
throw ex ;
throw ex ;
}
}
}
}
} ) ;
resolver . getClient ( ) . getDataSource ( ) . setTimeout ( timeout * 1000 ) ;
List < String > servers = getDnsServers ( context ) ;
Log . i ( "DNS servers=" + TextUtils . join ( "," , servers ) ) ;
DnsClient . addDnsServerLookupMechanism (
new AbstractDnsServerLookupMechanism ( "FairEmail" , 1 ) {
@Override
public boolean isAvailable ( ) {
return ( servers . size ( ) > 0 ) ;
}
}
} ;
resolver . setTimeout ( timeout ) ;
Lookup lookup = new Lookup ( name , rtype ) ;
lookup . setResolver ( resolver ) ;
Log . i ( "Lookup name=" + name + " @" + resolver . getAddress ( ) + " type=" + rtype ) ;
Record [ ] records = lookup . run ( ) ;
if ( lookup . getResult ( ) = = Lookup . HOST_NOT_FOUND | |
@Override
lookup . getResult ( ) = = Lookup . TYPE_NOT_FOUND )
public List < String > getDnsServerAddresses ( ) {
throw new UnknownHostException ( name ) ;
return servers ;
else if ( lookup . getResult ( ) ! = Lookup . SUCCESSFUL )
}
Log . i ( "DNS error=" + lookup . getErrorString ( ) ) ;
} ) ;
ResolverResult < ? extends Data > r = resolver . resolve ( name , clazz ) ;
if ( ! r . wasSuccessful ( ) ) {
DnsMessage . RESPONSE_CODE responseCode = r . getResponseCode ( ) ;
throw new IOException ( responseCode . name ( ) ) ;
}
List < DnsRecord > result = new ArrayList < > ( ) ;
List < DnsRecord > result = new ArrayList < > ( ) ;
if ( records ! = null )
Set < ? extends Data > answers = r . getAnswers ( ) ;
for ( Record record : records ) {
if ( answers ! = null )
Log . i ( "Found record=" + record ) ;
for ( Data answer : answers ) {
if ( record instanceof NSRecord ) {
Log . i ( "Answer=" + answer ) ;
NSRecord ns = ( NSRecord ) record ;
if ( answer instanceof NS ) {
result . add ( new DnsRecord ( ns . getTarget ( ) . toString ( true ) ) ) ;
NS ns = ( NS ) answer ;
} else if ( record instanceof MXRecord ) {
result . add ( new DnsRecord ( ns . getTarget ( ) . toString ( ) ) ) ;
MXRecord mx = ( MXRecord ) record ;
} else if ( answer instanceof MX ) {
result . add ( new DnsRecord ( mx . getTarget ( ) . toString ( true ) ) ) ;
MX mx = ( MX ) answer ;
} else if ( record instanceof SOARecord ) {
result . add ( new DnsRecord ( mx . target . toString ( ) ) ) ;
SOARecord soa = ( SOARecord ) record ;
} else if ( answer instanceof SRV ) {
result . add ( new DnsRecord ( soa . getHost ( ) . toString ( true ) ) ) ;
SRV srv = ( SRV ) answer ;
} else if ( record instanceof SRVRecord ) {
result . add ( new DnsRecord ( srv . target . toString ( ) , srv . port , srv . priority , srv . weight ) ) ;
SRVRecord srv = ( SRVRecord ) record ;
} else if ( answer instanceof TXT ) {
result . add ( new DnsRecord ( srv . getTarget ( ) . toString ( true ) , srv . getPort ( ) , srv . getPriority ( ) , srv . getWeight ( ) ) ) ;
} else if ( record instanceof TXTRecord ) {
StringBuilder sb = new StringBuilder ( ) ;
StringBuilder sb = new StringBuilder ( ) ;
TXTRecord txt = ( TXTRecord ) record ;
TXT txt = ( TXT ) answer ;
for ( Object content : txt . getStrings ( ) ) {
for ( String text : txt . getCharacterStrings ( ) ) {
String text = content . toString ( ) ;
if ( filter ! = null & &
if ( filter ! = null & &
( TextUtils . isEmpty ( text ) | | ! text . toLowerCase ( Locale . ROOT ) . startsWith ( filter ) ) )
( TextUtils . isEmpty ( text ) | | ! text . toLowerCase ( Locale . ROOT ) . startsWith ( filter ) ) )
continue ;
continue ;
@ -240,32 +250,38 @@ public class DnsHelper {
sb . append ( text ) ;
sb . append ( text ) ;
}
}
result . add ( new DnsRecord ( sb . toString ( ) , 0 ) ) ;
result . add ( new DnsRecord ( sb . toString ( ) , 0 ) ) ;
} else if ( record instanceof ARecord ) {
} else if ( answer instanceof A ) {
A Record a = ( ARecord ) record ;
A a = ( A ) answer ;
result . add ( new DnsRecord ( a . get Address( ) . getHostAddress ( ) ) ) ;
result . add ( new DnsRecord ( a . get Inet Address( ) . getHostAddress ( ) ) ) ;
} else if ( record instanceof AAAARecord ) {
} else if ( answer instanceof AAAA ) {
AAAA Record aaaa = ( AAAARecord ) record ;
AAAA aaaa = ( AAAA ) answer ;
result . add ( new DnsRecord ( aaaa . get Address( ) . getHostAddress ( ) ) ) ;
result . add ( new DnsRecord ( aaaa . get Inet Address( ) . getHostAddress ( ) ) ) ;
} else
} else
throw new IllegalArgumentException ( record . getClass ( ) . getName ( ) ) ;
throw new IllegalArgumentException ( answer . getClass ( ) . getName ( ) ) ;
}
}
for ( DnsRecord record : result )
for ( DnsRecord record : result ) {
record . query = name ;
record . query = name ;
record . secure = r . isAuthenticData ( ) ;
}
return result . toArray ( new DnsRecord [ 0 ] ) ;
return result . toArray ( new DnsRecord [ 0 ] ) ;
} catch ( Throwable ex ) {
} catch ( Throwable ex ) {
// TextParseException
if ( ex instanceof DnssecValidationFailedException )
// Lookup static ctor: RuntimeException("Failed to initialize resolver")
Log . i ( ex ) ;
else
Log . e ( ex ) ;
Log . e ( ex ) ;
return new DnsRecord [ 0 ] ;
return new DnsRecord [ 0 ] ;
}
}
}
}
private static String getDnsServer ( Context context ) {
private static List < String > getDnsServers ( Context context ) {
List < String > result = new ArrayList < > ( ) ;
result . add ( DEFAULT_DNS ) ;
ConnectivityManager cm = Helper . getSystemService ( context , ConnectivityManager . class ) ;
ConnectivityManager cm = Helper . getSystemService ( context , ConnectivityManager . class ) ;
if ( cm = = null )
if ( cm = = null )
return DEFAULT_DNS ;
return result ;
LinkProperties props = null ;
LinkProperties props = null ;
@ -281,19 +297,38 @@ public class DnsHelper {
else {
else {
Network active = cm . getActiveNetwork ( ) ;
Network active = cm . getActiveNetwork ( ) ;
if ( active = = null )
if ( active = = null )
return DEFAULT_DNS ;
return result ;
props = cm . getLinkProperties ( active ) ;
props = cm . getLinkProperties ( active ) ;
Log . i ( "New props=" + props ) ;
Log . i ( "New props=" + props ) ;
}
}
if ( props = = null )
if ( props = = null )
return DEFAULT_DNS ;
return result ;
List < InetAddress > dns = props . getDnsServers ( ) ;
List < InetAddress > dns = props . getDnsServers ( ) ;
if ( dns . size ( ) = = 0 )
for ( int i = 0 ; i < dns . size ( ) ; i + + )
return DEFAULT_DNS ;
result . add ( i , dns . get ( i ) . getHostAddress ( ) ) ;
else
return dns . get ( 0 ) . getHostAddress ( ) ;
return result ;
}
static void test ( Context context ) throws UnknownHostException {
log ( lookup ( context , "gmail.com" , "ns" ) ) ;
log ( lookup ( context , "gmail.com" , "mx" ) ) ;
log ( lookup ( context , "_imaps._tcp.gmail.com" , "srv" ) ) ;
log ( lookup ( context , "gmail.com" , "txt" ) ) ;
log ( lookup ( context , "gmail.com" , "a" ) ) ;
log ( lookup ( context , "gmail.com" , "aaaa" ) ) ;
log ( lookup ( context , "posteo.de" , "a" ) ) ;
log ( lookup ( context , "non.existent.tld" , "a" ) ) ;
log ( lookup ( context , "rubbish" , "a" ) ) ;
}
static void log ( DnsRecord [ ] records ) {
if ( records . length = = 0 )
Log . w ( "No records" ) ;
for ( DnsRecord record : records )
Log . w ( "DNS " + record ) ;
}
}
static class DnsRecord {
static class DnsRecord {
@ -302,6 +337,7 @@ public class DnsHelper {
Integer port ;
Integer port ;
Integer priority ;
Integer priority ;
Integer weight ;
Integer weight ;
Boolean secure ;
DnsRecord ( String response ) {
DnsRecord ( String response ) {
this . response = response ;
this . response = response ;
@ -322,7 +358,7 @@ public class DnsHelper {
@NonNull
@NonNull
@Override
@Override
public String toString ( ) {
public String toString ( ) {
return query + "=" + response + ":" + port + " " + priority + "/" + weight ;
return query + "=" + response + ":" + port + " " + priority + "/" + weight + " secure=" + secure ;
}
}
}
}
}
}