mirror of
https://github.com/edu4rdshl/rusolver.git
synced 2026-07-18 07:34:48 +00:00
Compare commits
33 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87f9336cc2 | |||
| 12318ce9c0 | |||
| c372db9eab | |||
| 9b329daa6a | |||
| 206c392fc0 | |||
| 28b29ca515 | |||
| 3bb0ac4b35 | |||
| 55fd2cb2be | |||
| 37020360c2 | |||
| e06eeffb48 | |||
| 7e683bd1ae | |||
|
|
cf75cafee7 | ||
|
|
7755533e1e | ||
|
|
88ee0f9b58 | ||
|
|
624a3febb9 | ||
|
|
06ea060de9 | ||
|
|
42dbf01d76 | ||
|
|
8620eded16 | ||
|
|
e26ae062d6 | ||
|
|
ba828758d9 | ||
|
|
d60cc8b150 | ||
|
|
17776e7b88 | ||
|
|
7f974c8414 | ||
|
|
e7b2bc2050 | ||
|
|
dfdc0ee0be | ||
|
|
ccd7bfc646 | ||
|
|
6a63f85199 | ||
|
|
ddd055757b | ||
|
|
50b0e6bcdc | ||
|
|
5d0443a678 | ||
|
|
41e80a944b | ||
|
|
986dfbe37f | ||
|
|
14f3bfbccc |
14 changed files with 2132 additions and 893 deletions
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
|
|
@ -1,3 +1 @@
|
|||
patreon: edu4rdshl
|
||||
open_collective: findomain
|
||||
custom: ["https://paypal.me/sechacklabs"]
|
||||
github: [edu4rdshl]
|
||||
|
|
|
|||
128
.github/workflows/make-release.yml
vendored
Normal file
128
.github/workflows/make-release.yml
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
name: Create and upload artifacts
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-nix:
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
type: [ubuntu-x64, ubuntu-x86, armv7, aarch64]
|
||||
include:
|
||||
- type: ubuntu-x64
|
||||
os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
name: rusolver-linux
|
||||
path: target/x86_64-unknown-linux-musl/release/rusolver
|
||||
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
- type: ubuntu-x86
|
||||
os: ubuntu-latest
|
||||
target: i686-unknown-linux-musl
|
||||
name: rusolver-linux-i386
|
||||
path: target/i686-unknown-linux-musl/release/rusolver
|
||||
pkg_config_path: /usr/lib/i686-linux-gnu/pkgconfig
|
||||
- type: armv7
|
||||
os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
name: rusolver-armv7
|
||||
path: target/armv7-unknown-linux-gnueabihf/release/rusolver
|
||||
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
- type: aarch64
|
||||
os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
name: rusolver-aarch64
|
||||
path: target/aarch64-unknown-linux-gnu/release/rusolver
|
||||
pkg_config_path: /usr/lib/x86_64-linux-gnu/pkgconfig
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install System Dependencies
|
||||
run: |
|
||||
env
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends libssl-dev pkg-config gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
env:
|
||||
PKG_CONFIG_PATH: ${{ matrix.pkg_config_path }}
|
||||
OPENSSL_DIR: /usr/lib/ssl
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=${{ matrix.target }}
|
||||
- name: Strip symbols from binary
|
||||
run: |
|
||||
strip -s ${{ matrix.path }} || arm-linux-gnueabihf-strip -s ${{ matrix.path }} || aarch64-linux-gnu-strip -s ${{ matrix.path }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ matrix.path }}
|
||||
|
||||
build-macos:
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-apple-darwin
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=x86_64-apple-darwin
|
||||
- name: Strip symbols from binary
|
||||
run: |
|
||||
strip -u -r target/x86_64-apple-darwin/release/rusolver
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: rusolver-osx
|
||||
path: target/x86_64-apple-darwin/release/rusolver
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: rusolver-osx
|
||||
path: rusolver-osx
|
||||
|
||||
build-windows:
|
||||
env:
|
||||
IN_PIPELINE: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
type: [windows-x64, windows-x86]
|
||||
include:
|
||||
- type: windows-x64
|
||||
os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
name: rusolver-windows.exe
|
||||
path: target\x86_64-pc-windows-msvc\release\rusolver.exe
|
||||
- type: windows-x86
|
||||
os: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
name: rusolver-windows-i686.exe
|
||||
path: target\i686-pc-windows-msvc\release\rusolver.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --release --target=${{ matrix.target }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: ${{ matrix.path }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
|||
resolvers
|
||||
.gitignore
|
||||
wordlist
|
||||
/ghbinaries
|
||||
2123
Cargo.lock
generated
2123
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
20
Cargo.toml
20
Cargo.toml
|
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
name = "rusolver"
|
||||
version = "0.6.1"
|
||||
version = "0.10.2"
|
||||
authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "Fast DNS resolver written in Rust."
|
||||
repository = "https://github.com/edu4rdshl/rusolver"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
|
@ -12,8 +12,14 @@ readme = "README.md"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.6.0", features = ["full"] }
|
||||
futures = "0.3.15"
|
||||
trust-dns-resolver = { git = "https://github.com/Edu4rdSHL/trust-dns", branch = "main" }
|
||||
clap = "2.33.3"
|
||||
rand = "0.8.3"
|
||||
tokio = { version = "1.47.0", features = ["full"] }
|
||||
futures = "0.3.31"
|
||||
hickory-resolver = "0.25.2"
|
||||
clap = { version = "4.5.41", features = ["derive"] }
|
||||
rand = "0.9.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
strip = true
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ Fast DNS resolver written in Rust. Works on Linux, Windows, macOS, Android, Aarc
|
|||
# Goal
|
||||
Offer the community an efficient host resolution tool.
|
||||
|
||||
# Performance & speed
|
||||
Rusolver is **very** resource friendly, you can use up to 1000 threads in an single core machine and this will work without any problem, the bottleneck for this tool is your network speed. By default, Rusolver is able to perform resolution for ~1532 hosts per second in good network conditions (tested in an AWS machine).
|
||||
# Performance, speed and accuracy
|
||||
Rusolver is **very** resource friendly, you can use up to 1000 threads in an single core machine and this will work without any problem, the bottleneck for this tool is your network speed. By default, Rusolver is able to perform resolution for ~1532 hosts per second in good network conditions (tested in an AWS machine). Additionally the tool does a double-check by default for resolved hosts against Cloudflare, Google, Quad9, OpenDNS, Verisign, UncensoredDNS and dns.watch DNS, with that algoritm the possibility to get an false-positive is null.
|
||||
|
||||
## Demo
|
||||
|
||||
|
|
|
|||
69
builder.sh
69
builder.sh
|
|
@ -1,28 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rusolver releaser
|
||||
NAME="rusolver"
|
||||
|
||||
LINUX_TARGET="x86_64-unknown-linux-musl"
|
||||
LINUX_X86_TARGET="i686-unknown-linux-musl"
|
||||
WIN_TARGET="x86_64-pc-windows-gnu"
|
||||
ARMV7_TARGET="armv7-unknown-linux-gnueabihf"
|
||||
AARCH_TARGET="aarch64-unknown-linux-gnu"
|
||||
OSX_TARGET="x86_64-apple-darwin"
|
||||
# OSX_TARGET="x86_64-apple-darwin"
|
||||
MANPAGE_DIR="./$NAME.1"
|
||||
BIN_OUTPUT_DIR="./ghbinaries"
|
||||
|
||||
rm -rf "$BIN_OUTPUT_DIR"
|
||||
mkdir -p "$BIN_OUTPUT_DIR"
|
||||
|
||||
# Linux build
|
||||
echo "Building Linux artifact."
|
||||
if cargo build -q --release --target="$LINUX_TARGET"; then
|
||||
if cross build -q --release --target="$LINUX_TARGET"; then
|
||||
echo "Linux artifact build: SUCCESS"
|
||||
cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux"
|
||||
strip "target/$LINUX_TARGET/release/$NAME-linux"
|
||||
sha512sum "target/$LINUX_TARGET/release/$NAME-linux" >"$BIN_OUTPUT_DIR/$NAME-linux.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-linux-x64.zip" "target/$LINUX_TARGET/release/$NAME-linux"
|
||||
else
|
||||
echo "Linux artifact build: FAILED"
|
||||
fi
|
||||
|
||||
# Linux x86 build
|
||||
echo "Building Linux x86 artifact."
|
||||
if cross build -q --release --target="$LINUX_X86_TARGET"; then
|
||||
echo "Linux x86 artifact build: SUCCESS"
|
||||
cp "target/$LINUX_X86_TARGET/release/$NAME" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||
strip "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||
sha512sum "target/$LINUX_X86_TARGET/release/$NAME-linux-i386" >"$BIN_OUTPUT_DIR/$NAME-linux-i386.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-linux-i386.zip" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||
else
|
||||
echo "Linux x86 artifact build: FAILED"
|
||||
fi
|
||||
|
||||
# Windows build
|
||||
echo "Building Windows artifact."
|
||||
if cross build -q --release --target="$WIN_TARGET"; then
|
||||
echo "Windows artifact build: SUCCESS"
|
||||
cp "target/$WIN_TARGET/release/$NAME.exe" "target/$WIN_TARGET/release/$NAME-windows.exe"
|
||||
strip "target/$WIN_TARGET/release/$NAME-windows.exe"
|
||||
sha512sum "target/$WIN_TARGET/release/$NAME-windows.exe" >"$BIN_OUTPUT_DIR/$NAME-windows.exe.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-windows.zip" "target/$WIN_TARGET/release/$NAME-windows.exe"
|
||||
else
|
||||
echo "Windows artifact build: FAILED"
|
||||
fi
|
||||
|
|
@ -32,6 +54,9 @@ echo "Building ARMv7 artifact."
|
|||
if cross build -q --release --target="$ARMV7_TARGET"; then
|
||||
echo "ARMv7 artifact build: SUCCESS"
|
||||
cp "target/$ARMV7_TARGET/release/$NAME" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||
strip "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||
sha512sum "target/$ARMV7_TARGET/release/$NAME-armv7" >"$BIN_OUTPUT_DIR/$NAME-armv7.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-armv7.zip" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||
else
|
||||
echo "ARMv7 artifact build: FAILED"
|
||||
fi
|
||||
|
|
@ -41,18 +66,24 @@ echo "Building Aarch64 artifact."
|
|||
if cross build -q --release --target="$AARCH_TARGET"; then
|
||||
echo "Aarch64 artifact build: SUCCESS"
|
||||
cp "target/$AARCH_TARGET/release/$NAME" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||
strip "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||
sha512sum "target/$AARCH_TARGET/release/$NAME-aarch64" >"$BIN_OUTPUT_DIR/$NAME-aarch64.sha512"
|
||||
zip -q -j "$BIN_OUTPUT_DIR/$NAME-aarch64.zip" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||
else
|
||||
echo "Aarch64 artifact build: FAILED"
|
||||
fi
|
||||
|
||||
# Mac OS build
|
||||
echo "Building OSX artifact."
|
||||
if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
|
||||
echo "OSX artifact build: SUCCESS"
|
||||
cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
|
||||
else
|
||||
echo "OSX artifact build: FAILED"
|
||||
fi
|
||||
# # Mac OS build
|
||||
# echo "Building OSX artifact."
|
||||
# if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
|
||||
# echo "OSX artifact build: SUCCESS"
|
||||
# cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
|
||||
# strip "target/$OSX_TARGET/release/$NAME-osx"
|
||||
# sha512sum "target/$OSX_TARGET/release/$NAME-osx" >"target/$OSX_TARGET/release/$NAME-osx.sha512"
|
||||
# zip -q "$BIN_OUTPUT_DIR/$NAME-osx.zip" "target/$OSX_TARGET/release/$NAME-osx"
|
||||
# else
|
||||
# echo "OSX artifact build: FAILED"
|
||||
# fi
|
||||
|
||||
echo "Creating manpage..."
|
||||
if command -v help2man >/dev/null; then
|
||||
|
|
@ -64,19 +95,3 @@ if command -v help2man >/dev/null; then
|
|||
else
|
||||
echo "Please install the help2man package."
|
||||
fi
|
||||
|
||||
if command -v git >/dev/null; then
|
||||
git add .
|
||||
git commit -m "Bump version."
|
||||
git push
|
||||
fi
|
||||
|
||||
#echo "Uploading crate to crates.io..."
|
||||
#if cargo publish --no-verify > /dev/null; then
|
||||
# echo "Crate uploaded."
|
||||
#else
|
||||
# echo "An error has occurred while uploading the crate to crates.io."
|
||||
# exit
|
||||
#fi
|
||||
|
||||
echo "All builds have passed!"
|
||||
|
|
|
|||
68
rusolver.1
68
rusolver.1
|
|
@ -1,56 +1,52 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.3.
|
||||
.TH RUSOLVER "1" "May 2021" "Rusolver 0.6.1" "User Commands"
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH RUSOLVER "1" "August 2025" "rusolver 0.10.2" "User Commands"
|
||||
.SH NAME
|
||||
Rusolver \- manual page for Rusolver 0.6.1
|
||||
rusolver \- manual page for rusolver 0.10.2
|
||||
.SH SYNOPSIS
|
||||
.B rusolver
|
||||
[\fI\,OPTIONS\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Rusolver 0.6.1
|
||||
Eduard Tolosa <edu4rdshl@protonmail.com>
|
||||
Fast DNS resolver written in Rust.
|
||||
.SS "USAGE:"
|
||||
.IP
|
||||
rusolver [FLAGS] [OPTIONS]
|
||||
.SS "FLAGS:"
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Prints help information
|
||||
\fB\-t\fR, \fB\-\-threads\fR <THREADS>
|
||||
Number of threads. Default: 100 [default: 100]
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-ip\fR
|
||||
Show the discovered IP addresses. Default: false
|
||||
\fB\-\-retries\fR <RETRIES>
|
||||
Number of retries after lookup failure before giving up. Defaults to 0 [default: 0]
|
||||
.TP
|
||||
\fB\-\-no\-verify\fR
|
||||
Disables the double verification algorithm for valid subdomains \fB\-NOT\fR RECOMMENDED\-. Default: false
|
||||
\fB\-d\fR, \fB\-\-domain\fR <DOMAIN>
|
||||
Target domain. When it's specified, a wordlist can be used from stdin for bruteforcing.
|
||||
.TP
|
||||
\fB\-q\fR, \fB\-\-quiet\fR
|
||||
Enables quiet mode.
|
||||
.TP
|
||||
\fB\-V\fR, \fB\-\-version\fR
|
||||
Prints version information
|
||||
.SS "OPTIONS:"
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-domain\fR <domain>
|
||||
Target domain. When it's specified, a wordlist can be used from stdin for
|
||||
bruteforcing.
|
||||
.TP
|
||||
\fB\-r\fR, \fB\-\-resolvers\fR <resolvers>
|
||||
\fB\-r\fR, \fB\-\-resolvers\fR <RESOLVERS>
|
||||
File with DNS ips.
|
||||
.TP
|
||||
\fB\-\-retries\fR <retries>
|
||||
Number of retries after lookup failure before giving up. Defaults to 0
|
||||
\fB\-\-timeout\fR <TIMEOUT>
|
||||
Timeout in seconds. Default: 3 [default: 3]
|
||||
.TP
|
||||
\fB\-t\fR, \fB\-\-threads\fR <threads>
|
||||
Number of threads. Default: 100
|
||||
\fB\-i\fR, \fB\-\-ip\fR
|
||||
Display the record data.
|
||||
.TP
|
||||
\fB\-\-timeout\fR <timeout>
|
||||
Timeout in seconds. Default: 1
|
||||
\fB\-e\fR, \fB\-\-enable\-double\-check\fR
|
||||
Enable the double verification algorithm for subdomains. Default: false
|
||||
.TP
|
||||
\fB\-q\fR, \fB\-\-quiet\-flag\fR
|
||||
Quiet mode, no output except errors.
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Print help
|
||||
.TP
|
||||
\fB\-V\fR, \fB\-\-version\fR
|
||||
Print version
|
||||
.SH "SEE ALSO"
|
||||
The full documentation for
|
||||
.B Rusolver
|
||||
.B rusolver
|
||||
is maintained as a Texinfo manual. If the
|
||||
.B info
|
||||
and
|
||||
.B Rusolver
|
||||
.B rusolver
|
||||
programs are properly installed at your site, the command
|
||||
.IP
|
||||
.B info Rusolver
|
||||
.B info rusolver
|
||||
.PP
|
||||
should give you access to the complete manual.
|
||||
|
|
|
|||
46
src/args.rs
Normal file
46
src/args.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use clap::{arg, Parser};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 100,
|
||||
help = "Number of threads. Default: 100"
|
||||
)]
|
||||
pub threads: usize,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
default_value_t = 0,
|
||||
help = "Number of retries after lookup failure before giving up. Defaults to 0"
|
||||
)]
|
||||
pub retries: usize,
|
||||
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Target domain. When it's specified, a wordlist can be used from stdin for bruteforcing."
|
||||
)]
|
||||
pub domain: Option<String>,
|
||||
|
||||
#[arg(short, long, help = "File with DNS ips.")]
|
||||
pub resolvers: Option<String>,
|
||||
|
||||
#[arg(long, default_value_t = 3, help = "Timeout in seconds. Default: 3")]
|
||||
pub timeout: u64,
|
||||
|
||||
#[arg(short, long, help = "Display the record data.")]
|
||||
pub ip: bool,
|
||||
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Enable the double verification algorithm for subdomains. Default: false"
|
||||
)]
|
||||
pub enable_double_check: bool,
|
||||
|
||||
#[arg(short, long, help = "Quiet mode, no output except errors.")]
|
||||
pub quiet_flag: bool,
|
||||
}
|
||||
150
src/dnslib.rs
Normal file
150
src/dnslib.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
use {
|
||||
crate::{
|
||||
structs::{DomainData, LibOptions},
|
||||
utils::print_domain_data,
|
||||
},
|
||||
futures::stream::{self, StreamExt},
|
||||
hickory_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
name_server::TokioConnectionProvider,
|
||||
proto::{rr::RecordType, xfer::Protocol},
|
||||
TokioResolver,
|
||||
},
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
net::SocketAddr,
|
||||
},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn return_tokio_asyncresolver<S: ::std::hash::BuildHasher>(
|
||||
nameserver_ips: &HashSet<String, S>,
|
||||
options: ResolverOpts,
|
||||
) -> TokioResolver {
|
||||
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len());
|
||||
|
||||
name_servers.extend(nameserver_ips.iter().map(|server| {
|
||||
let socket_addr = SocketAddr::V4(server.parse().unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error parsing the server {}, only IPv4 are allowed. Error: {}",
|
||||
server, e
|
||||
)
|
||||
}));
|
||||
|
||||
NameServerConfig {
|
||||
socket_addr,
|
||||
protocol: Protocol::Udp,
|
||||
tls_dns_name: None,
|
||||
http_endpoint: None,
|
||||
trust_negative_responses: false,
|
||||
bind_addr: None,
|
||||
}
|
||||
}));
|
||||
|
||||
TokioResolver::builder_with_config(
|
||||
ResolverConfig::from_parts(None, vec![], name_servers),
|
||||
TokioConnectionProvider::default(),
|
||||
)
|
||||
.with_options(options)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub async fn return_hosts_data(options: &LibOptions) -> HashMap<String, DomainData> {
|
||||
let threads = if options.hosts.len() < options.threads {
|
||||
options.hosts.len()
|
||||
} else {
|
||||
options.threads
|
||||
};
|
||||
|
||||
stream::iter(options.hosts.clone().into_iter().map(|host| {
|
||||
let lookup_host = format!("{}.", host.trim_end_matches('.'));
|
||||
let wildcard_ips = options.wildcard_ips.clone();
|
||||
let mut domain_data = DomainData::default();
|
||||
|
||||
async move {
|
||||
let ip_lookup = if options.enable_double_check {
|
||||
match (
|
||||
options.resolvers.ipv4_lookup(lookup_host.clone()).await,
|
||||
options.trustable_resolvers.ipv4_lookup(lookup_host).await,
|
||||
) {
|
||||
(Ok(_), Ok(ip)) => Some(ip),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
options.resolvers.ipv4_lookup(lookup_host).await.ok()
|
||||
};
|
||||
|
||||
if let Some(ip_lookup) = ip_lookup {
|
||||
for ip in ip_lookup.iter() {
|
||||
domain_data.ipv4_addresses.insert(ip.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
domain_data.is_wildcard = domain_data
|
||||
.ipv4_addresses
|
||||
.iter()
|
||||
.all(|ip| wildcard_ips.contains(ip));
|
||||
|
||||
if !options.quiet_flag {
|
||||
print_domain_data(&host, &domain_data, options);
|
||||
}
|
||||
|
||||
(host, domain_data)
|
||||
}
|
||||
}))
|
||||
.buffer_unordered(threads)
|
||||
.collect::<HashMap<String, DomainData>>()
|
||||
.await
|
||||
}
|
||||
|
||||
// Used internally for now
|
||||
pub async fn return_cname_data<S: ::std::hash::BuildHasher>(
|
||||
hosts: HashSet<String, S>,
|
||||
resolver: TokioResolver,
|
||||
trustable_resolver: TokioResolver,
|
||||
disable_double_check: bool,
|
||||
mut threads: usize,
|
||||
) -> HashMap<String, DomainData> {
|
||||
if hosts.len() < threads {
|
||||
threads = hosts.len();
|
||||
}
|
||||
|
||||
stream::iter(hosts)
|
||||
.map(|host| {
|
||||
let host = host.trim_end_matches('.').to_owned();
|
||||
let fqdn = format!("{host}.");
|
||||
|
||||
let resolver = resolver.clone();
|
||||
let trustable_resolver = trustable_resolver.clone();
|
||||
|
||||
async move {
|
||||
let cname_lookup = if disable_double_check {
|
||||
resolver.lookup(fqdn.clone(), RecordType::CNAME).await.ok()
|
||||
} else {
|
||||
match (
|
||||
resolver.lookup(fqdn.clone(), RecordType::CNAME).await,
|
||||
trustable_resolver.lookup(fqdn, RecordType::CNAME).await,
|
||||
) {
|
||||
(Ok(_), Ok(lookup)) => Some(lookup),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let mut domain_data = DomainData::default();
|
||||
|
||||
if let Some(lookup) = cname_lookup {
|
||||
for record in lookup.iter() {
|
||||
if let Some(cname) = record.as_cname() {
|
||||
domain_data.cname = cname.to_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(host, domain_data)
|
||||
}
|
||||
})
|
||||
.buffer_unordered(threads)
|
||||
.collect::<HashMap<String, DomainData>>()
|
||||
.await
|
||||
}
|
||||
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod args;
|
||||
pub mod dnslib;
|
||||
pub mod structs;
|
||||
pub mod utils;
|
||||
278
src/main.rs
278
src/main.rs
|
|
@ -1,105 +1,24 @@
|
|||
use {
|
||||
clap::{value_t, App, Arg},
|
||||
futures::stream::{self, StreamExt},
|
||||
rand::{distributions::Alphanumeric, thread_rng as rng, Rng},
|
||||
std::{
|
||||
collections::HashSet,
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
},
|
||||
clap::Parser,
|
||||
rusolver::{args::Args, dnslib, structs, utils},
|
||||
std::collections::HashSet,
|
||||
tokio::{
|
||||
self,
|
||||
fs::File,
|
||||
io::{self, AsyncReadExt},
|
||||
},
|
||||
trust_dns_resolver::{
|
||||
config::{
|
||||
LookupIpStrategy, NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig,
|
||||
ResolverOpts,
|
||||
},
|
||||
name_server::{GenericConnection, GenericConnectionProvider, TokioRuntime},
|
||||
AsyncResolver, TokioAsyncResolver,
|
||||
},
|
||||
};
|
||||
|
||||
// WIP: add support for AAAA, TXT, SRV, NAPTR, PTR, CNAME, DNAME, MX, NS, SOA, LOC, SVCB, HTTPS, SPF, CAA and AVC resource records.
|
||||
// This could use a new command line option such as -t, e.g. echo www.example.com | rusolver -i -t AAAA. It might also make sense
|
||||
// to change -i/--ip to -d/--data with the text Display the record data.
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Eval args
|
||||
let matches = App::new("Rusolver")
|
||||
.version(clap::crate_version!())
|
||||
.author("Eduard Tolosa <edu4rdshl@protonmail.com>")
|
||||
.about("Fast DNS resolver written in Rust.")
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.short("t")
|
||||
.long("threads")
|
||||
.takes_value(true)
|
||||
.help("Number of threads. Default: 100"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("retries")
|
||||
.long("retries")
|
||||
.takes_value(true)
|
||||
.help("Number of retries after lookup failure before giving up. Defaults to 0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("domain")
|
||||
.short("d")
|
||||
.long("domain")
|
||||
.takes_value(true)
|
||||
.help("Target domain. When it's specified, a wordlist can be used from stdin for bruteforcing."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("resolvers")
|
||||
.short("r")
|
||||
.long("resolvers")
|
||||
.takes_value(true)
|
||||
.help("File with DNS ips."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("timeout")
|
||||
.long("timeout")
|
||||
.takes_value(true)
|
||||
.help("Timeout in seconds. Default: 1"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ip")
|
||||
.short("i")
|
||||
.long("ip")
|
||||
.takes_value(false)
|
||||
.help("Show the discovered IP addresses. Default: false"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-verify")
|
||||
.long("no-verify")
|
||||
.takes_value(false)
|
||||
.help("Disables the double verification algorithm for valid subdomains -NOT RECOMMENDED-. Default: false"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("quiet")
|
||||
.short("q")
|
||||
.long("quiet")
|
||||
.takes_value(false)
|
||||
.help("Enables quiet mode."),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// Assign values or use defaults
|
||||
let show_ip_adress = matches.is_present("ip");
|
||||
let threads = value_t!(matches.value_of("threads"), usize).unwrap_or_else(|_| 100);
|
||||
let timeout = value_t!(matches.value_of("timeout"), u64).unwrap_or_else(|_| 1);
|
||||
let retries = value_t!(matches.value_of("retries"), usize).unwrap_or_else(|_| 0);
|
||||
let quiet_flag = matches.is_present("quiet");
|
||||
let custom_resolvers = matches.is_present("resolvers");
|
||||
let disable_double_check = matches.is_present("no-verify") && custom_resolvers;
|
||||
let args = Args::parse();
|
||||
|
||||
// Resolver opts
|
||||
let options = ResolverOpts {
|
||||
timeout: std::time::Duration::from_secs(timeout),
|
||||
attempts: retries,
|
||||
ip_strategy: LookupIpStrategy::Ipv4Only,
|
||||
num_concurrent_reqs: 1,
|
||||
..Default::default()
|
||||
};
|
||||
let options = utils::return_resolver_opts(args.timeout, args.retries);
|
||||
|
||||
let built_in_nameservers: HashSet<String> = vec![
|
||||
// Cloudflare
|
||||
|
|
@ -117,28 +36,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// Verisign
|
||||
"64.6.64.6:53",
|
||||
"64.6.65.6:53",
|
||||
// UncensoredDNS
|
||||
"91.239.100.100:53",
|
||||
"89.233.43.71:53",
|
||||
// dns.watch
|
||||
"84.200.69.80:53",
|
||||
"84.200.70.40:53",
|
||||
]
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
// Create resolvers
|
||||
let nameserver_ips;
|
||||
let mut nameserver_ips;
|
||||
|
||||
if custom_resolvers {
|
||||
nameserver_ips =
|
||||
return_file_lines(value_t!(matches.value_of("resolvers"), String).unwrap()).await;
|
||||
if args.resolvers.is_some() {
|
||||
nameserver_ips = utils::return_file_lines(&args.resolvers.unwrap()).await;
|
||||
nameserver_ips.retain(|ip| !ip.is_empty());
|
||||
} else {
|
||||
nameserver_ips = built_in_nameservers.clone();
|
||||
}
|
||||
let resolvers = return_tokio_asyncresolver(nameserver_ips, options);
|
||||
let trustable_resolver = return_tokio_asyncresolver(built_in_nameservers, options);
|
||||
|
||||
let resolvers = dnslib::return_tokio_asyncresolver(&nameserver_ips, options.clone());
|
||||
let trustable_resolvers = dnslib::return_tokio_asyncresolver(&built_in_nameservers, options);
|
||||
let mut wildcard_ips = HashSet::new();
|
||||
|
||||
// Read stdin
|
||||
|
|
@ -146,152 +60,30 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut stdin = io::stdin();
|
||||
stdin.read_to_string(&mut buffer).await?;
|
||||
|
||||
let hosts: Vec<String> = if matches.is_present("domain") {
|
||||
let domain = value_t!(matches, "domain", String).unwrap();
|
||||
wildcard_ips = detect_wildcards(&domain, &trustable_resolver, quiet_flag).await;
|
||||
let hosts: HashSet<String> = if args.domain.is_some() {
|
||||
let domain = args.domain.unwrap();
|
||||
wildcard_ips =
|
||||
utils::detect_wildcards(&domain, &trustable_resolvers, args.quiet_flag).await;
|
||||
buffer
|
||||
.lines()
|
||||
.map(|word| format!("{}.{}", word, domain))
|
||||
.map(|word| format!("{word}.{domain}"))
|
||||
.collect()
|
||||
} else {
|
||||
buffer.lines().map(str::to_owned).collect()
|
||||
};
|
||||
|
||||
futures::stream::iter(hosts.into_iter().map(|host| {
|
||||
let resolver_fut = resolvers.ipv4_lookup(host.clone());
|
||||
let trustable_resolver_fut = trustable_resolver.ipv4_lookup(host.clone());
|
||||
let wildcard_ips = wildcard_ips.clone();
|
||||
async move {
|
||||
if let Ok(ip) = resolver_fut.await {
|
||||
let mut ips = HashSet::new();
|
||||
if disable_double_check {
|
||||
ips = ip
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<HashSet<String>>();
|
||||
} else if let Ok(ip) = trustable_resolver_fut.await {
|
||||
ips = ip
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<HashSet<String>>();
|
||||
}
|
||||
if show_ip_adress && !ips.iter().all(|ip| wildcard_ips.contains(ip)) {
|
||||
println!("{};{:?}", host, ips)
|
||||
} else if !ips.iter().all(|ip| wildcard_ips.contains(ip)) {
|
||||
println!("{}", host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.buffer_unordered(threads)
|
||||
.collect::<Vec<()>>()
|
||||
.await;
|
||||
let options = structs::LibOptions {
|
||||
hosts,
|
||||
resolvers,
|
||||
trustable_resolvers,
|
||||
wildcard_ips,
|
||||
enable_double_check: args.enable_double_check,
|
||||
threads: args.threads,
|
||||
show_ip_address: args.ip,
|
||||
quiet_flag: args.quiet_flag,
|
||||
};
|
||||
|
||||
dnslib::return_hosts_data(&options).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// In the future I may need to implement error propagation, but for now it's fine
|
||||
// to deal with matches
|
||||
async fn return_file_lines(file: String) -> HashSet<String> {
|
||||
let mut f = match File::open(&file).await {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Error opening resolvers file. Error: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
};
|
||||
let mut buffer = String::new();
|
||||
|
||||
match f.read_to_string(&mut buffer).await {
|
||||
Ok(a) => a,
|
||||
_ => unreachable!("Error reading to string."),
|
||||
};
|
||||
buffer.lines().map(|f| format!("{}:53", f)).collect()
|
||||
}
|
||||
|
||||
fn return_tokio_asyncresolver(
|
||||
nameserver_ips: HashSet<String>,
|
||||
options: ResolverOpts,
|
||||
) -> AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>> {
|
||||
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len() * 2);
|
||||
|
||||
name_servers.extend(nameserver_ips.into_iter().flat_map(|server| {
|
||||
let socket_addr = SocketAddr::V4(match server.parse() {
|
||||
Ok(a) => a,
|
||||
Err(e) => unreachable!(
|
||||
"Error parsing the server {}, only IPv4 are allowed. Error: {}",
|
||||
server, e
|
||||
),
|
||||
});
|
||||
|
||||
std::iter::once(NameServerConfig {
|
||||
socket_addr,
|
||||
protocol: Protocol::Udp,
|
||||
tls_dns_name: None,
|
||||
trust_nx_responses: false,
|
||||
})
|
||||
.chain(std::iter::once(NameServerConfig {
|
||||
socket_addr,
|
||||
protocol: Protocol::Tcp,
|
||||
tls_dns_name: None,
|
||||
trust_nx_responses: false,
|
||||
}))
|
||||
}));
|
||||
|
||||
TokioAsyncResolver::tokio(
|
||||
ResolverConfig::from_parts(None, vec![], name_servers),
|
||||
options,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn detect_wildcards(
|
||||
target: &str,
|
||||
resolvers: &AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>>,
|
||||
quiet_flag: bool,
|
||||
) -> HashSet<String> {
|
||||
if !quiet_flag {
|
||||
println!("Running wildcards detection for {}...\n", target)
|
||||
}
|
||||
let mut generated_wilcards: HashSet<String> = HashSet::new();
|
||||
for _ in 1..20 {
|
||||
generated_wilcards.insert(format!(
|
||||
"{}.{}.",
|
||||
rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(15)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
target
|
||||
));
|
||||
}
|
||||
|
||||
generated_wilcards = stream::iter(generated_wilcards.clone().into_iter().map(
|
||||
|host| async move {
|
||||
if let Ok(ips) = resolvers.ipv4_lookup(host.clone()).await {
|
||||
ips.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
))
|
||||
.buffer_unordered(10)
|
||||
.map(stream::iter)
|
||||
.flatten()
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
generated_wilcards.retain(|ip| ip.parse::<Ipv4Addr>().is_ok());
|
||||
|
||||
if !generated_wilcards.is_empty() && !quiet_flag {
|
||||
println!(
|
||||
"Wilcards detected for {} and wildcard's IP saved for furter work.",
|
||||
target
|
||||
);
|
||||
println!("Wilcard IPs: {:?}\n", generated_wilcards)
|
||||
} else if !quiet_flag {
|
||||
println!("No wilcards detected for {}, nice!\n", target)
|
||||
}
|
||||
generated_wilcards
|
||||
}
|
||||
|
|
|
|||
21
src/structs.rs
Normal file
21
src/structs.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use {hickory_resolver::TokioResolver, std::collections::HashSet};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DomainData {
|
||||
pub ipv4_addresses: HashSet<String>,
|
||||
pub ipv6_addresses: HashSet<String>,
|
||||
pub cname: String,
|
||||
pub is_wildcard: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LibOptions {
|
||||
pub hosts: HashSet<String>,
|
||||
pub resolvers: TokioResolver,
|
||||
pub trustable_resolvers: TokioResolver,
|
||||
pub wildcard_ips: HashSet<String>,
|
||||
pub enable_double_check: bool,
|
||||
pub threads: usize,
|
||||
pub show_ip_address: bool,
|
||||
pub quiet_flag: bool,
|
||||
}
|
||||
109
src/utils.rs
Normal file
109
src/utils.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use {
|
||||
crate::structs::{DomainData, LibOptions},
|
||||
futures::stream::{self, StreamExt},
|
||||
hickory_resolver::{
|
||||
config::{LookupIpStrategy, ResolverOpts, ServerOrderingStrategy},
|
||||
TokioResolver,
|
||||
},
|
||||
rand::{distr::Alphanumeric, rng, Rng},
|
||||
std::{collections::HashSet, net::Ipv4Addr},
|
||||
tokio::{fs::File, io::AsyncReadExt},
|
||||
};
|
||||
|
||||
pub async fn return_file_lines(file: &str) -> HashSet<String> {
|
||||
let mut f = match File::open(file).await {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Error opening resolvers file. Error: {e}");
|
||||
std::process::exit(1)
|
||||
}
|
||||
};
|
||||
let mut buffer = String::new();
|
||||
|
||||
(f.read_to_string(&mut buffer).await)
|
||||
.unwrap_or_else(|_| unreachable!("Error reading to string."));
|
||||
|
||||
let estimated_lines = buffer.matches('\n').count() + 1;
|
||||
let mut result = HashSet::with_capacity(estimated_lines);
|
||||
|
||||
for line in buffer.lines() {
|
||||
if !line.is_empty() {
|
||||
result.insert(format!("{line}:53"));
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn detect_wildcards(
|
||||
target: &str,
|
||||
resolvers: &TokioResolver,
|
||||
quiet_flag: bool,
|
||||
) -> HashSet<String> {
|
||||
if !quiet_flag {
|
||||
println!("Running wildcards detection for {target}...\n");
|
||||
}
|
||||
|
||||
let mut generated_wildcards = HashSet::with_capacity(19);
|
||||
|
||||
// Generate random subdomains for wildcard detection
|
||||
for _ in 1..20 {
|
||||
let random_subdomain: String = rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(15)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
generated_wildcards.insert(format!("{random_subdomain}.{target}."));
|
||||
}
|
||||
|
||||
let wildcard_ips: HashSet<String> = stream::iter(generated_wildcards.into_iter())
|
||||
.map(|host| async move {
|
||||
resolvers.ipv4_lookup(host).await.map_or_else(
|
||||
|_| Vec::new(),
|
||||
|ips| {
|
||||
ips.into_iter()
|
||||
.filter_map(|ip| {
|
||||
let ip_str = ip.to_string();
|
||||
if ip_str.parse::<Ipv4Addr>().is_ok() {
|
||||
Some(ip_str)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
},
|
||||
)
|
||||
})
|
||||
.buffer_unordered(10)
|
||||
.map(stream::iter)
|
||||
.flatten()
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
if !wildcard_ips.is_empty() && !quiet_flag {
|
||||
println!("Wildcards detected for {target} and wildcard's IP saved for further work.");
|
||||
println!("Wildcard IPs: {wildcard_ips:?}\n");
|
||||
} else if !quiet_flag {
|
||||
println!("No wildcards detected for {target}, nice!\n");
|
||||
}
|
||||
wildcard_ips
|
||||
}
|
||||
|
||||
pub fn print_domain_data(host: &str, domain_data: &DomainData, options: &LibOptions) {
|
||||
if !domain_data.is_wildcard && !domain_data.ipv4_addresses.is_empty() {
|
||||
if options.show_ip_address {
|
||||
println!("{}:{:?}", host, domain_data.ipv4_addresses);
|
||||
} else {
|
||||
println!("{}", host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_resolver_opts(timeout: u64, retries: usize) -> ResolverOpts {
|
||||
let mut options = ResolverOpts::default();
|
||||
options.timeout = std::time::Duration::from_secs(timeout);
|
||||
options.attempts = retries;
|
||||
options.ip_strategy = LookupIpStrategy::Ipv4Only;
|
||||
options.num_concurrent_reqs = 1;
|
||||
options.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
|
||||
options
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue