commit 74a2b3f89dfb9e5aea16e5b79bbbf79f7b120e04 Author: wyhaya Date: Sat Aug 24 15:19:48 2019 +0800 add project files 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); + } + } + } +}