DNSolve is a Dart library that provides an easy way to perform DNS lookups using native resolution via FFI. It supports both forward and reverse DNS lookups using the raw DNS protocol (UDP/TCP on port 53) through a compiled Rust library built on hickory-dns.
Unlike traditional DNS-over-HTTPS libraries, DNSolve resolves DNS queries natively using the system's DNS resolver or any custom DNS server. No HTTP requests to Google or Cloudflare -- just direct DNS protocol communication.
- Forward DNS lookups (A, AAAA, MX, SRV, TXT, and many more record types)
- Reverse DNS lookups (PTR records)
- Native DNS resolution via Rust FFI (no HTTP overhead)
- System resolver support (uses OS-configured DNS by default)
- Custom DNS servers (Google, Cloudflare, Quad9, or any IP)
- DNSSEC support
- Configurable timeouts
- Comprehensive error handling
- IPv6 support (forward and reverse lookups)
- Enhanced record parsing (MX, CAA, SOA, TXT records)
- Batch lookups (parallel queries)
- Response caching (TTL-based)
- Retry mechanism (with exponential backoff)
- Query statistics (success rate, average response time)
- Builder pattern (fluent configuration API)
DNSolve requires a compiled native library. You need Rust installed to build it.
# From the project root
make build
# Or directly
cd native && cargo build --releaseThis produces:
- macOS:
native/target/release/libdnsolve_native.dylib - Linux:
native/target/release/libdnsolve_native.so - Windows:
native/target/release/dnsolve_native.dll
Ensure the compiled library is on your library search path:
# macOS
export DYLD_LIBRARY_PATH="/path/to/dnsolve/native/target/release:$DYLD_LIBRARY_PATH"
# Linux
export LD_LIBRARY_PATH="/path/to/dnsolve/native/target/release:$LD_LIBRARY_PATH"Or copy the library next to your Dart executable.
Add the following dependency to your pubspec.yaml file:
dependencies:
dnsolve: ^3.0.0import 'package:dnsolve/dnsolve.dart';
Future<void> main() async {
final dnsolve = DNSolve(); // Uses system DNS by default
try {
final response = await dnsolve.lookup(
'example.com',
type: RecordType.A,
);
if (response.answer?.records != null) {
for (final record in response.answer!.records!) {
print('${record.name}: ${record.data}');
}
}
} finally {
dnsolve.dispose(); // Always dispose when done
}
}import 'package:dnsolve/dnsolve.dart';
Future<void> main() async {
// Use Cloudflare DNS
final dnsolve = DNSolve(server: DNSServer.cloudflare);
// Or Google DNS
final dnsolve2 = DNSolve(server: DNSServer.google);
// Or any custom server
final dnsolve3 = DNSolve(server: DNSServer.custom('9.9.9.9'));
// Or override per-query
final dnsolve4 = DNSolve();
try {
final response = await dnsolve4.lookup(
'example.com',
server: DNSServer.custom('208.67.222.222'), // OpenDNS
);
print('Status: ${response.status}');
} finally {
dnsolve4.dispose();
}
}import 'dart:developer';
import 'package:dnsolve/dnsolve.dart';
Future<void> main() async {
final dnsolve = DNSolve();
try {
final response = await dnsolve.lookup(
'_xmpp._tcp.vsevex.me',
dnsSec: true,
type: RecordType.srv,
);
if (response.answer?.records != null) {
for (final record in response.answer!.records!) {
log(record.toBind);
}
}
// Access parsed SRV records
if (response.answer?.srvs != null) {
for (final srv in response.answer!.srvs!) {
print('Priority: ${srv.priority}, Port: ${srv.port}, Target: ${srv.target}');
}
}
} finally {
dnsolve.dispose();
}
}import 'package:dnsolve/dnsolve.dart';
Future<void> main() async {
final dnsolve = DNSolve();
try {
// IPv4 reverse lookup
final records = await dnsolve.reverseLookup('8.8.8.8');
for (final record in records) {
print('PTR: ${record.data}');
}
// IPv6 reverse lookup
final ipv6Records = await dnsolve.reverseLookup('2001:4860:4860::8888');
for (final record in ipv6Records) {
print('PTR: ${record.data}');
}
} finally {
dnsolve.dispose();
}
}import 'package:dnsolve/dnsolve.dart';
Future<void> main() async {
final dnsolve = DNSolve.builder()
.withServer(DNSServer.cloudflare)
.withCache(enable: true, maxSize: 200)
.withStatistics(enable: true)
.withRetries(3)
.withRetryDelay(Duration(milliseconds: 500))
.build();
try {
final response = await dnsolve.lookup('example.com');
print('Status: ${response.status}');
print('Stats: ${dnsolve.statistics}');
} finally {
dnsolve.dispose();
}
}import 'package:dnsolve/dnsolve.dart';
Future<void> main() async {
final dnsolve = DNSolve();
try {
final response = await dnsolve.lookup(
'example.com',
timeout: Duration(seconds: 5),
);
} on TimeoutException catch (e) {
print('Query timed out: ${e.message}');
} on DNSLookupException catch (e) {
print('DNS lookup failed: ${e.message} (Status: ${e.statusCode})');
} on InvalidDomainException catch (e) {
print('Invalid domain: ${e.message}');
} on NativeException catch (e) {
print('Native resolver error: ${e.message}');
} finally {
dnsolve.dispose();
}
}DNSolve({
DNSServer server = DNSServer.system,
bool enableCache = false,
int cacheMaxSize = 100,
bool enableStatistics = false,
int maxRetries = 0,
Duration? retryDelay,
})Creates a new DNSolve instance. Defaults to using the system DNS resolver.
Performs a forward DNS lookup.
Future<ResolveResponse> lookup(
String domain, {
bool dnsSec = false,
RecordType type = RecordType.A,
DNSServer? server,
Duration? timeout,
})Performs multiple DNS lookups in parallel.
Future<List<ResolveResponse>> lookupBatch(
List<String> domains, {
bool dnsSec = false,
RecordType type = RecordType.A,
DNSServer? server,
Duration? timeout,
})Parameters:
domain: The domain name to lookup (required)dnsSec: Whether to enable DNSSEC (default:false)type: The DNS record type (default:RecordType.A)server: DNS server override for this query (optional)timeout: Timeout duration (default: 30 seconds)
Returns: Future<ResolveResponse>
Throws:
InvalidDomainException: If domain is empty or invalidTimeoutException: If query exceeds timeoutNativeException: If native resolver encounters an errorDNSLookupException: If DNS query fails
Performs a reverse DNS lookup (PTR record).
Future<List<Record>> reverseLookup(
String ip, {
DNSServer? server,
Duration? timeout,
})Parameters:
ip: The IP address (IPv4 or IPv6) to lookup (required)server: DNS server override for this query (optional)timeout: Timeout duration (default: 30 seconds)
Returns: Future<List<Record>>
Gets query statistics if statistics are enabled.
DNSStatistics? get statisticsClears the DNS cache if caching is enabled.
void clearCache()Gets the current cache size if caching is enabled.
int? get cacheSizeDisposes of resources used by this instance. Always call this when done.
void dispose()Creates a builder for configuring a DNSolve instance.
static DNSolveBuilder builder()DNSServer.system- System default DNS resolver (no external traffic)DNSServer.google- Google Public DNS (8.8.8.8)DNSServer.cloudflare- Cloudflare DNS (1.1.1.1)DNSServer.custom('ip')- Any custom DNS server by IP address
The following DNS record types are supported:
A- IPv4 addressaaaa- IPv6 addressany- Any record typecaa- Certificate Authority Authorizationcds- Child DScert- Certificatecname- Canonical namedname- Delegation namednskey- DNS Keyds- Delegation Signerhinfo- Host informationipseckey- IPSEC keymx- Mail exchangenaptr- Name Authority Pointerns- Name servernsec- Next Securensec3Param- NSEC3 parametersptr- Pointer (for reverse lookups)rp- Responsible Personrrsig- Resource Record Signaturesoa- Start of Authorityspf- Sender Policy Frameworksrv- Service locatorsshfp- SSH Fingerprinttlsa- TLSA certificatetxt- Text recordwks- Well-known service
Contains the DNS resolution response.
Properties:
status: DNS status code (0 = success)answer:Answerobject containing DNS recordsquestions: List ofQuestionobjectstc,rd,ra,ad,cd: DNS flagscomment: Additional comments
Contains DNS records.
Properties:
records: List ofRecordobjectssrvs: List of parsedSRVRecordobjects (if SRV type)mxs: List of parsedMXRecordobjects (if MX type)caas: List of parsedCAARecordobjects (if CAA type)soas: List of parsedSOARecordobjects (if SOA type)txts: List of parsedTXTRecordobjects (if TXT type)
Represents a single DNS record.
Properties:
name: Domain namerType: Record typettl: Time to livedata: Record data
Methods:
toBind: Returns BIND format string
Represents a parsed SRV record.
Properties:
priority: Priority valueweight: Weight valueport: Port numbertarget: Target hostnamefqdn: Fully qualified domain name
Methods:
sort(): Static method to sort SRV records by priority and weight
Represents a parsed MX (Mail Exchange) record.
Properties:
priority: Priority value (lower is preferred)exchange: Mail exchange hostnamefqdn: Fully qualified domain name
Represents a parsed CAA (Certificate Authority Authorization) record.
Properties:
flags: Flags bytetag: Tag (e.g., "issue", "issuewild")value: Value associated with the tagfqdn: Fully qualified domain name
Represents a parsed SOA (Start of Authority) record.
Properties:
mname: Primary name serverrname: Administrator email (with @ replaced by .)serial: Serial numberrefresh: Refresh interval in secondsretry: Retry interval in secondsexpire: Expire time in secondsminimum: Minimum TTL in secondsfqdn: Fully qualified domain name
Represents a parsed TXT record.
Properties:
text: Text contentfqdn: Fully qualified domain name
Query statistics tracking.
Properties:
totalQueries: Total number of queriessuccessfulQueries: Number of successful queriesfailedQueries: Number of failed queriesaverageResponseTimeMs: Average response time in millisecondssuccessRate: Success rate as a percentage
Methods:
reset(): Resets all statistics
DNSolveException: Base exception classDNSLookupException: DNS query failedNativeException: Native FFI library errorTimeoutException: Query timeoutInvalidDomainException: Invalid domain or IP addressSRVRecordFormatException: SRV record parsing error
-
Resolution method: DNS-over-HTTPS has been replaced with native DNS resolution via Rust FFI. Queries are sent directly over UDP/TCP port 53 instead of HTTPS.
-
DNS providers replaced with DNS servers:
DNSProviderenum has been replaced withDNSServerclass:// Before (v2) await dnsolve.lookup('example.com', provider: DNSProvider.google); // After (v3) await dnsolve.lookup('example.com', server: DNSServer.google); // New: system resolver (default, no external traffic) await dnsolve.lookup('example.com'); // Uses DNSServer.system // New: custom DNS server await dnsolve.lookup('example.com', server: DNSServer.custom('9.9.9.9'));
-
HTTP client removed: The
clientconstructor parameter andwithClient()builder method have been removed. Useserver/withServer()instead:// Before (v2) final dnsolve = DNSolve(client: http.Client()); final dnsolve2 = DNSolve.builder().withClient(client).build(); // After (v3) final dnsolve = DNSolve(server: DNSServer.cloudflare); final dnsolve2 = DNSolve.builder().withServer(DNSServer.cloudflare).build();
-
Web support removed:
dart:ffidoes not work in web browsers. Thewebplatform has been removed from supported platforms. -
Dependencies changed: The
httppackage dependency has been replaced withffi. A compiled native library is now required. -
Exception changes:
ResponseExceptionhas been removed (no more HTTP responses).NativeExceptionhas been added for FFI-specific errors.
- Native DNS resolution (no HTTP overhead)
- System resolver support (no external traffic by default)
- Custom DNS server support (any IP address)
- Per-query server override
- macOS (x86_64 and arm64)
- Linux (x86_64 and arm64)
- Windows (x86_64)
- Android (arm64, armeabi-v7a, x86_64)
- iOS (arm64)
Note: Web is not supported due to
dart:ffilimitations.
Contributions are welcome! If you have any improvements, bug fixes, or new features to contribute, please create a pull request.
This project is licensed under the MIT License - see the LICENSE file for details.