mirror of
https://github.com/edu4rdshl/rusolver.git
synced 2026-07-17 23:24:55 +00:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87f9336cc2 | |||
| 12318ce9c0 | |||
| c372db9eab | |||
| 9b329daa6a | |||
| 206c392fc0 | |||
| 28b29ca515 | |||
| 3bb0ac4b35 | |||
| 55fd2cb2be | |||
| 37020360c2 | |||
| e06eeffb48 | |||
| 7e683bd1ae | |||
|
|
cf75cafee7 | ||
|
|
7755533e1e | ||
|
|
88ee0f9b58 | ||
|
|
624a3febb9 | ||
|
|
06ea060de9 | ||
|
|
42dbf01d76 | ||
|
|
8620eded16 | ||
|
|
e26ae062d6 |
12 changed files with 2021 additions and 871 deletions
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
|
||||
2105
Cargo.lock
generated
2105
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
name = "rusolver"
|
||||
version = "0.9.0"
|
||||
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,17 +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.16.1", features = ["full"] }
|
||||
futures = "0.3.21"
|
||||
trust-dns-resolver = "0.20.4"
|
||||
# trust-dns-resolver = { git = "https://github.com/Edu4rdSHL/trust-dns", branch = "main" }
|
||||
clap = "2.33.4"
|
||||
rand = "0.8.4"
|
||||
|
||||
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'
|
||||
incremental = false
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
|
|
|||
61
builder.sh
61
builder.sh
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rusolver releaser
|
||||
NAME="rusolver"
|
||||
|
||||
LINUX_TARGET="x86_64-unknown-linux-musl"
|
||||
|
|
@ -7,16 +6,21 @@ 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" >"target/$LINUX_TARGET/release/$NAME-linux.sha512"
|
||||
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
|
||||
|
|
@ -27,7 +31,8 @@ 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" >"target/$LINUX_X86_TARGET/release/$NAME-linux-i386.sha512"
|
||||
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
|
||||
|
|
@ -38,7 +43,8 @@ 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" >"target/$WIN_TARGET/release/$NAME-windows.exe.sha512"
|
||||
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
|
||||
|
|
@ -49,7 +55,8 @@ 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" >"target/$ARMV7_TARGET/release/$NAME-armv7.sha512"
|
||||
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
|
||||
|
|
@ -60,21 +67,23 @@ 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" >"target/$AARCH_TARGET/release/$NAME-aarch64.sha512"
|
||||
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"
|
||||
strip "target/$OSX_TARGET/release/$NAME-osx"
|
||||
sha512sum "target/$OSX_TARGET/release/$NAME-osx" >"target/$OSX_TARGET/release/$NAME-osx.sha512"
|
||||
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
|
||||
|
|
@ -86,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.5.
|
||||
.TH RUSOLVER "1" "February 2022" "Rusolver 0.9.0" "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.9.0
|
||||
rusolver \- manual page for rusolver 0.10.2
|
||||
.SH SYNOPSIS
|
||||
.B rusolver
|
||||
[\fI\,OPTIONS\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Rusolver 0.9.0
|
||||
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: 3
|
||||
\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
46
src/args.rs
|
|
@ -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,
|
||||
}
|
||||
174
src/dnslib.rs
174
src/dnslib.rs
|
|
@ -1,65 +1,109 @@
|
|||
use {
|
||||
crate::structs::DomainData,
|
||||
crate::{
|
||||
structs::{DomainData, LibOptions},
|
||||
utils::print_domain_data,
|
||||
},
|
||||
futures::stream::{self, StreamExt},
|
||||
std::collections::{HashMap, HashSet},
|
||||
trust_dns_resolver::config::ResolverOpts,
|
||||
};
|
||||
|
||||
use {
|
||||
std::net::SocketAddr,
|
||||
trust_dns_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig},
|
||||
name_server::{GenericConnection, GenericConnectionProvider, TokioRuntime},
|
||||
AsyncResolver, TokioAsyncResolver,
|
||||
hickory_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
name_server::TokioConnectionProvider,
|
||||
proto::{rr::RecordType, xfer::Protocol},
|
||||
TokioResolver,
|
||||
},
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
net::SocketAddr,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn return_tokio_asyncresolver(
|
||||
nameserver_ips: HashSet<String>,
|
||||
#[must_use]
|
||||
pub fn return_tokio_asyncresolver<S: ::std::hash::BuildHasher>(
|
||||
nameserver_ips: &HashSet<String, S>,
|
||||
options: ResolverOpts,
|
||||
) -> AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>> {
|
||||
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len() * 2);
|
||||
) -> TokioResolver {
|
||||
let mut name_servers = NameServerConfigGroup::with_capacity(nameserver_ips.len());
|
||||
|
||||
name_servers.extend(nameserver_ips.into_iter().flat_map(|server| {
|
||||
let socket_addr = SocketAddr::V4(match server.parse() {
|
||||
Ok(a) => a,
|
||||
Err(e) => unreachable!(
|
||||
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
|
||||
),
|
||||
});
|
||||
)
|
||||
}));
|
||||
|
||||
std::iter::once(NameServerConfig {
|
||||
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,
|
||||
}))
|
||||
http_endpoint: None,
|
||||
trust_negative_responses: false,
|
||||
bind_addr: None,
|
||||
}
|
||||
}));
|
||||
|
||||
TokioAsyncResolver::tokio(
|
||||
TokioResolver::builder_with_config(
|
||||
ResolverConfig::from_parts(None, vec![], name_servers),
|
||||
options,
|
||||
TokioConnectionProvider::default(),
|
||||
)
|
||||
.unwrap()
|
||||
.with_options(options)
|
||||
.build()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn return_hosts_data(
|
||||
hosts: HashSet<String>,
|
||||
resolvers: AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>>,
|
||||
trustable_resolver: AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>>,
|
||||
wildcard_ips: HashSet<String>,
|
||||
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,
|
||||
show_ip_addresses: bool,
|
||||
quiet_flag: bool,
|
||||
) -> HashMap<String, DomainData> {
|
||||
if hosts.len() < threads {
|
||||
threads = hosts.len();
|
||||
|
|
@ -67,38 +111,34 @@ pub async fn return_hosts_data(
|
|||
|
||||
stream::iter(hosts)
|
||||
.map(|host| {
|
||||
let resolver_fut = resolvers.ipv4_lookup(host.trim_end_matches('.').to_owned() + ".");
|
||||
let trustable_resolver_fut =
|
||||
trustable_resolver.ipv4_lookup(host.trim_end_matches('.').to_owned() + ".");
|
||||
let wildcard_ips = wildcard_ips.clone();
|
||||
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 Ok(ip) = resolver_fut.await {
|
||||
if disable_double_check {
|
||||
domain_data.ipv4_addresses = ip
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<HashSet<String>>();
|
||||
} else if let Ok(ip) = trustable_resolver_fut.await {
|
||||
domain_data.ipv4_addresses = ip
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<HashSet<String>>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
domain_data.is_wildcard = domain_data
|
||||
.ipv4_addresses
|
||||
.iter()
|
||||
.all(|ip| wildcard_ips.contains(ip));
|
||||
|
||||
if !quiet_flag {
|
||||
if show_ip_addresses && !domain_data.is_wildcard {
|
||||
println!("{};{:?}", host, domain_data.ipv4_addresses);
|
||||
} else if !domain_data.is_wildcard {
|
||||
println!("{}", host)
|
||||
}
|
||||
}
|
||||
|
||||
(host, domain_data)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod args;
|
||||
pub mod dnslib;
|
||||
pub mod structs;
|
||||
pub mod utils;
|
||||
|
|
|
|||
128
src/main.rs
128
src/main.rs
|
|
@ -1,97 +1,24 @@
|
|||
use {
|
||||
clap::{value_t, App, Arg},
|
||||
rusolver::{dnslib, utils},
|
||||
clap::Parser,
|
||||
rusolver::{args::Args, dnslib, structs, utils},
|
||||
std::collections::HashSet,
|
||||
tokio::{
|
||||
self,
|
||||
io::{self, AsyncReadExt},
|
||||
},
|
||||
trust_dns_resolver::config::{LookupIpStrategy, ResolverOpts},
|
||||
};
|
||||
|
||||
// Please add support for AAAA, TXT, SRV, NAPTR, PTR, CNAME, DNAME, MX, NS, SOA, LOC, SVCB, HTTPS, SPF, CAA and AVC resource records.
|
||||
// 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: 3"),
|
||||
)
|
||||
.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(|_| 3);
|
||||
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
|
||||
|
|
@ -109,30 +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 mut nameserver_ips;
|
||||
|
||||
if custom_resolvers {
|
||||
nameserver_ips =
|
||||
utils::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 = dnslib::return_tokio_asyncresolver(nameserver_ips, options);
|
||||
let trustable_resolver = dnslib::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
|
||||
|
|
@ -140,28 +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: HashSet<String> = if matches.is_present("domain") {
|
||||
let domain = value_t!(matches, "domain", String).unwrap();
|
||||
wildcard_ips = utils::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()
|
||||
};
|
||||
|
||||
dnslib::return_hosts_data(
|
||||
let options = structs::LibOptions {
|
||||
hosts,
|
||||
resolvers,
|
||||
trustable_resolver,
|
||||
trustable_resolvers,
|
||||
wildcard_ips,
|
||||
disable_double_check,
|
||||
threads,
|
||||
show_ip_adress,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashSet;
|
||||
use {hickory_resolver::TokioResolver, std::collections::HashSet};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DomainData {
|
||||
pub ipv4_addresses: HashSet<String>,
|
||||
pub ipv6_addresses: HashSet<String>,
|
||||
|
|
@ -8,13 +8,14 @@ pub struct DomainData {
|
|||
pub is_wildcard: bool,
|
||||
}
|
||||
|
||||
impl Default for DomainData {
|
||||
fn default() -> Self {
|
||||
DomainData {
|
||||
ipv4_addresses: HashSet::new(),
|
||||
ipv6_addresses: HashSet::new(),
|
||||
cname: String::from(""),
|
||||
is_wildcard: false,
|
||||
}
|
||||
}
|
||||
#[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,
|
||||
}
|
||||
|
|
|
|||
112
src/utils.rs
112
src/utils.rs
|
|
@ -1,81 +1,109 @@
|
|||
use {
|
||||
crate::structs::{DomainData, LibOptions},
|
||||
futures::stream::{self, StreamExt},
|
||||
rand::{distributions::Alphanumeric, thread_rng as rng, Rng},
|
||||
std::{collections::HashSet, net::Ipv4Addr},
|
||||
tokio::{self, fs::File, io::AsyncReadExt},
|
||||
trust_dns_resolver::{
|
||||
name_server::{GenericConnection, GenericConnectionProvider, TokioRuntime},
|
||||
AsyncResolver,
|
||||
hickory_resolver::{
|
||||
config::{LookupIpStrategy, ResolverOpts, ServerOrderingStrategy},
|
||||
TokioResolver,
|
||||
},
|
||||
rand::{distr::Alphanumeric, rng, Rng},
|
||||
std::{collections::HashSet, net::Ipv4Addr},
|
||||
tokio::{fs::File, io::AsyncReadExt},
|
||||
};
|
||||
|
||||
// In the future I may need to implement error propagation, but for now it's fine
|
||||
// to deal with matches
|
||||
pub async fn return_file_lines(file: String) -> HashSet<String> {
|
||||
let mut f = match File::open(&file).await {
|
||||
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);
|
||||
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()
|
||||
(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: &AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>>,
|
||||
resolvers: &TokioResolver,
|
||||
quiet_flag: bool,
|
||||
) -> HashSet<String> {
|
||||
if !quiet_flag {
|
||||
println!("Running wildcards detection for {}...\n", target)
|
||||
println!("Running wildcards detection for {target}...\n");
|
||||
}
|
||||
let mut generated_wilcards: HashSet<String> = HashSet::new();
|
||||
|
||||
let mut generated_wildcards = HashSet::with_capacity(19);
|
||||
|
||||
// Generate random subdomains for wildcard detection
|
||||
for _ in 1..20 {
|
||||
generated_wilcards.insert(format!(
|
||||
"{}.{}.",
|
||||
rng()
|
||||
let random_subdomain: String = rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(15)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
target
|
||||
));
|
||||
.collect();
|
||||
generated_wildcards.insert(format!("{random_subdomain}.{target}."));
|
||||
}
|
||||
|
||||
generated_wilcards = stream::iter(generated_wilcards.clone().into_iter().map(
|
||||
|host| async move {
|
||||
if let Ok(ips) = resolvers.ipv4_lookup(host.clone()).await {
|
||||
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()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.filter_map(|ip| {
|
||||
let ip_str = ip.to_string();
|
||||
if ip_str.parse::<Ipv4Addr>().is_ok() {
|
||||
Some(ip_str)
|
||||
} else {
|
||||
Vec::new()
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
},
|
||||
))
|
||||
)
|
||||
})
|
||||
.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)
|
||||
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 wilcards detected for {}, nice!\n", target)
|
||||
println!("No wildcards detected for {target}, nice!\n");
|
||||
}
|
||||
generated_wilcards
|
||||
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