10 Commits

Author SHA1 Message Date
wyhaya
14610a7867 Fix App function name 2020-04-07 19:34:01 +08:00
wyhaya
9044f44e10 Use RwLock replace unsafe 2020-04-05 22:30:07 +08:00
wyhaya
7fe89d5aa1 Fix clippy args 2020-03-31 19:49:02 +08:00
wyhaya
6f674173a8 Delete travis build status 2020-03-17 21:33:05 +08:00
wyhaya
21e9c8d83b Delete .travis.yml 2020-03-17 21:15:10 +08:00
wyhaya
2c501b9fe4 Use github action 2020-03-17 21:11:21 +08:00
wyhaya
e92acf8214 Fix try_parse_duration fn 2020-03-17 21:10:36 +08:00
wyhaya
65fd8c7d4d Update doc 2020-01-13 20:57:16 +08:00
wyhaya
e78cf2ba82 Use log crate 2020-01-13 20:55:30 +08:00
wyhaya
d4d796d3b1 optimize domain name matching 2019-12-31 20:07:51 +08:00
10 changed files with 555 additions and 336 deletions

109
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,109 @@
name: Build
on: [push, pull_request]
jobs:
build:
name: Build in ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
env:
NAME: updns
steps:
- uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt, clippy
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --release
- name: Cargo build
uses: actions-rs/cargo@v1
with:
command: build
args: --release
# -------------- Relese --------------
- name: Get release version (windows)
if: startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows')
id: GITHUB_RELEASE
shell: bash
run: echo ::set-output name=TAG::${GITHUB_REF:10}
- name: Package zip (linux)
if: startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'ubuntu')
run: |
cd ./target/release/
zip ${{ env.NAME }}-${GITHUB_REF:10}-linux.zip ${{ env.NAME }}
- name: Package zip (osx)
if: startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'macos')
run: |
cd ./target/release/
zip ${{ env.NAME }}-${GITHUB_REF:10}-osx.zip ${{ env.NAME }}
- name: Package zip (windows)
if: startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'windows')
run: |
cd ./target/release/
Compress-Archive -CompressionLevel Optimal -Force -Path ${{ env.NAME }}.exe -DestinationPath ${{ env.NAME }}-${{ steps.GITHUB_RELEASE.outputs.TAG }}-windows.zip
- name: GitHub release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: ./target/release/*.zip
- name: Cargo publish
uses: actions-rs/cargo@v1
if: startsWith(github.ref, 'refs/tags/') && startsWith(matrix.os, 'ubuntu')
with:
command: publish
args: --token ${{ secrets.CARGO_TOKEN }} -v

View File

@@ -1,62 +0,0 @@
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 component add rustfmt
script:
- cargo fmt --all -- --check
- cargo test --release
- 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
branches:
only:
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
email:
on_success: never

136
Cargo.lock generated
View File

@@ -2,7 +2,7 @@
# It is not intended for manual editing.
[[package]]
name = "ace"
version = "0.0.3"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -151,6 +151,11 @@ dependencies = [
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@@ -172,79 +177,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-channel"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-core"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-executor"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-io"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-macro"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-sink"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-task"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-util"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.4 (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)",
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -361,7 +366,7 @@ name = "proc-macro-hack"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -381,7 +386,7 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.6"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -400,7 +405,7 @@ name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -455,18 +460,18 @@ dependencies = [
[[package]]
name = "regex"
version = "1.3.1"
version = "1.3.6"
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.12 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.12"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -504,7 +509,7 @@ name = "syn"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -522,7 +527,7 @@ dependencies = [
[[package]]
name = "thread_local"
version = "0.3.6"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -540,11 +545,13 @@ dependencies = [
[[package]]
name = "tokio"
version = "0.2.2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -552,14 +559,15 @@ dependencies = [
"mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-macros 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tokio-macros"
version = "0.2.0"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -576,15 +584,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "updns"
version = "0.1.1"
version = "0.1.3"
dependencies = [
"ace 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ace 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -626,7 +635,7 @@ dependencies = [
]
[metadata]
"checksum ace 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42d6302f03bbae8544fb07d9ab94bfbe2a5dbaadc857749d9fad959b55f13069"
"checksum ace 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77c9e271cdb57529662aa91421d9b47ec2a87b92e0f3fcdebb17a3949c65fa4a"
"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"
@@ -646,18 +655,19 @@ dependencies = [
"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 fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"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 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987"
"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86"
"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231"
"checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff"
"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764"
"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
"checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
"checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
@@ -674,7 +684,7 @@ dependencies = [
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
@@ -683,18 +693,18 @@ dependencies = [
"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.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
"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 syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92"
"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 thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum tokio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2e765bf9f550bd9b8a970633ca3b56b8120c4b6c5dcbe26a93744cb02fee4b17"
"checksum tokio-macros 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5795a71419535c6dcecc9b6ca95bdd3c2d6142f7e8343d7beb9923f129aa87e"
"checksum tokio 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ee5a0dd887e37d37390c13ff8ac830f992307fe30a1fff0ab8427af67211ba28"
"checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"

View File

@@ -1,6 +1,6 @@
[package]
name = "updns"
version = "0.1.1"
version = "0.1.3"
edition = "2018"
authors = ["wyhaya <wyhaya@gmail.com>"]
@@ -18,10 +18,11 @@ keywords = [
]
[dependencies]
ace = "0.0.3"
ace = "0.2.0"
dirs = "2.0.2"
futures = "0.3.1"
futures = "0.3.4"
lazy_static = "1.4.0"
regex = "1.3.1"
log = { version = "0.4.8", features = ["max_level_trace", "release_max_level_info"] }
regex = "1.3.6"
time = "0.1.42"
tokio = {version = "0.2.2", features = ["fs", "io-util", "macros", "net", "stream", "time"]}
tokio = { version = "0.2.16", features = ["fs", "io-util", "macros", "net", "stream", "time", "sync"] }

View File

@@ -1,14 +1,13 @@
# updns
[![Build Status](https://img.shields.io/travis/wyhaya/updns.svg?style=flat-square)](https://travis-ci.org/wyhaya/updns)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wyhaya/updns/Build?style=flat-square)](https://github.com/wyhaya/updns/actions)
[![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.
updns is a simple DNS proxy server developed using `Rust`. You can intercept any domain name and return the ip you need
## Install
@@ -20,7 +19,7 @@ Or use `cargo` to install
cargo install updns
```
## Start to use
## Start to use 🚀
```bash
updns
@@ -28,9 +27,7 @@ updns
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` 🚀
You may use `sudo` to run this command because you will use the `53` port
## Running in docker
@@ -53,43 +50,42 @@ Usage:
Command:
add Add a DNS record
ls Print all configured DNS records
config Call vim to edit the configuration file
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
-w Check the interval of the configuration file
-i Check the interval time of the configuration file
format: 1ms, 1s, 1m, 1h, 1d
```
## 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 `config` file
You can use `updns config` command and then call `vim` edit, or find `~/.updns/config` edit
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
You can specify standard domains, or utilize [regular expressions](https://rustexp.lpil.uk "rustexp") for dynamic matching
> Regular expression starts with `~`
```ini
bind 0.0.0.0:53 # Binding address
proxy 8.8.8.8:53 # Proxy address
timeout 2000 # Proxy timeout (ms)
bind 0.0.0.0:53 # Binding address
proxy 8.8.8.8:53 # Proxy address
timeout 2s # Proxy timeout (format: 1ms, 1s, 1m, 1h, 1d)
# Domain matching
example.com 1.1.1.1
*.example.com 2.2.2.2
^\w+\.example\.[a-z]+$ 3.3.3.3
example.com 1.1.1.1
*.example.com 2.2.2.2
~^\w+\.example\.[a-z]+$ 3.3.3.3
# IPv6
test.com ::
# Import from other file
import /other/hosts
```
## Todo
* Dynamically update port bindings
## Reference
[Building a DNS server in Rust](https://github.com/EmilHernvall/dnsguide)

View File

@@ -1,4 +1,7 @@
use crate::matcher::Matcher;
use futures::future::{BoxFuture, FutureExt};
use lazy_static::lazy_static;
use log::error;
use regex::Regex;
use std::{
borrow::Cow,
@@ -6,6 +9,7 @@ use std::{
path::{Path, PathBuf},
result,
slice::Iter,
time::Duration,
};
use tokio::{
fs,
@@ -17,6 +21,37 @@ lazy_static! {
static ref COMMENT_REGEX: Regex = Regex::new("#.*$").unwrap();
}
// Parse time format into Duration
pub fn try_parse_duration(text: &str) -> result::Result<Duration, ()> {
let numbers = "0123456789.".chars().collect::<Vec<char>>();
let i = text
.chars()
.position(|ch| !numbers.contains(&ch))
.ok_or_else(|| ())?;
let time = &text[..i];
let unit = &text[i..];
if time.is_empty() {
return Err(());
}
let n = time.parse::<f64>().map_err(|_| ())?;
let ms = match unit {
"d" => Ok(24_f64 * 60_f64 * 60_f64 * 1000_f64 * n),
"h" => Ok(60_f64 * 60_f64 * 1000_f64 * n),
"m" => Ok(60_f64 * 1000_f64 * n),
"s" => Ok(1000_f64 * n),
"ms" => Ok(n),
_ => Err(()),
}? as u64;
if ms == 0 {
Err(())
} else {
Ok(Duration::from_millis(ms))
}
}
#[derive(Debug)]
pub struct Invalid {
pub line: usize,
@@ -24,6 +59,23 @@ pub struct Invalid {
pub kind: InvalidType,
}
pub trait MultipleInvalid {
fn print(&self);
}
impl MultipleInvalid for Vec<Invalid> {
fn print(&self) {
for invalid in self {
error!(
"[line:{}] {} `{}`",
invalid.line,
invalid.kind.description(),
invalid.source
);
}
}
}
#[derive(Debug)]
pub enum InvalidType {
Regex,
@@ -34,7 +86,7 @@ pub enum InvalidType {
}
impl InvalidType {
pub fn as_str(&self) -> &str {
pub fn description(&self) -> &str {
match self {
InvalidType::SocketAddr => "Cannot parse socket address",
InvalidType::IpAddr => "Cannot parse ip address",
@@ -47,7 +99,7 @@ impl InvalidType {
#[derive(Debug)]
pub struct Hosts {
record: Vec<(Host, IpAddr)>,
record: Vec<(Matcher, IpAddr)>,
}
impl Hosts {
@@ -55,7 +107,7 @@ impl Hosts {
Hosts { record: Vec::new() }
}
fn push(&mut self, record: (Host, IpAddr)) {
fn push(&mut self, record: (Matcher, IpAddr)) {
self.record.push(record);
}
@@ -65,7 +117,7 @@ impl Hosts {
}
}
pub fn iter(&mut self) -> Iter<(Host, IpAddr)> {
pub fn iter(&mut self) -> Iter<(Matcher, IpAddr)> {
self.record.iter()
}
@@ -79,67 +131,12 @@ impl Hosts {
}
}
// domain match
const TEXT: &str = "abcdefghijklmnopqrstuvwxyz0123456789-.";
const WILDCARD: &str = "abcdefghijklmnopqrstuvwxyz0123456789-.*";
#[derive(Debug)]
pub struct Host(MatchMode);
#[derive(Debug)]
enum MatchMode {
Text(String),
Regex(Regex),
}
impl Host {
fn new(domain: &str) -> result::Result<Host, regex::Error> {
// example.com
if Self::is_text(domain) {
return Ok(Host(MatchMode::Text(domain.to_string())));
}
// *.example.com
if Self::is_wildcard(domain) {
let s = format!("^{}$", domain.replace(".", r"\.").replace("*", r"[^.]+"));
return Ok(Host(MatchMode::Regex(Regex::new(&s)?)));
}
// use regex
Ok(Host(MatchMode::Regex(Regex::new(domain)?)))
}
fn is_text(domain: &str) -> bool {
domain.chars().all(|item| TEXT.chars().any(|c| item == c))
}
fn is_wildcard(domain: &str) -> bool {
domain
.chars()
.all(|item| WILDCARD.chars().any(|c| item == c))
}
pub fn is_match(&self, domain: &str) -> bool {
match &self.0 {
MatchMode::Text(text) => text == domain,
MatchMode::Regex(reg) => reg.is_match(domain),
}
}
pub fn as_str(&self) -> &str {
match &self.0 {
MatchMode::Text(text) => text,
MatchMode::Regex(reg) => reg.as_str(),
}
}
}
#[derive(Debug)]
pub struct Config {
pub bind: Vec<SocketAddr>,
pub proxy: Vec<SocketAddr>,
pub hosts: Hosts,
pub timeout: Option<u64>,
pub timeout: Option<Duration>,
pub invalid: Vec<Invalid>,
}
@@ -221,19 +218,18 @@ impl Parser {
}
// match host
// example.com 0.0.0.0
// 0.0.0.0 example.com
fn record(left: &str, right: &str) -> result::Result<(Host, IpAddr), InvalidType> {
// example.com 0.0.0.0 or 0.0.0.0 example.com
fn record(left: &str, right: &str) -> result::Result<(Matcher, IpAddr), InvalidType> {
// ip domain
if let Ok(ip) = right.parse() {
return Host::new(left)
return Matcher::new(left)
.map(|host| (host, ip))
.map_err(|_| InvalidType::Regex);
}
// domain ip
if let Ok(ip) = left.parse() {
return Host::new(right)
return Matcher::new(right)
.map(|host| (host, ip))
.map_err(|_| InvalidType::Regex);
}
@@ -282,12 +278,12 @@ impl Parser {
Ok(addr) => config.proxy.push(addr),
Err(_) => invalid!(InvalidType::SocketAddr),
},
"timeout" => match value.parse::<u64>() {
"timeout" => match try_parse_duration(value) {
Ok(timeout) => config.timeout = Some(timeout),
Err(_) => invalid!(InvalidType::Timeout),
},
"import" => {
let mut path = Path::new(value).to_path_buf();
let mut path = PathBuf::from(value);
if path.is_relative() {
if let Some(parent) = self.path.parent() {
path = parent.join(path);
@@ -304,42 +300,6 @@ impl Parser {
Ok(config)
}
.boxed()
}
}
#[cfg(test)]
mod test_host {
use super::*;
#[test]
fn test_create() {}
#[test]
fn test_text() {
let host = Host::new("example.com").unwrap();
assert!(host.is_match("example.com"));
assert!(!host.is_match("-example.com"));
assert!(!host.is_match("example.com.cn"));
}
#[test]
fn test_wildcard() {
let host = Host::new("*.example.com").unwrap();
assert!(host.is_match("test.example.com"));
assert!(!host.is_match("test.example.test"));
assert!(!host.is_match("test.test.com"));
let host = Host::new("*.example.*").unwrap();
assert!(host.is_match("test.example.test"));
assert!(!host.is_match("example.com"));
assert!(!host.is_match("test.test.test"));
}
#[test]
fn test_regex() {
let host = Host::new("^example.com$").unwrap();
assert!(host.is_match("example.com"));
assert!(!host.is_match("test.example.com"));
.boxed()
}
}

26
src/logger.rs Normal file
View File

@@ -0,0 +1,26 @@
use log::{LevelFilter, Metadata, Record, SetLoggerError};
static LOGGER: Logger = Logger;
pub fn init() -> Result<(), SetLoggerError> {
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Trace))
}
struct Logger;
impl log::Log for Logger {
fn enabled(&self, _: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
println!(
"[{}] {:<5} {}",
time::now().strftime("%F %T").unwrap(),
record.level(),
record.args()
);
}
fn flush(&self) {}
}

View File

@@ -1,15 +1,16 @@
#[macro_use]
extern crate lazy_static;
mod config;
mod lib;
mod logger;
mod matcher;
mod watch;
use ace::App;
use config::{Config, Hosts, Invalid, Parser};
use config::{Config, Hosts, MultipleInvalid, Parser};
use dirs;
use futures::prelude::*;
use lazy_static::lazy_static;
use lib::*;
use log::{error, info, warn};
use regex::Regex;
use std::{
env,
@@ -21,6 +22,7 @@ use std::{
use tokio::{
io::{Error, ErrorKind, Result},
net::UdpSocket,
sync::RwLock,
time::timeout,
};
use watch::Watch;
@@ -29,13 +31,15 @@ const CONFIG_FILE: [&str; 2] = [".updns", "config"];
const DEFAULT_BIND: &str = "0.0.0.0:53";
const DEFAULT_PROXY: [&str; 2] = ["8.8.8.8:53", "1.1.1.1:53"];
const DEFAULT_TIMEOUT: u64 = 2000;
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(2000);
const WATCH_INTERVAL: u64 = 5000;
const WATCH_INTERVAL: Duration = Duration::from_millis(5000);
static mut PROXY: Vec<SocketAddr> = Vec::new();
static mut HOSTS: Option<Hosts> = None;
static mut TIMEOUT: u64 = DEFAULT_TIMEOUT;
lazy_static! {
static ref PROXY: RwLock<Vec<SocketAddr>> = RwLock::new(Vec::new());
static ref HOSTS: RwLock<Hosts> = RwLock::new(Hosts::new());
static ref TIMEOUT: RwLock<Duration> = RwLock::new(DEFAULT_TIMEOUT);
}
macro_rules! exit {
($($arg:tt)*) => {
@@ -45,28 +49,14 @@ macro_rules! exit {
}
};
}
macro_rules! error {
($($arg:tt)*) => {
eprint!("{} ERROR ", time::now().strftime("[%Y-%m-%d %H:%M:%S]").unwrap());
eprintln!($($arg)*);
};
}
macro_rules! info {
($($arg:tt)*) => {
print!("{} INFO ", time::now().strftime("[%Y-%m-%d %H:%M:%S]").unwrap());
println!($($arg)*);
};
}
macro_rules! warn {
($($arg:tt)*) => {
print!("{} WARN ", time::now().strftime("[%Y-%m-%d %H:%M:%S]").unwrap());
println!($($arg)*);
};
}
#[tokio::main]
async fn main() {
let app = App::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
logger::init().unwrap_or_else(|err| exit!("Log init failed:\n{:#?}", err));
let app = App::new()
.name(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.cmd("add", "Add a DNS record")
.cmd("ls", "Print all configured DNS records")
.cmd("config", "Call 'vim' to edit the configuration file")
@@ -74,12 +64,18 @@ async fn main() {
.cmd("help", "Print help information")
.cmd("version", "Print version information")
.opt("-c", "Specify a config file")
.opt("-w", "Check the interval of the configuration file (ms)");
.opt(
"-i",
vec![
"Check the interval time of the configuration file",
"format: 1ms, 1s, 1m, 1h, 1d",
],
);
let config_path = match app.value("-c") {
Some(values) => {
if values.is_empty() {
exit!("'-c' value: [CONFIG]");
exit!("'-c' missing a value: [FILE]");
}
PathBuf::from(values[0])
}
@@ -90,17 +86,20 @@ async fn main() {
};
// Check profile interval
let watch_interval = match app.value("-w") {
Some(values) => {
let watch_interval = app
.value("-i")
.map(|values| {
if values.is_empty() {
exit!("'-w' value: [ms]");
exit!("'-i' missing a value: : [1ms, 1s, 1m, 1h, 1d]");
}
values[0]
.parse::<u64>()
.unwrap_or_else(|_| exit!("Cannot resolve '{}' to number", &values[0]))
}
None => WATCH_INTERVAL,
};
config::try_parse_duration(values[0]).unwrap_or_else(|_| {
exit!(
"Cannot resolve '{}' to interval time, format: 1ms, 1s, 1m, 1h, 1d",
&values[0]
)
})
})
.unwrap_or(WATCH_INTERVAL);
if let Some(cmd) = app.command() {
match cmd.as_str() {
@@ -131,16 +130,16 @@ async fn main() {
}
}
"ls" => {
let mut config = config_parse(&config_path).await;
let mut config = force_get_config(&config_path).await;
let n = config
.hosts
.iter()
.map(|(r, _)| r.as_str().len())
.map(|(m, _)| m.to_string().len())
.fold(0, |a, b| a.max(b));
for (host, ip) in config.hosts.iter() {
println!("{:domain$} {}", host.as_str(), ip, domain = n);
println!("{:domain$} {}", host.to_string(), ip, domain = n);
}
}
"config" => {
@@ -150,7 +149,7 @@ async fn main() {
.unwrap_or_else(|err| exit!("Call 'vim' command failed\n{:?}", err));
if status.success() {
config_parse(&config_path).await;
force_get_config(&config_path).await;
} else {
println!("'vim' exits with a non-zero status code: {:?}", status);
}
@@ -165,14 +164,14 @@ async fn main() {
config_path.display()
);
}
"help" => app.help(),
"version" => app.version(),
_ => app.error_try("help"),
"help" => app.print_help(),
"version" => app.print_version(),
_ => app.print_error_try("help"),
}
return;
}
let mut config = config_parse(&config_path).await;
let mut config = force_get_config(&config_path).await;
if config.bind.is_empty() {
warn!("Will bind the default address '{}'", DEFAULT_BIND);
config.bind.push(DEFAULT_BIND.parse().unwrap());
@@ -184,7 +183,7 @@ async fn main() {
);
}
update_config(config.proxy, config.hosts, config.timeout);
update_config(config.proxy, config.hosts, config.timeout).await;
// Run server
for addr in config.bind {
@@ -194,21 +193,29 @@ async fn main() {
watch_config(config_path, watch_interval).await;
}
fn update_config(mut proxy: Vec<SocketAddr>, hosts: Hosts, timeout: Option<u64>) {
async fn update_config(mut proxy: Vec<SocketAddr>, hosts: Hosts, timeout: Option<Duration>) {
if proxy.is_empty() {
proxy = DEFAULT_PROXY
.iter()
.map(|p| p.parse().unwrap())
.collect::<Vec<SocketAddr>>();
}
unsafe {
PROXY = proxy;
HOSTS = Some(hosts);
TIMEOUT = timeout.unwrap_or(DEFAULT_TIMEOUT);
};
{
let mut w = PROXY.write().await;
*w = proxy;
}
{
let mut w = HOSTS.write().await;
*w = hosts;
}
{
let mut w = TIMEOUT.write().await;
*w = timeout.unwrap_or(DEFAULT_TIMEOUT);
}
}
async fn config_parse(file: &PathBuf) -> Config {
async fn force_get_config(file: &PathBuf) -> Config {
let parser = Parser::new(file)
.await
.unwrap_or_else(|err| exit!("Failed to read config file {:?}\n{:?}", file, err));
@@ -218,30 +225,19 @@ async fn config_parse(file: &PathBuf) -> Config {
.await
.unwrap_or_else(|err| exit!("Parsing config file failed\n{:?}", err));
output_invalid(&config.invalid);
config.invalid.print();
config
}
fn output_invalid(errors: &[Invalid]) {
for invalid in errors {
error!(
"[line:{}] {} `{}`",
invalid.line,
invalid.kind.as_str(),
invalid.source
);
}
}
async fn watch_config(p: PathBuf, t: u64) {
async fn watch_config(p: PathBuf, t: Duration) {
let mut watch = Watch::new(&p, t).await;
while let Some(_) = watch.next().await {
info!("Reload the configuration file: {:?}", &p);
if let Ok(parser) = Parser::new(&p).await {
if let Ok(config) = parser.parse().await {
update_config(config.proxy, config.hosts, config.timeout);
output_invalid(&config.invalid);
update_config(config.proxy, config.hosts, config.timeout).await;
config.invalid.print();
}
}
}
@@ -282,12 +278,13 @@ async fn run_server(addr: SocketAddr) {
}
async fn proxy(buf: &[u8]) -> Result<Vec<u8>> {
let proxy = unsafe { &PROXY };
let proxy = PROXY.read().await;
let duration = *TIMEOUT.read().await;
for addr in proxy.iter() {
let mut socket = UdpSocket::bind(("0.0.0.0", 0)).await?;
let data: Result<Vec<u8>> = timeout(Duration::from_millis(unsafe { TIMEOUT }), async {
let data: Result<Vec<u8>> = timeout(duration, async {
socket.send_to(&buf, addr).await?;
let mut res = [0; 512];
let len = socket.recv(&mut res).await?;
@@ -311,9 +308,8 @@ async fn proxy(buf: &[u8]) -> Result<Vec<u8>> {
))
}
fn get_answer(domain: &str, query: QueryType) -> Option<DnsRecord> {
let hosts = unsafe { HOSTS.as_ref().unwrap() };
if let Some(ip) = hosts.get(domain) {
async fn get_answer(domain: &str, query: QueryType) -> Option<DnsRecord> {
if let Some(ip) = HOSTS.read().await.get(domain) {
match query {
QueryType::A => {
if let IpAddr::V4(addr) = ip {
@@ -350,7 +346,7 @@ async fn handle(mut req: BytePacketBuffer, len: usize) -> Result<Vec<u8>> {
info!("{} {:?}", query.name, query.qtype);
// Whether to proxy
let answer = match get_answer(&query.name, query.qtype) {
let answer = match get_answer(&query.name, query.qtype).await {
Some(record) => record,
None => return proxy(&req.buf[..len]).await,
};
@@ -362,7 +358,6 @@ async fn handle(mut req: BytePacketBuffer, len: usize) -> Result<Vec<u8>> {
let mut res_buffer = BytePacketBuffer::new();
request.write(&mut res_buffer)?;
let len = res_buffer.pos();
let data = res_buffer.get_range(0, len)?;
let data = res_buffer.get_range(0, res_buffer.pos())?;
Ok(data.to_vec())
}

184
src/matcher.rs Normal file
View File

@@ -0,0 +1,184 @@
use regex::{Error, Regex};
use std::fmt;
#[derive(Debug)]
pub struct Matcher(MatchMode);
#[derive(Debug)]
enum MatchMode {
Static(String),
Wildcard(WildcardMatch),
Regex(Regex),
}
const REGEX_WORD: char = '~';
const WILDCARD: char = '*';
impl Matcher {
pub fn new(raw: &str) -> Result<Self, Error> {
// Use regex: ~^example\.com$
if raw.starts_with(REGEX_WORD) {
let reg = raw.replacen(REGEX_WORD, "", 1);
let mode = MatchMode::Regex(Regex::new(&reg)?);
return Ok(Matcher(mode));
}
// Use wildcard match: *.example.com
let find = raw.chars().any(|c| c == WILDCARD);
if find {
let mode = MatchMode::Wildcard(WildcardMatch::new(raw));
return Ok(Matcher(mode));
}
// Plain Text: example.com
Ok(Matcher(MatchMode::Static(raw.to_string())))
}
pub fn is_match(&self, domain: &str) -> bool {
match &self.0 {
MatchMode::Static(raw) => raw == domain,
MatchMode::Wildcard(raw) => raw.is_match(domain),
MatchMode::Regex(raw) => raw.is_match(domain),
}
}
}
#[derive(Debug)]
struct WildcardMatch {
chars: Vec<char>,
}
impl WildcardMatch {
fn new(raw: &str) -> Self {
let mut chars = Vec::with_capacity(raw.len());
for c in raw.chars() {
chars.push(c);
}
Self { chars }
}
fn is_match(&self, text: &str) -> bool {
let mut chars = text.chars();
let mut dot = false;
for cur in &self.chars {
match cur {
'*' => {
match chars.next() {
Some(c) => {
if c == '.' {
return false;
}
}
None => return false,
}
while let Some(n) = chars.next() {
if n == '.' {
dot = true;
break;
}
}
}
word => {
if dot {
if word == &'.' {
dot = false;
continue;
} else {
return false;
}
}
match chars.next() {
Some(c) => {
if word != &c {
return false;
}
}
None => return false,
}
}
}
}
if dot {
return false;
}
chars.next().is_none()
}
}
impl fmt::Display for Matcher {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0 {
MatchMode::Static(raw) => write!(f, "{}", raw),
MatchMode::Wildcard(raw) => {
let mut s = String::new();
for ch in raw.chars.clone() {
s.push(ch);
}
write!(f, "{}", s)
}
MatchMode::Regex(raw) => write!(f, "~{}", raw.as_str()),
}
}
}
#[cfg(test)]
mod test_matcher {
use super::*;
#[test]
fn test_create() {}
#[test]
fn test_text() {
let matcher = Matcher::new("example.com").unwrap();
assert!(matcher.is_match("example.com"));
assert!(!matcher.is_match("-example.com"));
assert!(!matcher.is_match("example.com.cn"));
}
#[test]
fn test_wildcard() {
let matcher = Matcher::new("*").unwrap();
assert!(matcher.is_match("localhost"));
assert!(!matcher.is_match(".localhost"));
assert!(!matcher.is_match("localhost."));
assert!(!matcher.is_match("local.host"));
let matcher = Matcher::new("*.com").unwrap();
assert!(matcher.is_match("test.com"));
assert!(matcher.is_match("example.com"));
assert!(!matcher.is_match("test.test"));
assert!(!matcher.is_match(".test.com"));
assert!(!matcher.is_match("test.com."));
assert!(!matcher.is_match("test.test.com"));
let matcher = Matcher::new("*.*").unwrap();
assert!(matcher.is_match("test.test"));
assert!(!matcher.is_match(".test.test"));
assert!(!matcher.is_match("test.test."));
assert!(!matcher.is_match("test.test.test"));
let matcher = Matcher::new("*.example.com").unwrap();
assert!(matcher.is_match("test.example.com"));
assert!(matcher.is_match("example.example.com"));
assert!(!matcher.is_match("test.example.com.com"));
assert!(!matcher.is_match("test.test.example.com"));
let matcher = Matcher::new("*.example.*").unwrap();
assert!(matcher.is_match("test.example.com"));
assert!(matcher.is_match("example.example.com"));
assert!(!matcher.is_match("test.test.example.test"));
assert!(!matcher.is_match("test.example.test.test"));
}
#[test]
fn test_regex() {
let matcher = Matcher::new("~^example.com$").unwrap();
assert!(matcher.is_match("example.com"));
assert!(!matcher.is_match("test.example.com"));
}
#[test]
fn test_to_string() {}
}

View File

@@ -19,13 +19,13 @@ pub struct Watch {
}
impl Watch {
pub async fn new<P: AsRef<Path>>(path: P, duration: u64) -> Watch {
pub async fn new<P: AsRef<Path>>(path: P, duration: Duration) -> Watch {
let path = path.as_ref().to_path_buf();
Watch {
path: path.clone(),
state: None,
modified: Self::modified(path).await,
timer: interval(Duration::from_millis(duration)),
timer: interval(duration),
}
}