From 74a2b3f89dfb9e5aea16e5b79bbbf79f7b120e04 Mon Sep 17 00:00:00 2001 From: wyhaya Date: Sat, 24 Aug 2019 15:19:48 +0800 Subject: [PATCH] add project files --- .gitignore | 4 + .travis.yml | 67 +++++ Cargo.lock | 638 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 25 ++ Dockerfile | 14 + LICENSE | 21 ++ README.md | 91 ++++++ src/config.rs | 216 ++++++++++++++ src/lib.rs | 769 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 348 +++++++++++++++++++++++ src/watch.rs | 46 +++ 11 files changed, 2239 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/config.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/watch.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c83a792 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.idea/ +target/ +todo \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6a3a2d7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ + +language: rust +services: docker +sudo: required + +env: + global: + - CRATE_NAME=updns + +matrix: + include: + + - env: TARGET=linux + os: linux + + - env: TARGET=osx + os: osx + + - env: TARGET=windows + os: windows + +before_install: + - set -e + - rustup default nightly + - rustup component add rustfmt + +script: + - cargo fmt --all -- --check + - cargo test + - cargo build --release + +after_script: set +e + +before_deploy: + - cd ./target/release/ + - test -r $CRATE_NAME && zip $CRATE_NAME-$TRAVIS_TAG-$TARGET.zip $CRATE_NAME || mv $CRATE_NAME.exe $CRATE_NAME-$TRAVIS_TAG-$TARGET.exe + - cd ../../ + +deploy: + + - provider: releases + api_key: + secure: $GITHUB_TOKEN + file_glob: true + file: ./target/release/$CRATE_NAME-$TRAVIS_TAG-$TARGET.* + skip_cleanup: true + on: + tags: true + + - provider: cargo + token: $CARGO_TOKEN + on: + condition: $TARGET = linux + tags: true + +cache: cargo +before_cache: + - chmod -R a+r $HOME/.cargo + +branches: + only: + - /^v\d+\.\d+\.\d+.*$/ + - master + +notifications: + email: + on_success: never \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dcad2b1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,638 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ace" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "aho-corasick" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayref" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "async-std" +version = "0.99.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-timer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "async-task" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "blake2b_simd" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crossbeam-channel" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dirs-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-channel-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-core-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-executor-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-io-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-executor-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-sink-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-timer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-util-preview" +version = "0.3.0-alpha.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mio" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_users" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rust-argon2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "updns" +version = "0.0.1" +dependencies = [ + "ace 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "async-std 0.99.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum ace 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e021d9548a20c4e74f88b5cff290ba225565e3404afcffdc6b275395128a140" +"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" +"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" +"checksum async-std 0.99.4 (registry+https://github.com/rust-lang/crates.io-index)" = "95dbe66a9f8c59a70277214f98d39f25fe1f36f20f6e8412a8b33af0272a2c79" +"checksum async-task 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de6bd58f7b9cc49032559422595c81cbfcf04db2f2133592f70af19e258a1ced" +"checksum backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "1371048253fa3bac6704bfd6bbfc922ee9bdcee8881330d40f308b81cc5adc55" +"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" +"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" +"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures-channel-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "f477fd0292c4a4ae77044454e7f2b413207942ad405f759bb0b4698b7ace5b12" +"checksum futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2f26f774b81b3847dcda0c81bd4b6313acfb4f69e5a0390c7cb12c058953e9" +"checksum futures-executor-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "80705612926df8a1bc05f0057e77460e29318801f988bf7d803a734cf54e7528" +"checksum futures-io-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "ee7de0c1c9ed23f9457b0437fec7663ce64d9cc3c906597e714e529377b5ddd1" +"checksum futures-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "efa8f90c4fb2328e381f8adfd4255b4a2b696f77d1c63a3dee6700b564c4e4b5" +"checksum futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "e9b65a2481863d1b78e094a07e9c0eed458cc7dc6e72b22b7138b8a67d924859" +"checksum futures-timer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f9eb554aa23143abc64ec4d0016f038caf53bb7cbc3d91490835c54edc96550" +"checksum futures-util-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "7df53daff1e98cc024bf2720f3ceb0414d96fbb0a94f3cad3a5c3bf3be1d261c" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" +"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" +"checksum regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88c3d9193984285d544df4a30c23a4e62ead42edf70a4452ceb76dac1ce05c26" +"checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f" +"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..044078c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "updns" +version = "0.0.1" +edition = "2018" + +authors = ["wyhaya "] +description = "DNS proxy tool" +homepage = "https://github.com/wyhaya/updns" +repository = "https://github.com/wyhaya/updns.git" +license = "MIT" +readme = "README.md" + +keywords = [ + "dns", + "dns-server", + "dns-proxy", + "udp" +] + +[dependencies] +ace = "0.0.2" +async-std = "0.99.4" +dirs = "2.0.2" +lazy_static = "1.3.0" +regex = "1.2.1" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d62349 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ + + +FROM rustlang/rust:nightly as builder +WORKDIR /root +COPY . /root +RUN cargo build --release + +FROM ubuntu +EXPOSE 53/udp +WORKDIR /root +COPY --from=builder ./root/target/release/updns . +CMD ["./updns"] + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd03778 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) wyhaya + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9985991 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ + + +# updns + +[![Build Status](https://img.shields.io/travis/wyhaya/updns.svg?style=flat-square)](https://travis-ci.org/wyhaya/updns) +[![Crates.io](https://img.shields.io/crates/v/updns.svg?style=flat-square)](https://crates.io/crates/updns) +[![Crates.io](https://img.shields.io/crates/l/updns.svg?style=flat-square)](https://github.com/wyhaya/updns/blob/master/LICENSE) + +--- + +updns is a simple DNS proxy server developed using `Rust`. You can intercept any domain name and return the ip you need. + +## Install + +[Download](https://github.com/wyhaya/updns/releases) the binary from the release page + +Or use `cargo` to install + +```bash +cargo install updns +``` + +## Start to use + +```bash +updns +# or +updns -c /your/hosts +``` + +You may use `sudo` to run this command because you will use the `53` port, make sure you have sufficient permissions. + +Now change your local DNS server to `127.0.0.1` 🚀 + +### Command + +``` +Usage: + updns [COMMAND] [OPTION] + +Command: + add Add a DNS record + rm Remove a DNS record + ls Print all configured DNS records + config Call vim to edit the configuration file + path Print related directories + help Print help information + version Print version information + +Option: + -c Specify a config file +``` + +## Running in docker + +Build docker image +```bash +docker build -t updns . +``` + +Start up +```bash +docker run -d --name updns -p 53:53/udp --restart always updns +``` + +## Config + +You can use `updns config` command and then call `vim` quick edit, or use `updns path` find the updns's installation directory and edit the `.updns` file + +You can specify standard domains, or utilize [regular expressions](https://rustexp.lpil.uk "rustexp") for dynamic matching, +You can update the config file at any time, updns will listen for file changes + +```ini +bind 0.0.0.0:53 # Binding address +proxy 8.8.8.8:53 # Proxy address + +# Domain matching +google.com 1.1.1.1 +^(\w+.)?go+gle.com$ 2.2.2.2 + +# Import from other file +import /other/hosts +``` + +## Reference + +[Building a DNS server in Rust](https://github.com/EmilHernvall/dnsguide) + +## License + +[MIT](./LICENSE) license diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..481dd77 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,216 @@ +use regex::Regex; +use std::fs::File; +use std::io; +use std::io::{Read, Write}; +use std::net::{IpAddr, SocketAddr}; +use std::path::Path; +use std::slice::Iter; + +lazy_static! { + static ref REG_IGNORE: Regex = Regex::new(r#"^\s*(#.*)?$"#).unwrap(); + static ref REG_BIND: Regex = Regex::new(r#"^\s*bind\s+(?P[^\s#]+)"#).unwrap(); + static ref REG_PROXY: Regex = Regex::new(r#"^\s*proxy\s+(?P[^\s#]+)"#).unwrap(); + // todo + // The path will also contain '#' and ' ' + static ref REG_IMPORT: Regex = Regex::new(r#"\s*import\s+(?P(/.*))"#).unwrap(); + static ref REG_DOMAIN_IP: Regex = Regex::new(r#"^\s*(?P[^\s#]+)\s+(?P[^\s#]+)"#).unwrap(); +} + +fn cap_socket_addr(reg: &Regex, text: &str) -> Option> { + if let Some(cap) = reg.captures(text) { + return match cap.name("val") { + Some(m) => match m.as_str().parse() { + Ok(addr) => Some(Ok(addr)), + Err(_) => Some(Err(InvalidType::SocketAddr)), + }, + None => Some(Err(InvalidType::SocketAddr)), + }; + } + None +} + +fn cap_ip_addr(reg: &Regex, text: &str) -> Option> { + if let Some(cap) = reg.captures(text) { + if let (Some(val1), Some(val2)) = (cap.name("val1"), cap.name("val2")) { + let (val1, val2) = (val1.as_str(), val2.as_str()); + + if let Ok(ip) = val1.parse() { + return match Regex::new(val2) { + Ok(reg) => Some(Ok((reg, ip))), + Err(_) => Some(Err(InvalidType::Regex)), + }; + } else { + let ip = match val2.parse() { + Ok(ip) => ip, + Err(_) => return Some(Err(InvalidType::IpAddr)), + }; + + let reg = match Regex::new(val1) { + Ok(reg) => reg, + Err(_) => return Some(Err(InvalidType::Regex)), + }; + + return Some(Ok((reg, ip))); + } + } + + return Some(Err(InvalidType::Other)); + } + None +} + +#[derive(Debug)] +pub struct Config { + file: File, + content: String, +} + +#[derive(Debug)] +pub struct Invalid { + pub line: usize, + pub source: String, + pub err: InvalidType, +} +#[derive(Debug)] +pub enum InvalidType { + Regex, + SocketAddr, + IpAddr, + Other, +} + +impl Config { + pub fn new>(path: P) -> io::Result { + let mut file = std::fs::OpenOptions::new() + .read(true) + .append(true) + .create(true) + .open(path)?; + + let mut content = String::new(); + file.read_to_string(&mut content)?; + + Ok(Config { file, content }) + } + + pub fn add(&mut self, domain: &str, ip: &str) -> std::io::Result<()> { + if self.content.ends_with("\n") { + writeln!(self.file, "{} {}", domain, ip) + } else { + writeln!(self.file, "\n{} {}", domain, ip) + } + } + + pub fn parse(&mut self) -> io::Result<(Vec, Vec, Hosts, Vec)> { + let (mut hosts, mut binds, mut proxys, mut errors) = + (Hosts::new(), Vec::new(), Vec::new(), Vec::new()); + + for (n, line) in self.content.lines().enumerate() { + // ignore + if REG_IGNORE.is_match(&line) { + continue; + } + + // bind + if let Some(addr) = cap_socket_addr(®_BIND, &line) { + match addr { + Ok(addr) => binds.push(addr), + Err(err) => { + errors.push(Invalid { + line: n + 1, + source: line.to_string(), + err, + }); + } + } + continue; + } + + // proxy + if let Some(addr) = cap_socket_addr(®_PROXY, &line) { + match addr { + Ok(addr) => proxys.push(addr), + Err(err) => { + errors.push(Invalid { + line: n + 1, + source: line.to_string(), + err, + }); + } + } + continue; + } + + // import + if let Some(cap) = REG_IMPORT.captures(&line) { + if let Some(m) = cap.name("val") { + let (b, p, h, e) = Config::new(m.as_str())?.parse()?; + binds.extend(b); + proxys.extend(p); + hosts.extend(h); + errors.extend(e); + } else { + // todo + } + continue; + } + + // host + if let Some(d) = cap_ip_addr(®_DOMAIN_IP, &line) { + match d { + Ok((domain, ip)) => hosts.push(domain, ip), + Err(err) => { + errors.push(Invalid { + line: n + 1, + source: line.to_string(), + err, + }); + } + } + continue; + } + + errors.push(Invalid { + line: n + 1, + source: line.to_string(), + err: InvalidType::Other, + }); + } + + Ok((binds, proxys, hosts, errors)) + } +} + +#[derive(Debug)] +pub struct Hosts { + list: Vec<(Regex, IpAddr)>, +} + +impl Hosts { + pub fn new() -> Hosts { + Hosts { list: Vec::new() } + } + + fn push(&mut self, domain: Regex, ip: IpAddr) { + self.list.push((domain, ip)); + } + + fn extend(&mut self, hosts: Hosts) { + for item in hosts.list { + self.list.push(item); + } + } + + pub fn iter(&mut self) -> Iter<(Regex, IpAddr)> { + self.list.iter() + } + + pub fn get(&self, domain: &str) -> Option<&IpAddr> { + for (reg, ip) in &self.list { + if reg.is_match(domain) { + return Some(ip); + } + } + None + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..05940c3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,769 @@ +// From : EmilHernvall/dnsguide +// GitHub : https://github.com/EmilHernvall/dnsguide + +#![allow(dead_code)] + +use std::io::{Error, ErrorKind}; +use std::io::{Read, Result}; + +use std::net::{Ipv4Addr, Ipv6Addr}; + +pub struct BytePacketBuffer { + pub buf: [u8; 512], + pub pos: usize, +} + +impl BytePacketBuffer { + pub fn new() -> BytePacketBuffer { + BytePacketBuffer { + buf: [0; 512], + pos: 0, + } + } + + pub fn pos(&self) -> usize { + self.pos + } + + fn step(&mut self, steps: usize) -> Result<()> { + self.pos += steps; + + Ok(()) + } + + fn seek(&mut self, pos: usize) -> Result<()> { + self.pos = pos; + + Ok(()) + } + + fn read(&mut self) -> Result { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + let res = self.buf[self.pos]; + self.pos += 1; + + Ok(res) + } + + fn get(&mut self, pos: usize) -> Result { + if pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(self.buf[pos]) + } + + pub fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + if start + len >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(&self.buf[start..start + len as usize]) + } + + fn read_u16(&mut self) -> Result { + let res = ((self.read()? as u16) << 8) | (self.read()? as u16); + + Ok(res) + } + + fn read_u32(&mut self) -> Result { + let res = ((self.read()? as u32) << 24) + | ((self.read()? as u32) << 16) + | ((self.read()? as u32) << 8) + | ((self.read()? as u32) << 0); + + Ok(res) + } + + fn read_qname(&mut self, outstr: &mut String) -> Result<()> { + let mut pos = self.pos(); + let mut jumped = false; + + let mut delim = ""; + loop { + let len = self.get(pos)?; + + // A two byte sequence, where the two highest bits of the first byte is + // set, represents a offset relative to the start of the buffer. We + // handle this by jumping to the offset, setting a flag to indicate + // that we shouldn't update the shared buffer position once done. + if (len & 0xC0) == 0xC0 { + // When a jump is performed, we only modify the shared buffer + // position once, and avoid making the change later on. + if !jumped { + self.seek(pos + 2)?; + } + + let b2 = self.get(pos + 1)? as u16; + let offset = (((len as u16) ^ 0xC0) << 8) | b2; + pos = offset as usize; + jumped = true; + continue; + } + + pos += 1; + + // Names are terminated by an empty label of length 0 + if len == 0 { + break; + } + + outstr.push_str(delim); + + let str_buffer = self.get_range(pos, len as usize)?; + outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + + delim = "."; + + pos += len as usize; + } + + if !jumped { + self.seek(pos)?; + } + + Ok(()) + } + + fn write(&mut self, val: u8) -> Result<()> { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + self.buf[self.pos] = val; + self.pos += 1; + Ok(()) + } + + fn write_u8(&mut self, val: u8) -> Result<()> { + self.write(val)?; + + Ok(()) + } + + fn write_u16(&mut self, val: u16) -> Result<()> { + self.write((val >> 8) as u8)?; + self.write((val & 0xFF) as u8)?; + + Ok(()) + } + + fn write_u32(&mut self, val: u32) -> Result<()> { + self.write(((val >> 24) & 0xFF) as u8)?; + self.write(((val >> 16) & 0xFF) as u8)?; + self.write(((val >> 8) & 0xFF) as u8)?; + self.write(((val >> 0) & 0xFF) as u8)?; + + Ok(()) + } + + fn write_qname(&mut self, qname: &str) -> Result<()> { + let split_str = qname.split('.').collect::>(); + + for label in split_str { + let len = label.len(); + if len > 0x34 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Single label exceeds 63 characters of length", + )); + } + + self.write_u8(len as u8)?; + for b in label.as_bytes() { + self.write_u8(*b)?; + } + } + + self.write_u8(0)?; + + Ok(()) + } + + fn set(&mut self, pos: usize, val: u8) -> Result<()> { + self.buf[pos] = val; + + Ok(()) + } + + fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { + self.set(pos, (val >> 8) as u8)?; + self.set(pos + 1, (val & 0xFF) as u8)?; + + Ok(()) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ResultCode { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5, +} + +impl ResultCode { + pub fn from_num(num: u8) -> ResultCode { + match num { + 1 => ResultCode::FORMERR, + 2 => ResultCode::SERVFAIL, + 3 => ResultCode::NXDOMAIN, + 4 => ResultCode::NOTIMP, + 5 => ResultCode::REFUSED, + 0 | _ => ResultCode::NOERROR, + } + } +} + +#[derive(Clone, Debug)] +pub struct DnsHeader { + pub id: u16, // 16 bits + + pub recursion_desired: bool, // 1 bit + pub truncated_message: bool, // 1 bit + pub authoritative_answer: bool, // 1 bit + pub opcode: u8, // 4 bits + pub response: bool, // 1 bit + + pub rescode: ResultCode, // 4 bits + pub checking_disabled: bool, // 1 bit + pub authed_data: bool, // 1 bit + pub z: bool, // 1 bit + pub recursion_available: bool, // 1 bit + + pub questions: u16, // 16 bits + pub answers: u16, // 16 bits + pub authoritative_entries: u16, // 16 bits + pub resource_entries: u16, // 16 bits +} + +impl DnsHeader { + pub fn new() -> DnsHeader { + DnsHeader { + id: 0, + + recursion_desired: false, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: false, + + rescode: ResultCode::NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: false, + + questions: 0, + answers: 0, + authoritative_entries: 0, + resource_entries: 0, + } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + self.id = buffer.read_u16()?; + + let flags = buffer.read_u16()?; + let a = (flags >> 8) as u8; + let b = (flags & 0xFF) as u8; + self.recursion_desired = (a & (1 << 0)) > 0; + self.truncated_message = (a & (1 << 1)) > 0; + self.authoritative_answer = (a & (1 << 2)) > 0; + self.opcode = (a >> 3) & 0x0F; + self.response = (a & (1 << 7)) > 0; + + self.rescode = ResultCode::from_num(b & 0x0F); + self.checking_disabled = (b & (1 << 4)) > 0; + self.authed_data = (b & (1 << 5)) > 0; + self.z = (b & (1 << 6)) > 0; + self.recursion_available = (b & (1 << 7)) > 0; + + self.questions = buffer.read_u16()?; + self.answers = buffer.read_u16()?; + self.authoritative_entries = buffer.read_u16()?; + self.resource_entries = buffer.read_u16()?; + + // Return the constant header size + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + buffer.write_u16(self.id)?; + + (buffer.write_u8( + (self.recursion_desired as u8) + | ((self.truncated_message as u8) << 1) + | ((self.authoritative_answer as u8) << 2) + | (self.opcode << 3) + | ((self.response as u8) << 7) as u8, + ))?; + + (buffer.write_u8( + (self.rescode.clone() as u8) + | ((self.checking_disabled as u8) << 4) + | ((self.authed_data as u8) << 5) + | ((self.z as u8) << 6) + | ((self.recursion_available as u8) << 7), + ))?; + + buffer.write_u16(self.questions)?; + buffer.write_u16(self.answers)?; + buffer.write_u16(self.authoritative_entries)?; + buffer.write_u16(self.resource_entries)?; + + Ok(()) + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 + NS, // 2 + CNAME, // 5 + MX, // 15 + AAAA, // 28 +} + +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + QueryType::A => 1, + QueryType::NS => 2, + QueryType::CNAME => 5, + QueryType::MX => 15, + QueryType::AAAA => 28, + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + 1 => QueryType::A, + 2 => QueryType::NS, + 5 => QueryType::CNAME, + 15 => QueryType::MX, + 28 => QueryType::AAAA, + _ => QueryType::UNKNOWN(num), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DnsQuestion { + pub name: String, + pub qtype: QueryType, +} + +impl DnsQuestion { + pub fn new(name: String, qtype: QueryType) -> DnsQuestion { + DnsQuestion { + name: name, + qtype: qtype, + } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + buffer.read_qname(&mut self.name)?; + self.qtype = QueryType::from_num(buffer.read_u16()?); // qtype + let _ = buffer.read_u16()?; // class + + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + buffer.write_qname(&self.name)?; + + let typenum = self.qtype.to_num(); + buffer.write_u16(typenum)?; + buffer.write_u16(1)?; + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[allow(dead_code)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32, + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32, + }, // 1 + NS { + domain: String, + host: String, + ttl: u32, + }, // 2 + CNAME { + domain: String, + host: String, + ttl: u32, + }, // 5 + MX { + domain: String, + priority: u16, + host: String, + ttl: u32, + }, // 15 + AAAA { + domain: String, + addr: Ipv6Addr, + ttl: u32, + }, // 28 +} + +impl DnsRecord { + pub fn read(buffer: &mut BytePacketBuffer) -> Result { + let mut domain = String::new(); + buffer.read_qname(&mut domain)?; + + let qtype_num = buffer.read_u16()?; + let qtype = QueryType::from_num(qtype_num); + let _ = buffer.read_u16()?; + let ttl = buffer.read_u32()?; + let data_len = buffer.read_u16()?; + + match qtype { + QueryType::A => { + let raw_addr = buffer.read_u32()?; + let addr = Ipv4Addr::new( + ((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + ((raw_addr >> 0) & 0xFF) as u8, + ); + + Ok(DnsRecord::A { + domain: domain, + addr: addr, + ttl: ttl, + }) + } + QueryType::AAAA => { + let raw_addr1 = buffer.read_u32()?; + let raw_addr2 = buffer.read_u32()?; + let raw_addr3 = buffer.read_u32()?; + let raw_addr4 = buffer.read_u32()?; + let addr = Ipv6Addr::new( + ((raw_addr1 >> 16) & 0xFFFF) as u16, + ((raw_addr1 >> 0) & 0xFFFF) as u16, + ((raw_addr2 >> 16) & 0xFFFF) as u16, + ((raw_addr2 >> 0) & 0xFFFF) as u16, + ((raw_addr3 >> 16) & 0xFFFF) as u16, + ((raw_addr3 >> 0) & 0xFFFF) as u16, + ((raw_addr4 >> 16) & 0xFFFF) as u16, + ((raw_addr4 >> 0) & 0xFFFF) as u16, + ); + + Ok(DnsRecord::AAAA { + domain: domain, + addr: addr, + ttl: ttl, + }) + } + QueryType::NS => { + let mut ns = String::new(); + buffer.read_qname(&mut ns)?; + + Ok(DnsRecord::NS { + domain: domain, + host: ns, + ttl: ttl, + }) + } + QueryType::CNAME => { + let mut cname = String::new(); + buffer.read_qname(&mut cname)?; + + Ok(DnsRecord::CNAME { + domain: domain, + host: cname, + ttl: ttl, + }) + } + QueryType::MX => { + let priority = buffer.read_u16()?; + let mut mx = String::new(); + buffer.read_qname(&mut mx)?; + + Ok(DnsRecord::MX { + domain: domain, + priority: priority, + host: mx, + ttl: ttl, + }) + } + QueryType::UNKNOWN(_) => { + buffer.step(data_len as usize)?; + + Ok(DnsRecord::UNKNOWN { + domain: domain, + qtype: qtype_num, + data_len: data_len, + ttl: ttl, + }) + } + } + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + let start_pos = buffer.pos(); + + match *self { + DnsRecord::A { + ref domain, + ref addr, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::A.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + buffer.write_u16(4)?; + + let octets = addr.octets(); + buffer.write_u8(octets[0])?; + buffer.write_u8(octets[1])?; + buffer.write_u8(octets[2])?; + buffer.write_u8(octets[3])?; + } + DnsRecord::NS { + ref domain, + ref host, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::NS.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_qname(host)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + DnsRecord::CNAME { + ref domain, + ref host, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::CNAME.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_qname(host)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + DnsRecord::MX { + ref domain, + priority, + ref host, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::MX.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_u16(priority)?; + buffer.write_qname(host)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + DnsRecord::AAAA { + ref domain, + ref addr, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::AAAA.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + buffer.write_u16(16)?; + + for octet in &addr.segments() { + buffer.write_u16(*octet)?; + } + } + DnsRecord::UNKNOWN { .. } => { + println!("Skipping record: {:?}", self); + } + } + + Ok(buffer.pos() - start_pos) + } +} + +#[derive(Clone, Debug)] +pub struct DnsPacket { + pub header: DnsHeader, + pub questions: Vec, + pub answers: Vec, + pub authorities: Vec, + pub resources: Vec, +} + +impl DnsPacket { + pub fn new() -> DnsPacket { + DnsPacket { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new(), + } + } + + pub fn from_buffer(buffer: &mut BytePacketBuffer) -> Result { + let mut result = DnsPacket::new(); + result.header.read(buffer)?; + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), QueryType::UNKNOWN(0)); + question.read(buffer)?; + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = DnsRecord::read(buffer)?; + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = DnsRecord::read(buffer)?; + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = DnsRecord::read(buffer)?; + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + self.header.questions = self.questions.len() as u16; + self.header.answers = self.answers.len() as u16; + self.header.authoritative_entries = self.authorities.len() as u16; + self.header.resource_entries = self.resources.len() as u16; + + self.header.write(buffer)?; + + for question in &self.questions { + question.write(buffer)?; + } + for rec in &self.answers { + rec.write(buffer)?; + } + for rec in &self.authorities { + rec.write(buffer)?; + } + for rec in &self.resources { + rec.write(buffer)?; + } + + Ok(()) + } + + pub fn get_random_a(&self) -> Option { + if !self.answers.is_empty() { + let a_record = &self.answers[0]; + if let DnsRecord::A { ref addr, .. } = *a_record { + return Some(addr.to_string()); + } + } + + None + } + + pub fn get_resolved_ns(&self, qname: &str) -> Option { + let mut new_authorities = Vec::new(); + for auth in &self.authorities { + if let DnsRecord::NS { + ref domain, + ref host, + .. + } = *auth + { + if !qname.ends_with(domain) { + continue; + } + + for rsrc in &self.resources { + if let DnsRecord::A { + ref domain, + ref addr, + ttl, + } = *rsrc + { + if domain != host { + continue; + } + + let rec = DnsRecord::A { + domain: host.clone(), + addr: *addr, + ttl: ttl, + }; + + new_authorities.push(rec); + } + } + } + } + + if !new_authorities.is_empty() { + if let DnsRecord::A { addr, .. } = new_authorities[0] { + return Some(addr.to_string()); + } + } + + None + } + + pub fn get_unresolved_ns(&self, qname: &str) -> Option { + let mut new_authorities = Vec::new(); + for auth in &self.authorities { + if let DnsRecord::NS { + ref domain, + ref host, + .. + } = *auth + { + if !qname.ends_with(domain) { + continue; + } + + new_authorities.push(host); + } + } + + if !new_authorities.is_empty() { + return Some(new_authorities[0].clone()); + } + + None + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d0f8aca --- /dev/null +++ b/src/main.rs @@ -0,0 +1,348 @@ +#![feature(const_vec_new)] + +#[macro_use] +extern crate lazy_static; + +mod config; +mod lib; +mod watch; + +use ace::App; +use async_std::io; +use async_std::net::UdpSocket; +use async_std::task; +use config::{Config, Hosts, Invalid, InvalidType}; +use dirs; +use lib::*; +use std::env; +use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; +use std::process::Command; +use std::time::{Duration, Instant}; +use watch::Watch; + +const CONFIG_NAME: &str = ".updns"; +const DEFAULT_BIND: &str = "0.0.0.0:53"; +const DEFAULT_PROXY: [&str; 2] = ["8.8.8.8:53", "114.114.114.114:53"]; +const PROXY_TIMEOUT: u64 = 2000; +const WATCH_INTERVAL: u64 = 3000; +static mut PROXY: Vec = Vec::new(); +static mut HOSTS: Option = None; + +macro_rules! log { + ($($arg:tt)*) => { + println!($($arg)*); + }; +} + +macro_rules! warn { + ($($arg:tt)*) => { + print!("\x1B[{}m{}\x1B[0m", "1;33", "warning: "); + println!($($arg)*); + }; +} + +macro_rules! error { + ($($arg:tt)*) => { + eprint!("\x1B[{}m{}\x1B[0m", "1;31", "error: "); + eprintln!($($arg)*); + }; +} + +macro_rules! exit { + ($($arg:tt)*) => { + { + eprint!("\x1B[{}m{}\x1B[0m", "1;31", "error: "); + eprintln!($($arg)*); + std::process::exit(1) + } + }; +} + +fn main() { + let app = App::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")) + .cmd("add", "Add a DNS record") + .cmd("rm", "Remove a DNS record") + .cmd("ls", "Print all configured DNS records") + .cmd("config", "Call vim to edit the configuration file") + .cmd("path", "Print related directories") + .cmd("help", "Print help information") + .cmd("version", "Print version information") + .opt("-c", "Specify a config file"); + + let config_path = match app.value("-c") { + Some(values) => { + if values.is_empty() { + exit!("'-c' value: [CONFIG]"); + } + PathBuf::from(values[0]) + } + None => match dirs::home_dir() { + Some(p) => p.join(CONFIG_NAME), + None => exit!("Can't get home directory"), + }, + }; + + if let Some(cmd) = app.command() { + match cmd.as_str() { + "add" => { + let values = app.value("add").unwrap_or(vec![]); + if values.len() != 2 { + exit!("'add' value: [DOMAIN] [IP]"); + } + let mut config = match Config::new(&config_path) { + Ok(c) => c, + Err(err) => exit!("Failed to read config file: {:?}\n{:?}", &config_path, err), + }; + if let Err(err) = config.add(&values[0], &values[1]) { + exit!("Add record failed\n{:?}", err); + } + } + "rm" => { + if let Some(value) = app.value("rm") { + if value.is_empty() { + exit!("'rm' value: [DOMAIN | IP]"); + } + } + } + "ls" => { + let (_, _, _, mut hosts) = config_parse(&config_path); + let mut n = 0; + for (reg, _) in hosts.iter() { + if reg.as_str().len() > n { + n = reg.as_str().len(); + } + } + for (domain, ip) in hosts.iter() { + println!("{:domain$} {}", domain.as_str(), ip, domain = n); + } + } + "config" => { + let cmd = Command::new("vim").arg(&config_path).status(); + match cmd { + Ok(status) => { + if status.success() { + config_parse(&config_path); + } else { + warn!("Non-zero state exit\n{:?}", status); + } + } + Err(err) => exit!("Call vim command failed\n{:?}", err), + } + } + "path" => { + let binary = match env::current_exe() { + Ok(p) => p.display().to_string(), + Err(err) => exit!("Failed to get directory\n{:?}", err), + }; + println!("Binary: {}\nConfig: {:?}", binary, config_path); + } + "help" => { + app.help(); + } + "version" => { + app.version(); + } + _ => { + app.error_try("help"); + } + } + return; + } + + let (_, mut binds, proxys, hosts) = config_parse(&config_path); + if binds.is_empty() { + warn!("Will bind the default address '{}'", DEFAULT_BIND); + binds.push(DEFAULT_BIND.parse().unwrap()); + } + if proxys.is_empty() { + warn!( + "Will use the default proxy address '{}'", + DEFAULT_PROXY.join(", ") + ); + } + update_config(proxys, hosts); + + task::spawn(watch_config(config_path)); + task::block_on(run_server(binds)); +} + +fn config_parse(file: &PathBuf) -> (Config, Vec, Vec, Hosts) { + let mut config = match Config::new(file) { + Ok(c) => c, + Err(err) => exit!("Failed to read config file: {:?}\n{:?}", file, err), + }; + + let (binds, proxys, hosts, errors) = match config.parse() { + Ok(d) => d, + Err(err) => exit!("Parsing config file failed\n{:?}", err), + }; + output_invalid(errors); + + (config, binds, proxys, hosts) +} + +fn output_invalid(errors: Vec) { + if !errors.is_empty() { + for invalid in errors { + let msg = match invalid.err { + InvalidType::SocketAddr => "Cannot parse socket addr", + InvalidType::IpAddr => "Cannot parse ip addr", + InvalidType::Regex => "Cannot parse Regular expression", + InvalidType::Other => "Invalid line", + }; + warn!("{}", msg); + log!("Line {}: {}", invalid.line, invalid.source); + } + } +} + +async fn watch_config(p: PathBuf) { + let mut watch = Watch::new(p, WATCH_INTERVAL); + watch + .change(|c| { + log!("Reload the configuration file: {:?}", &c); + if let Ok(mut config) = Config::new(c) { + if let Ok((_, proxy, hosts, errors)) = config.parse() { + update_config(proxy, hosts); + output_invalid(errors); + } + } + }) + .await; +} + +fn update_config(mut proxy: Vec, hosts: Hosts) { + if proxy.is_empty() { + proxy = DEFAULT_PROXY + .iter() + .map(|p| p.parse().unwrap()) + .collect::>(); + } + unsafe { + PROXY = proxy; + HOSTS = Some(hosts); + }; +} + +async fn run_server(binds: Vec) { + let mut tasks = vec![]; + for addr in binds { + let task = task::spawn(async move { + let socket = match UdpSocket::bind(&addr).await { + Ok(socket) => { + log!("Start listening to '{}'", addr); + socket + } + Err(err) => exit!("Binding '{}' failed\n{:?}", addr, err), + }; + loop { + let mut req = BytePacketBuffer::new(); + match socket.recv_from(&mut req.buf).await { + Ok((len, src)) => { + let res = match handle(req, len).await { + Ok(data) => data, + Err(err) => { + error!("Processing request failed\n{:?}", err); + continue; + } + }; + if let Err(err) = socket.send_to(&res, &src).await { + error!("Replying to '{}' failed\n{:?}", &src, err); + } + } + Err(err) => { + error!("Failed to receive message\n{:?}", err); + } + } + } + }); + tasks.push(task); + } + for task in tasks { + task.await; + } +} + +async fn proxy(buf: &[u8]) -> io::Result> { + let proxy = unsafe { &PROXY }; + + for addr in proxy.iter() { + let socket = UdpSocket::bind(("0.0.0.0", 0)).await?; + + let data = io::timeout(Duration::from_millis(PROXY_TIMEOUT), async { + socket.send_to(&buf, addr).await?; + let mut res = [0; 512]; + let len = socket.recv(&mut res).await?; + Ok(res[..len].to_vec()) + }) + .await; + + match data { + Ok(data) => { + return Ok(data); + } + Err(err) => { + error!("Agent request to {}\n{:?}", addr, err); + } + } + } + + Err(io::Error::new( + io::ErrorKind::Other, + "Proxy server failed to proxy request", + )) +} + +fn get_answer(domain: &str, query: QueryType) -> Option { + let hosts = unsafe { HOSTS.as_ref().unwrap() }; + if let Some(ip) = hosts.get(domain) { + match query { + QueryType::A => { + if let IpAddr::V4(addr) = ip { + return Some(DnsRecord::A { + domain: domain.to_string(), + addr: addr.clone(), + ttl: 3600, + }); + } + } + QueryType::AAAA => { + if let IpAddr::V6(addr) = ip { + return Some(DnsRecord::AAAA { + domain: domain.to_string(), + addr: addr.clone(), + ttl: 3600, + }); + } + } + _ => {} + } + } + None +} + +async fn handle(mut req: BytePacketBuffer, len: usize) -> io::Result> { + let mut request = DnsPacket::from_buffer(&mut req)?; + + let query = match request.questions.get(0) { + Some(q) => q, + None => return proxy(&req.buf[..len]).await, + }; + + log!("Query: {} Type: {:?}", query.name, query.qtype); + + if let Some(answer) = get_answer(&query.name, query.qtype) { + request.header.recursion_desired = true; + request.header.recursion_available = true; + request.header.response = true; + request.answers.push(answer); + let mut res_buffer = BytePacketBuffer::new(); + request.write(&mut res_buffer)?; + let len = res_buffer.pos(); + let data = res_buffer.get_range(0, len)?; + Ok(data.to_vec()) + } else { + proxy(&req.buf[..len]).await + } +} diff --git a/src/watch.rs b/src/watch.rs new file mode 100644 index 0000000..ed9d463 --- /dev/null +++ b/src/watch.rs @@ -0,0 +1,46 @@ +use async_std::fs; +use async_std::io; +use async_std::stream; +use async_std::stream::Stream; +use async_std::task; +use std::path::PathBuf; +use std::time::{Duration, SystemTime}; + +pub struct Watch { + path: PathBuf, + interval: u64, +} + +impl Watch { + pub fn new(path: PathBuf, interval: u64) -> Watch { + Watch { interval, path } + } + + async fn modified(&self) -> io::Result { + let file = fs::File::open(&self.path).await?; + let modified = file.metadata().await?.modified()?; + Ok(modified) + } + + pub async fn change(&mut self, func: fn(path: &PathBuf)) { + let mut repeat = stream::repeat(0); + let mut before = match self.modified().await { + Ok(time) => Some(time), + Err(_) => None, + }; + + while let Some(_) = repeat.next().await { + task::sleep(Duration::from_millis(self.interval)).await; + + let after = match self.modified().await { + Ok(time) => Some(time), + Err(_) => None, + }; + + if before != after { + before = after; + func(&self.path); + } + } + } +}