mirror of
https://github.com/edu4rdshl/rusolver.git
synced 2026-07-18 07:34:48 +00:00
Compare commits
24 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 |
12 changed files with 2129 additions and 894 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
|
resolvers
|
||||||
.gitignore
|
.gitignore
|
||||||
wordlist
|
wordlist
|
||||||
|
/ghbinaries
|
||||||
2131
Cargo.lock
generated
2131
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
21
Cargo.toml
21
Cargo.toml
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rusolver"
|
name = "rusolver"
|
||||||
version = "0.8.0"
|
version = "0.10.2"
|
||||||
authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"]
|
authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
description = "Fast DNS resolver written in Rust."
|
description = "Fast DNS resolver written in Rust."
|
||||||
repository = "https://github.com/edu4rdshl/rusolver"
|
repository = "https://github.com/edu4rdshl/rusolver"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
|
@ -12,9 +12,14 @@ readme = "README.md"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.6.1", features = ["full"] }
|
tokio = { version = "1.47.0", features = ["full"] }
|
||||||
futures = "0.3.15"
|
futures = "0.3.31"
|
||||||
trust-dns-resolver = { version = "0.20.3" }
|
hickory-resolver = "0.25.2"
|
||||||
# trust-dns-resolver = { git = "https://github.com/Edu4rdSHL/trust-dns", branch = "main" }
|
clap = { version = "4.5.41", features = ["derive"] }
|
||||||
clap = "2.33.3"
|
rand = "0.9.2"
|
||||||
rand = "0.8.3"
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = 'abort'
|
||||||
|
strip = true
|
||||||
|
|
|
||||||
69
builder.sh
69
builder.sh
|
|
@ -1,28 +1,50 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Rusolver releaser
|
|
||||||
NAME="rusolver"
|
NAME="rusolver"
|
||||||
|
|
||||||
LINUX_TARGET="x86_64-unknown-linux-musl"
|
LINUX_TARGET="x86_64-unknown-linux-musl"
|
||||||
|
LINUX_X86_TARGET="i686-unknown-linux-musl"
|
||||||
WIN_TARGET="x86_64-pc-windows-gnu"
|
WIN_TARGET="x86_64-pc-windows-gnu"
|
||||||
ARMV7_TARGET="armv7-unknown-linux-gnueabihf"
|
ARMV7_TARGET="armv7-unknown-linux-gnueabihf"
|
||||||
AARCH_TARGET="aarch64-unknown-linux-gnu"
|
AARCH_TARGET="aarch64-unknown-linux-gnu"
|
||||||
OSX_TARGET="x86_64-apple-darwin"
|
# OSX_TARGET="x86_64-apple-darwin"
|
||||||
MANPAGE_DIR="./$NAME.1"
|
MANPAGE_DIR="./$NAME.1"
|
||||||
|
BIN_OUTPUT_DIR="./ghbinaries"
|
||||||
|
|
||||||
|
rm -rf "$BIN_OUTPUT_DIR"
|
||||||
|
mkdir -p "$BIN_OUTPUT_DIR"
|
||||||
|
|
||||||
# Linux build
|
# Linux build
|
||||||
echo "Building Linux artifact."
|
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"
|
echo "Linux artifact build: SUCCESS"
|
||||||
cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux"
|
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
|
else
|
||||||
echo "Linux artifact build: FAILED"
|
echo "Linux artifact build: FAILED"
|
||||||
fi
|
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
|
# Windows build
|
||||||
echo "Building Windows artifact."
|
echo "Building Windows artifact."
|
||||||
if cross build -q --release --target="$WIN_TARGET"; then
|
if cross build -q --release --target="$WIN_TARGET"; then
|
||||||
echo "Windows artifact build: SUCCESS"
|
echo "Windows artifact build: SUCCESS"
|
||||||
cp "target/$WIN_TARGET/release/$NAME.exe" "target/$WIN_TARGET/release/$NAME-windows.exe"
|
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
|
else
|
||||||
echo "Windows artifact build: FAILED"
|
echo "Windows artifact build: FAILED"
|
||||||
fi
|
fi
|
||||||
|
|
@ -32,6 +54,9 @@ echo "Building ARMv7 artifact."
|
||||||
if cross build -q --release --target="$ARMV7_TARGET"; then
|
if cross build -q --release --target="$ARMV7_TARGET"; then
|
||||||
echo "ARMv7 artifact build: SUCCESS"
|
echo "ARMv7 artifact build: SUCCESS"
|
||||||
cp "target/$ARMV7_TARGET/release/$NAME" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
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
|
else
|
||||||
echo "ARMv7 artifact build: FAILED"
|
echo "ARMv7 artifact build: FAILED"
|
||||||
fi
|
fi
|
||||||
|
|
@ -41,18 +66,24 @@ echo "Building Aarch64 artifact."
|
||||||
if cross build -q --release --target="$AARCH_TARGET"; then
|
if cross build -q --release --target="$AARCH_TARGET"; then
|
||||||
echo "Aarch64 artifact build: SUCCESS"
|
echo "Aarch64 artifact build: SUCCESS"
|
||||||
cp "target/$AARCH_TARGET/release/$NAME" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
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
|
else
|
||||||
echo "Aarch64 artifact build: FAILED"
|
echo "Aarch64 artifact build: FAILED"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Mac OS build
|
# # Mac OS build
|
||||||
echo "Building OSX artifact."
|
# echo "Building OSX artifact."
|
||||||
if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
|
# if CC=o64-clang CXX=o64-clang++ LIBZ_SYS_STATIC=1 cargo build -q --release --target="$OSX_TARGET"; then
|
||||||
echo "OSX artifact build: SUCCESS"
|
# echo "OSX artifact build: SUCCESS"
|
||||||
cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
|
# cp "target/$OSX_TARGET/release/$NAME" "target/$OSX_TARGET/release/$NAME-osx"
|
||||||
else
|
# strip "target/$OSX_TARGET/release/$NAME-osx"
|
||||||
echo "OSX artifact build: FAILED"
|
# sha512sum "target/$OSX_TARGET/release/$NAME-osx" >"target/$OSX_TARGET/release/$NAME-osx.sha512"
|
||||||
fi
|
# zip -q "$BIN_OUTPUT_DIR/$NAME-osx.zip" "target/$OSX_TARGET/release/$NAME-osx"
|
||||||
|
# else
|
||||||
|
# echo "OSX artifact build: FAILED"
|
||||||
|
# fi
|
||||||
|
|
||||||
echo "Creating manpage..."
|
echo "Creating manpage..."
|
||||||
if command -v help2man >/dev/null; then
|
if command -v help2man >/dev/null; then
|
||||||
|
|
@ -64,19 +95,3 @@ if command -v help2man >/dev/null; then
|
||||||
else
|
else
|
||||||
echo "Please install the help2man package."
|
echo "Please install the help2man package."
|
||||||
fi
|
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.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||||
.TH RUSOLVER "1" "January 2022" "Rusolver 0.8.0" "User Commands"
|
.TH RUSOLVER "1" "August 2025" "rusolver 0.10.2" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
Rusolver \- manual page for Rusolver 0.8.0
|
rusolver \- manual page for rusolver 0.10.2
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B rusolver
|
||||||
|
[\fI\,OPTIONS\/\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Rusolver 0.8.0
|
|
||||||
Eduard Tolosa <edu4rdshl@protonmail.com>
|
|
||||||
Fast DNS resolver written in Rust.
|
Fast DNS resolver written in Rust.
|
||||||
.SS "USAGE:"
|
.SH OPTIONS
|
||||||
.IP
|
|
||||||
rusolver [FLAGS] [OPTIONS]
|
|
||||||
.SS "FLAGS:"
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-t\fR, \fB\-\-threads\fR <THREADS>
|
||||||
Prints help information
|
Number of threads. Default: 100 [default: 100]
|
||||||
.TP
|
.TP
|
||||||
\fB\-i\fR, \fB\-\-ip\fR
|
\fB\-\-retries\fR <RETRIES>
|
||||||
Show the discovered IP addresses. Default: false
|
Number of retries after lookup failure before giving up. Defaults to 0 [default: 0]
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-no\-verify\fR
|
\fB\-d\fR, \fB\-\-domain\fR <DOMAIN>
|
||||||
Disables the double verification algorithm for valid subdomains \fB\-NOT\fR RECOMMENDED\-. Default: false
|
Target domain. When it's specified, a wordlist can be used from stdin for bruteforcing.
|
||||||
.TP
|
.TP
|
||||||
\fB\-q\fR, \fB\-\-quiet\fR
|
\fB\-r\fR, \fB\-\-resolvers\fR <RESOLVERS>
|
||||||
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>
|
|
||||||
File with DNS ips.
|
File with DNS ips.
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-retries\fR <retries>
|
\fB\-\-timeout\fR <TIMEOUT>
|
||||||
Number of retries after lookup failure before giving up. Defaults to 0
|
Timeout in seconds. Default: 3 [default: 3]
|
||||||
.TP
|
.TP
|
||||||
\fB\-t\fR, \fB\-\-threads\fR <threads>
|
\fB\-i\fR, \fB\-\-ip\fR
|
||||||
Number of threads. Default: 100
|
Display the record data.
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-timeout\fR <timeout>
|
\fB\-e\fR, \fB\-\-enable\-double\-check\fR
|
||||||
Timeout in seconds. Default: 1
|
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"
|
.SH "SEE ALSO"
|
||||||
The full documentation for
|
The full documentation for
|
||||||
.B Rusolver
|
.B rusolver
|
||||||
is maintained as a Texinfo manual. If the
|
is maintained as a Texinfo manual. If the
|
||||||
.B info
|
.B info
|
||||||
and
|
and
|
||||||
.B Rusolver
|
.B rusolver
|
||||||
programs are properly installed at your site, the command
|
programs are properly installed at your site, the command
|
||||||
.IP
|
.IP
|
||||||
.B info Rusolver
|
.B info rusolver
|
||||||
.PP
|
.PP
|
||||||
should give you access to the complete manual.
|
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;
|
||||||
275
src/main.rs
275
src/main.rs
|
|
@ -1,105 +1,24 @@
|
||||||
use {
|
use {
|
||||||
clap::{value_t, App, Arg},
|
clap::Parser,
|
||||||
futures::stream::{self, StreamExt},
|
rusolver::{args::Args, dnslib, structs, utils},
|
||||||
rand::{distributions::Alphanumeric, thread_rng as rng, Rng},
|
std::collections::HashSet,
|
||||||
std::{
|
|
||||||
collections::HashSet,
|
|
||||||
net::{Ipv4Addr, SocketAddr},
|
|
||||||
},
|
|
||||||
tokio::{
|
tokio::{
|
||||||
self,
|
self,
|
||||||
fs::File,
|
|
||||||
io::{self, AsyncReadExt},
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Eval args
|
// Eval args
|
||||||
let matches = App::new("Rusolver")
|
let args = Args::parse();
|
||||||
.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;
|
|
||||||
|
|
||||||
// Resolver opts
|
// Resolver opts
|
||||||
let options = ResolverOpts {
|
let options = utils::return_resolver_opts(args.timeout, args.retries);
|
||||||
timeout: std::time::Duration::from_secs(timeout),
|
|
||||||
attempts: retries,
|
|
||||||
ip_strategy: LookupIpStrategy::Ipv4Only,
|
|
||||||
num_concurrent_reqs: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let built_in_nameservers: HashSet<String> = vec![
|
let built_in_nameservers: HashSet<String> = vec![
|
||||||
// Cloudflare
|
// Cloudflare
|
||||||
|
|
@ -117,29 +36,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Verisign
|
// Verisign
|
||||||
"64.6.64.6:53",
|
"64.6.64.6:53",
|
||||||
"64.6.65.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()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(ToString::to_string)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Create resolvers
|
// Create resolvers
|
||||||
let mut nameserver_ips;
|
let mut nameserver_ips;
|
||||||
|
|
||||||
if custom_resolvers {
|
if args.resolvers.is_some() {
|
||||||
nameserver_ips =
|
nameserver_ips = utils::return_file_lines(&args.resolvers.unwrap()).await;
|
||||||
return_file_lines(value_t!(matches.value_of("resolvers"), String).unwrap()).await;
|
|
||||||
nameserver_ips.retain(|ip| !ip.is_empty());
|
nameserver_ips.retain(|ip| !ip.is_empty());
|
||||||
} else {
|
} else {
|
||||||
nameserver_ips = built_in_nameservers.clone();
|
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();
|
let mut wildcard_ips = HashSet::new();
|
||||||
|
|
||||||
// Read stdin
|
// Read stdin
|
||||||
|
|
@ -147,156 +60,30 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut stdin = io::stdin();
|
let mut stdin = io::stdin();
|
||||||
stdin.read_to_string(&mut buffer).await?;
|
stdin.read_to_string(&mut buffer).await?;
|
||||||
|
|
||||||
let hosts: Vec<String> = if matches.is_present("domain") {
|
let hosts: HashSet<String> = if args.domain.is_some() {
|
||||||
let domain = value_t!(matches, "domain", String).unwrap();
|
let domain = args.domain.unwrap();
|
||||||
wildcard_ips = detect_wildcards(&domain, &trustable_resolver, quiet_flag).await;
|
wildcard_ips =
|
||||||
|
utils::detect_wildcards(&domain, &trustable_resolvers, args.quiet_flag).await;
|
||||||
buffer
|
buffer
|
||||||
.lines()
|
.lines()
|
||||||
.map(|word| format!("{}.{}", word, domain))
|
.map(|word| format!("{word}.{domain}"))
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
buffer.lines().map(str::to_owned).collect()
|
buffer.lines().map(str::to_owned).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
stream::iter(hosts)
|
let options = structs::LibOptions {
|
||||||
.map(|host| {
|
hosts,
|
||||||
let resolver_fut = resolvers.ipv4_lookup(host.trim_end_matches('.').to_owned() + ".");
|
resolvers,
|
||||||
let trustable_resolver_fut =
|
trustable_resolvers,
|
||||||
trustable_resolver.ipv4_lookup(host.trim_end_matches('.').to_owned() + ".");
|
wildcard_ips,
|
||||||
let wildcard_ips = wildcard_ips.clone();
|
enable_double_check: args.enable_double_check,
|
||||||
|
threads: args.threads,
|
||||||
|
show_ip_address: args.ip,
|
||||||
|
quiet_flag: args.quiet_flag,
|
||||||
|
};
|
||||||
|
|
||||||
async move {
|
dnslib::return_hosts_data(&options).await;
|
||||||
let mut ips = HashSet::new();
|
|
||||||
if let Ok(ip) = resolver_fut.await {
|
|
||||||
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;
|
|
||||||
|
|
||||||
Ok(())
|
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