refactor: update DoH resolution method to use hostname first; improve circuit breaker logic

This commit is contained in:
Moustapha Kodjo Amadou 2026-02-25 11:52:18 +01:00
parent cc69c9f275
commit 9d829a16e7
2 changed files with 31 additions and 66 deletions

View file

@ -105,8 +105,7 @@ class DoHResolver {
}
}
/// Resolve using specific provider via bootstrap IP
/// This avoids circular DNS dependency by connecting to bootstrap IP directly
/// Resolve using specific provider via hostname
static Future<List<String>> _resolveFromProvider(
String host,
DoHProvider provider, {
@ -119,58 +118,34 @@ class DoHResolver {
return [];
}
final uri = Uri.parse(provider.url);
final providerHost = uri.host;
// Validate bootstrap IPs format
final validBootstrapIps = provider.bootstrapIPs
.where((ip) => _isValidIp(ip))
.toList();
if (validBootstrapIps.isEmpty) {
_log('No valid bootstrap IPs for ${provider.name}', isError: true);
return [];
}
// Try each bootstrap IP until one succeeds
for (int i = 0; i < validBootstrapIps.length; i++) {
final bootstrapIp = validBootstrapIps[i];
try {
final result = await _queryDoHViaBootstrapIp(
host,
provider,
bootstrapIp,
providerHost,
recordType,
);
if (result.isNotEmpty) {
_log(
'Resolved $host via ${provider.name} (bootstrap IP $i+1/${validBootstrapIps.length})',
);
return result;
}
} catch (e) {
_log('Bootstrap IP $i failed for ${provider.name}: $e');
continue;
try {
final directResult = await _queryDoHViaHostname(
host,
provider,
recordType,
);
if (directResult.isNotEmpty) {
_log('Resolved $host via ${provider.name} (hostname mode)');
stats.successCount++;
return directResult;
}
} catch (e) {
_log('Hostname mode failed for ${provider.name}: $e');
stats.failureCount++;
stats.lastFailure = DateTime.now();
if (stats.failureCount >= 3) {
stats.isCircuitOpen = true;
_log('Circuit breaker opened for ${provider.name}', isError: true);
}
}
stats.failureCount++;
stats.lastFailure = DateTime.now();
// Open circuit after 3 consecutive failures
if (stats.failureCount >= 3) {
stats.isCircuitOpen = true;
_log('Circuit breaker opened for ${provider.name}', isError: true);
}
return [];
}
/// Query DoH via specific bootstrap IP (direct IP connection, no DNS lookup)
static Future<List<String>> _queryDoHViaBootstrapIp(
/// Query DoH by provider hostname (relies on platform DNS for provider host)
static Future<List<String>> _queryDoHViaHostname(
String targetHost,
DoHProvider provider,
String bootstrapIp,
String providerHost,
String recordType,
) async {
final uri = Uri.parse(provider.url);
@ -178,19 +153,16 @@ class DoHResolver {
client.connectionTimeout = _requestTimeout;
try {
// Create request with bootstrap IP instead of hostname
final request = await client.getUrl(
Uri(
scheme: uri.scheme,
host: bootstrapIp, // Use bootstrap IP directly
host: uri.host,
port: uri.port,
path: uri.path,
query: 'name=$targetHost&type=$recordType',
),
);
// Set Host header to the actual provider hostname (required for HTTPS SNI)
request.headers.set('Host', providerHost);
request.headers.set('Accept', 'application/dns-json');
request.headers.set('User-Agent', 'Mangayomi/1.0');
@ -201,18 +173,10 @@ class DoHResolver {
return _parseDoHResponse(body);
}
_log('HTTP ${response.statusCode} from $bootstrapIp (${provider.name})');
_log('HTTP ${response.statusCode} from hostname (${provider.name})');
return [];
} on SocketException {
// Connection failed with this bootstrap IP
_log('Socket error on $bootstrapIp');
rethrow;
} on TimeoutException {
// Timeout with this bootstrap IP
_log('Timeout on $bootstrapIp');
rethrow;
} finally {
client.close();
client.close(force: true);
}
}
@ -272,6 +236,7 @@ class DoHResolver {
return null;
})
.whereType<String>()
.where(_isValidIp)
.toList();
if (ips.isNotEmpty) {

View file

@ -1699,26 +1699,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae"
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
url: "https://pub.dev"
source: hosted
version: "1.28.0"
version: "1.29.0"
test_api:
dependency: transitive
description:
name: test_api
sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8"
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.8"
version: "0.7.9"
test_core:
dependency: transitive
description:
name: test_core
sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
url: "https://pub.dev"
source: hosted
version: "0.6.14"
version: "0.6.15"
time:
dependency: transitive
description: