DNS error handling

This commit is contained in:
M66B 2024-01-06 20:18:11 +01:00
parent 5c4d5b6670
commit 9311953dd3
1 changed files with 143 additions and 137 deletions

View File

@ -101,7 +101,7 @@ public class DnsHelper {
String domain = UriHelper.getEmailDomain(email); String domain = UriHelper.getEmailDomain(email);
if (domain == null) if (domain == null)
continue; continue;
DnsRecord[] records = _lookup(context, domain, "mx", CHECK_TIMEOUT, false); DnsRecord[] records = _lookup(context, domain, "mx", CHECK_TIMEOUT);
if (records.length == 0) if (records.length == 0)
throw new UnknownHostException(domain); throw new UnknownHostException(domain);
} }
@ -109,11 +109,25 @@ public class DnsHelper {
@NonNull @NonNull
static DnsRecord[] lookup(Context context, String name, String type) { static DnsRecord[] lookup(Context context, String name, String type) {
return _lookup(context, name, type, LOOKUP_TIMEOUT, false); return _lookup(context, name, type, LOOKUP_TIMEOUT);
} }
@NonNull @NonNull
private static DnsRecord[] _lookup(Context context, String name, String type, int timeout, boolean dnssec) { private static DnsRecord[] _lookup(Context context, String name, String type, int timeout) {
try {
return _lookup(context, name, type, timeout, false);
} catch (Throwable ex) {
if (ex instanceof MultipleIoException ||
ex instanceof ResolutionUnsuccessfulException)
Log.i(ex);
else
Log.e(ex);
return new DnsRecord[0];
}
}
@NonNull
private static DnsRecord[] _lookup(Context context, String name, String type, int timeout, boolean dnssec) throws IOException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean dns_custom = prefs.getBoolean("dns_custom", false); boolean dns_custom = prefs.getBoolean("dns_custom", false);
@ -148,141 +162,130 @@ public class DnsHelper {
throw new IllegalArgumentException(type); throw new IllegalArgumentException(type);
} }
try { ResolverApi resolver = DnssecResolverApi.INSTANCE;
ResolverApi resolver = DnssecResolverApi.INSTANCE; AbstractDnsClient client = resolver.getClient();
AbstractDnsClient client = resolver.getClient();
if (false) { if (false) {
String private_dns = ConnectionHelper.getPrivateDnsServerName(context); String private_dns = ConnectionHelper.getPrivateDnsServerName(context);
Log.w("DNS private=" + private_dns); Log.w("DNS private=" + private_dns);
if (private_dns != null) if (private_dns != null)
client.setDataSource(new DoTDataSource(private_dns)); client.setDataSource(new DoTDataSource(private_dns));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !dns_custom) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !dns_custom)
client.setDataSource(new AndroidDataSource()); client.setDataSource(new AndroidDataSource());
client.getDataSource().setTimeout(timeout * 1000); client.getDataSource().setTimeout(timeout * 1000);
List<String> servers = getDnsServers(context); List<String> servers = getDnsServers(context);
Log.i("DNS servers=" + TextUtils.join(",", servers)); Log.i("DNS servers=" + TextUtils.join(",", servers));
DnsClient.addDnsServerLookupMechanism( DnsClient.addDnsServerLookupMechanism(
new AbstractDnsServerLookupMechanism("FairEmail", 1) { new AbstractDnsServerLookupMechanism("FairEmail", 1) {
@Override @Override
public boolean isAvailable() { public boolean isAvailable() {
return (servers.size() > 0); return (servers.size() > 0);
}
@Override
public List<String> getDnsServerAddresses() {
return servers;
}
});
// https://github.com/MiniDNS/minidns/issues/102
if (client instanceof DnssecClient && dns_custom)
((DnssecClient) client).setUseHardcodedDnsServers(false);
Log.i("DNS query name=" + type + ":" + name);
ResolverResult<? extends Data> data = resolver.resolve(name, clazz);
data.throwIfErrorResponse();
boolean secure = (data.getUnverifiedReasons() != null);
if (secure && dnssec) {
DnssecResultNotAuthenticException ex = data.getDnssecResultNotAuthenticException();
if (ex != null)
throw ex;
}
List<DnsRecord> result = new ArrayList<>();
DnsMessage raw = data.getRawAnswer();
List<Record<? extends Data>> answers = (raw == null ? null : raw.answerSection);
Log.i("DNS answers=" + (answers == null ? "n/a" : answers.size()));
if (answers != null) {
Record.TYPE expectedType = data.getQuestion().type;
for (Record<? extends Data> record : answers) {
if (record.type != expectedType) {
Log.i("DNS skip=" + record);
continue;
} }
Data answer = record.getPayload();
Log.i("DNS record=" + record + " answer=" + answer);
if (answer instanceof NS) {
NS ns = (NS) answer;
result.add(new DnsRecord(ns.getTarget().toString()));
} else if (answer instanceof MX) {
MX mx = (MX) answer;
result.add(new DnsRecord(mx.target.toString(), 0, mx.priority, 0));
} else if (answer instanceof SRV) {
SRV srv = (SRV) answer;
result.add(new DnsRecord(srv.target.toString(), srv.port, srv.priority, srv.weight));
} else if (answer instanceof TXT) {
StringBuilder sb = new StringBuilder();
TXT txt = (TXT) answer;
for (String text : txt.getCharacterStrings()) {
if (filter != null &&
(TextUtils.isEmpty(text) || !text.toLowerCase(Locale.ROOT).startsWith(filter)))
continue;
int i = 0;
int slash = text.indexOf('\\', i);
while (slash >= 0 && slash + 4 < text.length()) {
String digits = text.substring(slash + 1, slash + 4);
if (TextUtils.isDigitsOnly(digits)) {
int k = Integer.parseInt(digits);
text = text.substring(0, slash) + (char) k + text.substring(slash + 4);
} else
i += 4;
slash = text.indexOf('\\', i);
}
sb.append(text);
}
result.add(new DnsRecord(sb.toString(), 0));
} else if (answer instanceof A) {
A a = (A) answer;
result.add(new DnsRecord(a.getInetAddress()));
} else if (answer instanceof AAAA) {
AAAA aaaa = (AAAA) answer;
result.add(new DnsRecord(aaaa.getInetAddress()));
} else
throw new IllegalArgumentException(answer.getClass().getName());
}
}
for (DnsRecord record : result) {
record.query = name;
record.secure = secure;
record.authentic = data.isAuthenticData();
}
if ("mx".equals(type) || "srv".equals(type))
Collections.sort(result, new Comparator<DnsRecord>() {
@Override @Override
public int compare(DnsRecord d1, DnsRecord d2) { public List<String> getDnsServerAddresses() {
int o = Integer.compare( return servers;
d1.priority == null ? 0 : d1.priority,
d2.priority == null ? 0 : d2.priority);
if (o == 0)
o = Integer.compare(
d1.weight == null ? 0 : d1.weight,
d2.weight == null ? 0 : d2.weight);
return o;
} }
}); });
return result.toArray(new DnsRecord[0]); // https://github.com/MiniDNS/minidns/issues/102
} catch (Throwable ex) { if (client instanceof DnssecClient && dns_custom)
if (ex instanceof MultipleIoException || ((DnssecClient) client).setUseHardcodedDnsServers(false);
ex instanceof ResolutionUnsuccessfulException ||
ex instanceof DnssecValidationFailedException || Log.i("DNS query name=" + type + ":" + name);
ex instanceof DnssecResultNotAuthenticException) ResolverResult<? extends Data> data = resolver.resolve(name, clazz);
Log.i(ex); data.throwIfErrorResponse();
else
Log.e(ex); boolean secure = (data.getUnverifiedReasons() != null);
return new DnsRecord[0]; if (secure && dnssec) {
DnssecResultNotAuthenticException ex = data.getDnssecResultNotAuthenticException();
if (ex != null)
throw ex;
} }
List<DnsRecord> result = new ArrayList<>();
DnsMessage raw = data.getRawAnswer();
List<Record<? extends Data>> answers = (raw == null ? null : raw.answerSection);
Log.i("DNS answers=" + (answers == null ? "n/a" : answers.size()));
if (answers != null) {
Record.TYPE expectedType = data.getQuestion().type;
for (Record<? extends Data> record : answers) {
if (record.type != expectedType) {
Log.i("DNS skip=" + record);
continue;
}
Data answer = record.getPayload();
Log.i("DNS record=" + record + " answer=" + answer);
if (answer instanceof NS) {
NS ns = (NS) answer;
result.add(new DnsRecord(ns.getTarget().toString()));
} else if (answer instanceof MX) {
MX mx = (MX) answer;
result.add(new DnsRecord(mx.target.toString(), 0, mx.priority, 0));
} else if (answer instanceof SRV) {
SRV srv = (SRV) answer;
result.add(new DnsRecord(srv.target.toString(), srv.port, srv.priority, srv.weight));
} else if (answer instanceof TXT) {
StringBuilder sb = new StringBuilder();
TXT txt = (TXT) answer;
for (String text : txt.getCharacterStrings()) {
if (filter != null &&
(TextUtils.isEmpty(text) || !text.toLowerCase(Locale.ROOT).startsWith(filter)))
continue;
int i = 0;
int slash = text.indexOf('\\', i);
while (slash >= 0 && slash + 4 < text.length()) {
String digits = text.substring(slash + 1, slash + 4);
if (TextUtils.isDigitsOnly(digits)) {
int k = Integer.parseInt(digits);
text = text.substring(0, slash) + (char) k + text.substring(slash + 4);
} else
i += 4;
slash = text.indexOf('\\', i);
}
sb.append(text);
}
result.add(new DnsRecord(sb.toString(), 0));
} else if (answer instanceof A) {
A a = (A) answer;
result.add(new DnsRecord(a.getInetAddress()));
} else if (answer instanceof AAAA) {
AAAA aaaa = (AAAA) answer;
result.add(new DnsRecord(aaaa.getInetAddress()));
} else
throw new IllegalArgumentException(answer.getClass().getName());
}
}
for (DnsRecord record : result) {
record.query = name;
record.secure = secure;
record.authentic = data.isAuthenticData();
}
if ("mx".equals(type) || "srv".equals(type))
Collections.sort(result, new Comparator<DnsRecord>() {
@Override
public int compare(DnsRecord d1, DnsRecord d2) {
int o = Integer.compare(
d1.priority == null ? 0 : d1.priority,
d2.priority == null ? 0 : d2.priority);
if (o == 0)
o = Integer.compare(
d1.weight == null ? 0 : d1.weight,
d2.weight == null ? 0 : d2.weight);
return o;
}
});
return result.toArray(new DnsRecord[0]);
} }
static InetAddress getByName(Context context, String host) throws UnknownHostException { static InetAddress getByName(Context context, String host) throws UnknownHostException {
@ -316,19 +319,22 @@ public class DnsHelper {
List<InetAddress> result = new ArrayList<>(); List<InetAddress> result = new ArrayList<>();
boolean[] has46 = ConnectionHelper.has46(context); boolean[] has46 = ConnectionHelper.has46(context);
try {
if (has46[0])
for (DnsRecord a : _lookup(context, host, "a", LOOKUP_TIMEOUT, dnssec))
result.add(a.address);
if (has46[0]) if (has46[1])
for (DnsRecord a : _lookup(context, host, "a", LOOKUP_TIMEOUT, dnssec)) for (DnsRecord aaaa : _lookup(context, host, "aaaa", LOOKUP_TIMEOUT, dnssec))
result.add(a.address); result.add(aaaa.address);
if (has46[1]) if (result.size() == 0)
for (DnsRecord aaaa : _lookup(context, host, "aaaa", LOOKUP_TIMEOUT, dnssec)) throw new UnknownHostException(host);
result.add(aaaa.address);
if (result.size() == 0) return result.toArray(new InetAddress[0]);
throw new UnknownHostException(host); } catch (IOException ex) {
throw new UnknownHostException(ex.getMessage());
return result.toArray(new InetAddress[0]); }
} }
static void verifyDane(X509Certificate[] chain, String server, int port) throws CertificateException { static void verifyDane(X509Certificate[] chain, String server, int port) throws CertificateException {