diff --git a/.cargo/config.toml b/.cargo/config.toml index cbfec78..df5b707 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,4 +2,8 @@ protocol = "sparse" [build] -# target = ["x86_64-unknown-linux-gnu", "aarch64-linux-android"] +# target = ["x86_64-unknown-linux-gnu"] +# target = ["aarch64-linux-android"] +# target = ["aarch64-apple-ios"] +# target = ["x86_64-pc-windows-msvc"] +# target = ["x86_64-apple-darwin"] diff --git a/.github/workflows/format-build.yml b/.github/workflows/format-build.yml deleted file mode 100644 index c45afe6..0000000 --- a/.github/workflows/format-build.yml +++ /dev/null @@ -1,77 +0,0 @@ -on: [push, pull_request] - -name: Build and Formatting Tests - -jobs: - check: - name: Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: check - - fmt: - name: Rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - run: rustup component add rustfmt - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - clippy: - name: Clippy - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: rustfmt - run: | - rustc --version - cargo fmt --all -- --check - - run: rustup component add clippy - - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings - - name: Build - run: cargo build --verbose - - iperf: - name: Iperf - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: build - args: --release - - run: sudo apt-get install -y iperf3 dante-server - - run: sudo systemctl stop danted - - run: sudo tests/iperf/test.sh diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml deleted file mode 100644 index ca6ae54..0000000 --- a/.github/workflows/publish-docker.yml +++ /dev/null @@ -1,48 +0,0 @@ -# -name: Create and publish a Docker image - -# Configures this workflow to run every time a change is pushed to the branch called `release`. -on: - push: - tags: [ 'v*.*.*' ] - -# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. -jobs: - build-and-push-image: - runs-on: ubuntu-latest - # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. - permissions: - contents: read - packages: write - # - steps: - - name: Checkout repository - uses: actions/checkout@v4 - # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - - name: Log in to the Container registry - uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. - # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - - name: Build and push Docker image - uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..d66b58a --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,26 @@ +name: Push or PR + +on: + [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build_n_test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - name: rustfmt + run: cargo fmt --all -- --check + - name: check + run: cargo check --verbose + - name: clippy + run: cargo clippy --all-targets --all-features -- -D warnings + - name: Build + run: cargo build --verbose --tests --all-features diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 3e00ff3..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,45 +0,0 @@ -on: - pull_request_review: - types: [submitted] - push: - workflow_dispatch: - pull_request_target: - types: [labeled] - -name: Integration Tests - -jobs: - proxy_tests: - name: Proxy Tests - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test') - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --no-run - - name: Populate .env - env: - DOTENV: ${{ secrets.DOTENV }} - run: echo "$DOTENV" > .env - - name: Set up runner SSH key - run: >- - set -o allexport && - source .env && - set +o allexport && - mkdir ~/.ssh && - echo "$TEST_SERVER_PRIVATE_SSH_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa - - name: Run tests - run: >- - set -o allexport && - source .env && - set +o allexport && - ssh -N -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -D 1080 "$TEST_SERVER_SSH_DST" & - while ! nc -z 127.0.0.1 1080; do sleep 1; done && - sudo -E /home/runner/.cargo/bin/cargo test diff --git a/.gitignore b/.gitignore index bc020c7..1706695 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ -examples/ +.env +project.xcworkspace/ +xcuserdata/ +.vscode/ +.VSCodeCounter/ build/ tmp/ -.* -*.secret -*.iml -!/.github -!/.cargo -/target Cargo.lock -manual-test.sh +target/ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index fa6f162..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,12 +0,0 @@ -# Changelog for Tun2Proxy - -## 0.1.1 - -- Updated dependencies: - - `chrono`: v0.4, ready for next planned release ; - - `clap`: last version ; - - `mio`: v0.8 + rename renamed feature (os-util became os-ext) + some fixes due to removal of `TcpSocket` type ; - - `smoltcp`: set v0.8 but from crates.io, plus old reference could not work. -- Fixes: - - Removed typo from Cargo.toml ; - - Clippy. diff --git a/Cargo.toml b/Cargo.toml index 5706b92..fa9691f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,66 +1,43 @@ [package] -authors = ["B. Blechschmidt", "ssrlive"] -edition = "2021" name = "tun2proxy" -version = "0.1.12" +version = "0.2.0" +edition = "2021" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -crate-type = ["cdylib", "lib"] +crate-type = ["staticlib", "cdylib", "lib"] [dependencies] +async-recursion = "1.0" +async-trait = "0.1" base64 = { version = "0.21" } -clap = { version = "4.4", features = ["derive"] } -ctrlc2 = { version = "3.5", features = ["termination"] } +chrono = "0.4" +clap = { version = "4.4", features = ["derive", "wrap_help", "color"] } +ctrlc2 = { version = "3.5", features = ["tokio", "termination"] } digest_auth = "0.3" dotenvy = "0.15" -env_logger = "0.10" +env_logger = "0.11" hashlink = "0.9" httparse = "1.8" -libc = "0.2" -log = "0.4" -mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } -nix = { version = "0.27", features = [ - "process", - "signal", - "fs", - "mount", - "user", -] } -prctl = "1.0" -smoltcp = { version = "0.11", features = ["std", "phy-tuntap_interface"] } -socks5-impl = { version = "0.5", default-features = false } +ipstack = { version = "0.0", features = ["log"] } +log = { version = "0.4", features = ["std"] } +socks5-impl = { version = "0.5" } thiserror = "1.0" +tokio = { version = "1.35", features = ["full"] } +tproxy-config = { version = "0.1", features = ["log"] } trust-dns-proto = "0.23" +tun2 = { version = "1.0", features = ["async"] } +udp-stream = { version = "0.0", default-features = false } unicase = "2.7" url = "2.5" -[target.'cfg(target_family="unix")'.dependencies] -fork = "0.1" - [target.'cfg(target_os="android")'.dependencies] android_logger = "0.13" jni = { version = "0.21", default-features = false } -[dev-dependencies] -ctor = "0.2" -reqwest = { version = "0.11", default-features = false, features = [ - "blocking", - "json", - "rustls-tls", -] } -serial_test = "3.0" -test-log = "0.2" - -[target.'cfg(target_os="windows")'.dependencies] -rand = "0.8" -windows = { version = "0.52", features = [ - "Win32_Storage_FileSystem", - "Win32_NetworkManagement_IpHelper", - "Win32_NetworkManagement_Ndis", - "Win32_Networking_WinSock", - "Win32_Foundation", -] } -wintun = { version = "0.4", features = ["panic_on_unsent_packets"] } - [build-dependencies] serde_json = "1.0" + +[[bin]] +name = "tun2proxy" +path = "src/bin/main.rs" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 114ef12..0000000 --- a/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -#################################################################################################### -## Builder -#################################################################################################### -FROM rust:latest AS builder - -WORKDIR /worker -COPY ./ . -RUN cargo build --release --target x86_64-unknown-linux-gnu - - -#################################################################################################### -## Final image -#################################################################################################### -FROM ubuntu:latest - -RUN apt update && apt install -y iproute2 && apt clean all - -COPY --from=builder /worker/target/x86_64-unknown-linux-gnu/release/tun2proxy /usr/bin/tun2proxy - -ENTRYPOINT ["/usr/bin/tun2proxy", "--setup", "auto"] diff --git a/apple/readme.md b/apple/readme.md new file mode 100644 index 0000000..2baae5f --- /dev/null +++ b/apple/readme.md @@ -0,0 +1,21 @@ +Build iOS framework +---------------- + +# Install Rust build tools + +- Install Xcode Command Line Tools: `xcode-select --install` +- Install Rust programming language: `curl https://sh.rustup.rs -sSf | sh` +- Install iOS target support: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios` +- Install cbindgen tool: `cargo install cbindgen` + +# Building iOS framework + +Due to an unknown reason at present, compiling Rust code inside Xcode fails, so you have to manually compile it. Please run the following command in zsh (or bash): +```bash +cd tun2proxy + +cargo build --release --target aarch64-apple-ios +cargo build --release --target x86_64-apple-ios +lipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a +cbindgen --config cbindgen.toml -l C -o target/tun2proxy-sys.h +``` diff --git a/apple/tun2proxy.xcodeproj/project.pbxproj b/apple/tun2proxy.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6b8aa02 --- /dev/null +++ b/apple/tun2proxy.xcodeproj/project.pbxproj @@ -0,0 +1,398 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = B648A35829F43D110045B334 /* Tun2proxyWrapper.m */; }; + B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B692ACC829F7EA4C006BF04D /* libtun2proxy.a */; }; + B6DE654429F4255A00468184 /* tun2proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = B6DE654329F4255A00468184 /* tun2proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + B648A35829F43D110045B334 /* Tun2proxyWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tun2proxyWrapper.m; sourceTree = ""; }; + B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tun2proxyWrapper.h; sourceTree = ""; }; + B692ACC829F7EA4C006BF04D /* libtun2proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtun2proxy.a; path = ../target/libtun2proxy.a; sourceTree = ""; }; + B6DE654029F4255A00468184 /* tun2proxy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = tun2proxy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B6DE654329F4255A00468184 /* tun2proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tun2proxy.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B6DE653D29F4255A00468184 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + B692ACC729F7EA4C006BF04D /* Frameworks */ = { + isa = PBXGroup; + children = ( + B692ACC829F7EA4C006BF04D /* libtun2proxy.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + B6DE653629F4255A00468184 = { + isa = PBXGroup; + children = ( + B6DE654229F4255A00468184 /* tun2proxy */, + B6DE654129F4255A00468184 /* Products */, + B692ACC729F7EA4C006BF04D /* Frameworks */, + ); + sourceTree = ""; + }; + B6DE654129F4255A00468184 /* Products */ = { + isa = PBXGroup; + children = ( + B6DE654029F4255A00468184 /* tun2proxy.framework */, + ); + name = Products; + sourceTree = ""; + }; + B6DE654229F4255A00468184 /* tun2proxy */ = { + isa = PBXGroup; + children = ( + B6DE654329F4255A00468184 /* tun2proxy.h */, + B648A35829F43D110045B334 /* Tun2proxyWrapper.m */, + B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */, + ); + path = tun2proxy; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + B6DE653B29F4255A00468184 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */, + B6DE654429F4255A00468184 /* tun2proxy.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + B6DE653F29F4255A00468184 /* tun2proxy */ = { + isa = PBXNativeTarget; + buildConfigurationList = B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */; + buildPhases = ( + B692ACB329F7E203006BF04D /* Run Script */, + B6DE653B29F4255A00468184 /* Headers */, + B6DE653C29F4255A00468184 /* Sources */, + B6DE653D29F4255A00468184 /* Frameworks */, + B6DE653E29F4255A00468184 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = tun2proxy; + productName = tun2proxy; + productReference = B6DE654029F4255A00468184 /* tun2proxy.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B6DE653729F4255A00468184 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1430; + TargetAttributes = { + B6DE653F29F4255A00468184 = { + CreatedOnToolsVersion = 13.2.1; + }; + }; + }; + buildConfigurationList = B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = B6DE653629F4255A00468184; + productRefGroup = B6DE654129F4255A00468184 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B6DE653F29F4255A00468184 /* tun2proxy */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + B6DE653E29F4255A00468184 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + B692ACB329F7E203006BF04D /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "set -e\nPATH=\"$PATH:${HOME}/.cargo/bin\"\nRUST_PROJ=${PROJECT_DIR}/..\ncd \"${RUST_PROJ}\"\ncargo build --release --target aarch64-apple-ios\ncargo build --release --target x86_64-apple-ios\nlipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a\ncbindgen --config cbindgen.toml -l C -o target/tun2proxy-sys.h\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B6DE653C29F4255A00468184 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + B6DE654529F4255A00468184 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + B6DE654629F4255A00468184 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + B6DE654829F4255A00468184 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + ../target, + "$(PROJECT_DIR)/../target", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; + PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ../target; + }; + name = Debug; + }; + B6DE654929F4255A00468184 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + ../target, + "$(PROJECT_DIR)/../target", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; + PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = ../target; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B6DE654529F4255A00468184 /* Debug */, + B6DE654629F4255A00468184 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B6DE654829F4255A00468184 /* Debug */, + B6DE654929F4255A00468184 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = B6DE653729F4255A00468184 /* Project object */; +} diff --git a/apple/tun2proxy/Tun2proxyWrapper.h b/apple/tun2proxy/Tun2proxyWrapper.h new file mode 100644 index 0000000..70badfd --- /dev/null +++ b/apple/tun2proxy/Tun2proxyWrapper.h @@ -0,0 +1,22 @@ +// +// Tun2proxyWrapper.h +// tun2proxy +// +// Created by ssrlive on 2023/4/23. +// + +#ifndef Tun2proxyWrapper_h +#define Tun2proxyWrapper_h + +@interface Tun2proxyWrapper : NSObject + ++ (void)startWithConfig:(NSString *)proxy_url + tun_fd:(int)tun_fd + tun_mtu:(uint32_t)tun_mtu + dns_over_tcp:(bool)dns_over_tcp + verbose:(bool)verbose; ++ (void) shutdown; + +@end + +#endif /* Tun2proxyWrapper_h */ diff --git a/apple/tun2proxy/Tun2proxyWrapper.m b/apple/tun2proxy/Tun2proxyWrapper.m new file mode 100644 index 0000000..47b264b --- /dev/null +++ b/apple/tun2proxy/Tun2proxyWrapper.m @@ -0,0 +1,27 @@ +// +// Tun2proxyWrapper.m +// tun2proxy +// +// Created by ssrlive on 2023/4/23. +// + +#import + +#import "Tun2proxyWrapper.h" +#include "tun2proxy-sys.h" + +@implementation Tun2proxyWrapper + ++ (void)startWithConfig:(NSString *)proxy_url + tun_fd:(int)tun_fd + tun_mtu:(uint32_t)tun_mtu + dns_over_tcp:(bool)dns_over_tcp + verbose:(bool)verbose { + tun2proxy_run(proxy_url.UTF8String, tun_fd, tun_mtu, dns_over_tcp, verbose); +} + ++ (void)shutdown { + tun2proxy_stop(); +} + +@end diff --git a/apple/tun2proxy/tun2proxy.h b/apple/tun2proxy/tun2proxy.h new file mode 100644 index 0000000..d62e035 --- /dev/null +++ b/apple/tun2proxy/tun2proxy.h @@ -0,0 +1,18 @@ +// +// tun2proxy.h +// tun2proxy +// +// Created by tun2proxy on 2023/4/22. +// + +#import + +//! Project version number for tun2proxy. +FOUNDATION_EXPORT double tun2proxyVersionNumber; + +//! Project version string for tun2proxy. +FOUNDATION_EXPORT const unsigned char tun2proxyVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..45a8d42 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,6 @@ +[export] +include = ["tun2proxy_run", "tun2proxy_stop", "tun2proxy_set_log_callback"] +exclude = [ + "Java_com_github_shadowsocks_bg_Tun2proxy_run", + "Java_com_github_shadowsocks_bg_Tun2proxy_stop", +] diff --git a/scripts/dante.conf b/scripts/dante.conf new file mode 100644 index 0000000..dc91ad2 --- /dev/null +++ b/scripts/dante.conf @@ -0,0 +1,24 @@ +# logoutput: /var/log/socks.log +internal: 10.0.0.3 port = 10800 +external: 10.0.0.3 +clientmethod: none +socksmethod: none +user.privileged: root +user.notprivileged: nobody + +client pass { + from: 0/0 to: 0/0 + log: error connect disconnect +} + +socks pass { + from: 0/0 to: 0/0 + command: bind connect udpassociate + log: error connect disconnect + socksmethod: none +} + +socks pass { + from: 0.0.0.0/0 to: 0.0.0.0/0 + command: bindreply udpreply +} diff --git a/scripts/iperf3.sh b/scripts/iperf3.sh new file mode 100755 index 0000000..f491072 --- /dev/null +++ b/scripts/iperf3.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# sudo apt install iperf3 dante-server +# sudo systemctl stop danted + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +echo $SCRIPT_DIR + +netns="test" +dante="danted" +tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy" + +ip netns add "$netns" + +ip link add veth0 type veth peer name veth0 netns "$netns" + +# Configure veth0 in default ns +ip addr add 10.0.0.2/24 dev veth0 +ip link set dev veth0 up + +# Configure veth0 in child ns +ip netns exec "$netns" ip addr add 10.0.0.3/24 dev veth0 +ip netns exec "$netns" ip addr add 10.0.0.4/24 dev veth0 +ip netns exec "$netns" ip link set dev veth0 up + +# Configure lo interface in child ns +ip netns exec "$netns" ip addr add 127.0.0.1/8 dev lo +ip netns exec "$netns" ip link set dev lo up + +echo "Starting Dante in background ..." +ip netns exec "$netns" "$dante" -f ${SCRIPT_DIR}/dante.conf & + +# Start iperf3 server in netns +ip netns exec "$netns" iperf3 -s -B 10.0.0.4 & + +sleep 1 + +# Prepare tun2proxy +ip tuntap add name tun0 mode tun +ip link set tun0 up +ip route add 10.0.0.4 dev tun0 +"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off & + +sleep 3 + +# Run iperf client through tun2proxy +iperf3 -c 10.0.0.4 -P 10 -R + +sleep 3 + +iperf3 -c 10.0.0.4 -P 10 + +# Clean up +# sudo sh -c "pkill tun2proxy; pkill iperf3; pkill danted; ip link del tun0; ip netns del test" diff --git a/scripts/linux.sh b/scripts/linux.sh new file mode 100755 index 0000000..dc1d805 --- /dev/null +++ b/scripts/linux.sh @@ -0,0 +1,66 @@ +#! /usr/bin/bash -x + +# Please set the following parameters according to your environment +# BYPASS_IP=123.45.67.89 +PROXY_IP=127.0.0.1 +PROXY_PORT=1080 +PROXY_TYPE=SOCKS5 + +function core_function() { + local is_envonly="${1}" + local bypass_ip="${2}" + + sudo ip tuntap add name tun0 mode tun + sudo ip link set tun0 up + + sudo ip route add "${bypass_ip}" $(ip route | grep '^default' | cut -d ' ' -f 2-) + + sudo ip route add 128.0.0.0/1 dev tun0 + sudo ip route add 0.0.0.0/1 dev tun0 + + sudo ip route add ::/1 dev tun0 + sudo ip route add 8000::/1 dev tun0 + + sudo sh -c "echo nameserver 198.18.0.1 > /etc/resolv.conf" + + if [ "$is_envonly" = true ]; then + read -n 1 -s -r -p "Don't do anything. If you want to exit and clearup environment, press any key..." + echo "" + restore + else + trap 'echo "" && echo "tun2proxy exited with code: $?" && restore' EXIT + local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local APP_BIN_PATH="${SCRIPT_DIR}/../target/release/tun2proxy" + "${APP_BIN_PATH}" --tun tun0 --proxy "${PROXY_TYPE}://${PROXY_IP}:${PROXY_PORT}" -v trace + fi +} + +function restore() { + sudo ip link del tun0 + sudo systemctl restart systemd-resolved.service +} + +function main() { + local action=${1} + # [ -z ${1} ] && action="envonly" + + local bypass_ip=${2} + # [ -z ${2} ] && bypass_ip="123.45.67.89" + + case "${action}" in + envonly) + core_function true "${bypass_ip}" + ;; + tun2proxy) + core_function false "${bypass_ip}" + ;; + *) + echo "Arguments error! [${action}]" + echo "Usage: `basename $0` [envonly|tun2proxy] [bypass_ip]" + ;; + esac + + exit 0 +} + +main "$@" diff --git a/scripts/rperf.sh b/scripts/rperf.sh new file mode 100755 index 0000000..1f06986 --- /dev/null +++ b/scripts/rperf.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +function install_rperf_bin() { + local rperf_bin_url="https://github.com/ssrlive/rperf/releases/latest/download/rperf-x86_64-unknown-linux-musl.zip" + local rperf_bin_zip_file="rperf-x86_64-unknown-linux-musl.zip" + + command -v rperf > /dev/null + if [ $? -ne 0 ]; then + echo "Downloading rperf binary ..." + wget "$rperf_bin_url" >/dev/null 2>&1 + unzip "$rperf_bin_zip_file" rperf -d /usr/local/bin/ >/dev/null 2>&1 + rm "$rperf_bin_zip_file" + fi + + rperf -h >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Failed to install rperf binary" + exit 1 + fi +} + +install_rperf_bin + +sudo apt install dante-server -y >/dev/null 2>&1 +sudo systemctl stop danted + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# echo $SCRIPT_DIR + +netns="test" +dante="danted" +tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy" + +ip netns add "$netns" + +ip link add veth0 type veth peer name veth0 netns "$netns" + +# Configure veth0 in default ns +ip addr add 10.0.0.2/24 dev veth0 +ip link set dev veth0 up + +# Configure veth0 in child ns +ip netns exec "$netns" ip addr add 10.0.0.3/24 dev veth0 +ip netns exec "$netns" ip addr add 10.0.0.4/24 dev veth0 +ip netns exec "$netns" ip link set dev veth0 up + +# Configure lo interface in child ns +ip netns exec "$netns" ip addr add 127.0.0.1/8 dev lo +ip netns exec "$netns" ip link set dev lo up + +echo "Starting Dante in background ..." +ip netns exec "$netns" "$dante" -f ${SCRIPT_DIR}/dante.conf & + +# Start rperf server in netns +ip netns exec "$netns" rperf -s -B 10.0.0.4 & + +sleep 1 + +# Prepare tun2proxy +ip tuntap add name tun0 mode tun +ip link set tun0 up +ip route add 10.0.0.4 dev tun0 +"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off & + +sleep 3 + +# Run rperf client through tun2proxy +rperf -c 10.0.0.4 -v off -P 1 -r + +sleep 3 + +rperf -c 10.0.0.4 -v off -P 1 + +sleep 3 + +rperf -c 10.0.0.4 -v off -P 1 -u + +sleep 3 + +rperf -c 10.0.0.4 -v trace -P 1 -u -r + +# Clean up +# sudo sh -c "pkill tun2proxy; pkill rperf; pkill danted; ip link del tun0; ip netns del test" diff --git a/src/android.rs b/src/android.rs index 41388c4..cf86fd4 100644 --- a/src/android.rs +++ b/src/android.rs @@ -1,14 +1,16 @@ #![cfg(target_os = "android")] -use crate::{error::Error, tun2proxy::TunToProxy, tun_to_proxy, NetworkInterface, Options, Proxy}; +use crate::{ + args::{ArgDns, ArgProxy}, + error::{Error, Result}, + ArgVerbosity, Args, +}; use jni::{ objects::{JClass, JString}, sys::{jboolean, jint}, JNIEnv, }; -static mut TUN_TO_PROXY: Option = None; - /// # Safety /// /// Running tun2proxy @@ -22,8 +24,9 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( verbose: jboolean, dns_over_tcp: jboolean, ) -> jint { - let log_level = if verbose != 0 { "trace" } else { "info" }; - let filter_str = &format!("off,tun2proxy={log_level}"); + let dns = if dns_over_tcp != 0 { ArgDns::OverTcp } else { ArgDns::Direct }; + let verbosity = if verbose != 0 { ArgVerbosity::Trace } else { ArgVerbosity::Info }; + let filter_str = &format!("off,tun2proxy={verbosity}"); let filter = android_logger::FilterBuilder::new().parse(filter_str).build(); android_logger::init_once( android_logger::Config::default() @@ -31,31 +34,11 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( .with_max_level(log::LevelFilter::Trace) .with_filter(filter), ); + let proxy_url = get_java_string(&mut env, &proxy_url).unwrap(); + let proxy = ArgProxy::from_url(proxy_url).unwrap(); - let mut block = || -> Result<(), Error> { - let proxy_url = get_java_string(&mut env, &proxy_url)?; - let proxy = Proxy::from_url(proxy_url)?; - - let addr = proxy.addr; - let proxy_type = proxy.proxy_type; - log::info!("Proxy {proxy_type} server: {addr}"); - - let dns_addr = "8.8.8.8".parse::().unwrap(); - let options = Options::new().with_dns_addr(Some(dns_addr)).with_mtu(tun_mtu as usize); - let options = if dns_over_tcp != 0 { options.with_dns_over_tcp() } else { options }; - - let interface = NetworkInterface::Fd(tun_fd); - let tun2proxy = tun_to_proxy(&interface, &proxy, options)?; - TUN_TO_PROXY = Some(tun2proxy); - if let Some(tun2proxy) = &mut TUN_TO_PROXY { - tun2proxy.run()?; - } - Ok::<(), Error>(()) - }; - if let Err(error) = block() { - log::error!("failed to run tun2proxy with error: {:?}", error); - } - 0 + let args = Args::new(Some(tun_fd), proxy, dns, verbosity); + crate::api::tun2proxy_internal_run(args, tun_mtu as _) } /// # Safety @@ -63,20 +46,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( /// Shutdown tun2proxy #[no_mangle] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint { - match &mut TUN_TO_PROXY { - None => { - log::error!("tun2proxy not started"); - 1 - } - Some(tun2proxy) => { - if let Err(e) = tun2proxy.shutdown() { - log::error!("failed to shutdown tun2proxy with error: {:?}", e); - 1 - } else { - 0 - } - } - } + crate::api::tun2proxy_internal_stop() } unsafe fn get_java_string<'a>(env: &'a mut JNIEnv, string: &'a JString) -> Result<&'a str, Error> { diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..4f7d3cf --- /dev/null +++ b/src/api.rs @@ -0,0 +1,70 @@ +#![cfg(any(target_os = "ios", target_os = "android"))] + +use crate::{Args, Builder, Quit}; +use std::{os::raw::c_int, sync::Arc}; + +static mut TUN_QUIT: Option> = None; + +pub(crate) fn tun2proxy_internal_run(args: Args, tun_mtu: usize) -> c_int { + if unsafe { TUN_QUIT.is_some() } { + log::error!("tun2proxy already started"); + return -1; + } + + let block = async move { + log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); + + let mut config = tun2::Configuration::default(); + config.raw_fd(args.tun_fd.ok_or(crate::Error::from("tun_fd"))?); + + let device = tun2::create_as_async(&config).map_err(std::io::Error::from)?; + + #[cfg(target_os = "android")] + let tun2proxy = Builder::new(device, args).mtu(tun_mtu).build(); + #[cfg(target_os = "ios")] + let tun2proxy = Builder::new(device, args).mtu(tun_mtu).build(); + let (join_handle, quit) = tun2proxy.start(); + + unsafe { TUN_QUIT = Some(Arc::new(quit)) }; + + join_handle.await + }; + + match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + Err(_err) => { + log::error!("failed to create tokio runtime with error: {:?}", _err); + -1 + } + Ok(rt) => match rt.block_on(block) { + Ok(_) => 0, + Err(_err) => { + log::error!("failed to run tun2proxy with error: {:?}", _err); + -2 + } + }, + } +} + +pub(crate) fn tun2proxy_internal_stop() -> c_int { + let res = match unsafe { &TUN_QUIT } { + None => { + log::error!("tun2proxy not started"); + -1 + } + Some(tun_quit) => match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + Err(_err) => { + log::error!("failed to create tokio runtime with error: {:?}", _err); + -2 + } + Ok(rt) => match rt.block_on(async move { tun_quit.trigger().await }) { + Ok(_) => 0, + Err(_err) => { + log::error!("failed to stop tun2proxy with error: {:?}", _err); + -3 + } + }, + }, + }; + unsafe { TUN_QUIT = None }; + res +} diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..f112482 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,198 @@ +use crate::{Error, Result}; +use socks5_impl::protocol::UserKey; +use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use tproxy_config::TUN_NAME; + +#[derive(Debug, Clone, clap::Parser)] +#[command(author, version, about = "tun2proxy application.", long_about = None)] +pub struct Args { + /// Proxy URL in the form proto://[username[:password]@]host:port, + /// where proto is one of socks4, socks5, http. For example: + /// socks5://myname:password@127.0.0.1:1080 + #[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")] + pub proxy: ArgProxy, + + /// Name of the tun interface + #[arg(short, long, value_name = "name", conflicts_with = "tun_fd", default_value = TUN_NAME)] + pub tun: String, + + /// File descriptor of the tun interface + #[arg(long, value_name = "fd", conflicts_with = "tun")] + pub tun_fd: Option, + + /// IPv6 enabled + #[arg(short = '6', long)] + pub ipv6_enabled: bool, + + #[cfg(target_os = "linux")] + #[arg(short, long)] + /// Routing and system setup, which decides whether to setup the routing and system configuration, + /// this option requires root privileges + pub setup: bool, + + /// DNS handling strategy + #[arg(short, long, value_name = "strategy", value_enum, default_value = "direct")] + pub dns: ArgDns, + + /// DNS resolver address + #[arg(long, value_name = "IP", default_value = "8.8.8.8")] + pub dns_addr: IpAddr, + + /// IPs used in routing setup which should bypass the tunnel + #[arg(short, long, value_name = "IP")] + pub bypass: Vec, + + /// Verbosity level + #[arg(short, long, value_name = "level", value_enum, default_value = "info")] + pub verbosity: ArgVerbosity, +} + +impl Default for Args { + fn default() -> Self { + Args { + proxy: ArgProxy::default(), + tun: TUN_NAME.to_string(), + tun_fd: None, + ipv6_enabled: false, + #[cfg(target_os = "linux")] + setup: false, + dns: ArgDns::default(), + dns_addr: "8.8.8.8".parse().unwrap(), + bypass: vec![], + verbosity: ArgVerbosity::Info, + } + } +} + +impl Args { + pub fn parse_args() -> Self { + use clap::Parser; + Self::parse() + } + + pub fn new(tun_fd: Option, proxy: ArgProxy, dns: ArgDns, verbosity: ArgVerbosity) -> Self { + Args { + proxy, + tun_fd, + dns, + verbosity, + ..Args::default() + } + } +} + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +pub enum ArgVerbosity { + Off, + Error, + Warn, + #[default] + Info, + Debug, + Trace, +} + +impl std::fmt::Display for ArgVerbosity { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ArgVerbosity::Off => write!(f, "off"), + ArgVerbosity::Error => write!(f, "error"), + ArgVerbosity::Warn => write!(f, "warn"), + ArgVerbosity::Info => write!(f, "info"), + ArgVerbosity::Debug => write!(f, "debug"), + ArgVerbosity::Trace => write!(f, "trace"), + } + } +} + +/// DNS query handling strategy +/// - Virtual: Use a virtual DNS server to handle DNS queries, also known as Fake-IP mode +/// - OverTcp: Use TCP to send DNS queries to the DNS server +/// - Direct: Do not handle DNS by relying on DNS server bypassing +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +pub enum ArgDns { + Virtual, + OverTcp, + #[default] + Direct, +} + +#[derive(Clone, Debug)] +pub struct ArgProxy { + pub proxy_type: ProxyType, + pub addr: SocketAddr, + pub credentials: Option, +} + +impl Default for ArgProxy { + fn default() -> Self { + ArgProxy { + proxy_type: ProxyType::Socks5, + addr: "127.0.0.1:1080".parse().unwrap(), + credentials: None, + } + } +} + +impl ArgProxy { + pub fn from_url(s: &str) -> Result { + let e = format!("`{s}` is not a valid proxy URL"); + let url = url::Url::parse(s).map_err(|_| Error::from(&e))?; + let e = format!("`{s}` does not contain a host"); + let host = url.host_str().ok_or(Error::from(e))?; + + let mut url_host = String::from(host); + let e = format!("`{s}` does not contain a port"); + let port = url.port().ok_or(Error::from(&e))?; + url_host.push(':'); + url_host.push_str(port.to_string().as_str()); + + let e = format!("`{host}` could not be resolved"); + let mut addr_iter = url_host.to_socket_addrs().map_err(|_| Error::from(&e))?; + + let e = format!("`{host}` does not resolve to a usable IP address"); + let addr = addr_iter.next().ok_or(Error::from(&e))?; + + let credentials = if url.username() == "" && url.password().is_none() { + None + } else { + let username = String::from(url.username()); + let password = String::from(url.password().unwrap_or("")); + Some(UserKey::new(username, password)) + }; + + let scheme = url.scheme(); + + let proxy_type = match url.scheme().to_ascii_lowercase().as_str() { + "socks4" => Some(ProxyType::Socks4), + "socks5" => Some(ProxyType::Socks5), + "http" => Some(ProxyType::Http), + _ => None, + } + .ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?; + + Ok(ArgProxy { + proxy_type, + addr, + credentials, + }) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +pub enum ProxyType { + Socks4, + #[default] + Socks5, + Http, +} + +impl std::fmt::Display for ProxyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProxyType::Socks4 => write!(f, "socks4"), + ProxyType::Socks5 => write!(f, "socks5"), + ProxyType::Http => write!(f, "http"), + } + } +} diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..f63e569 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,83 @@ +use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; +use tun2::DEFAULT_MTU as MTU; +use tun2proxy::{Args, Builder}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + dotenvy::dotenv().ok(); + let args = Args::parse_args(); + + let bypass_ips = args.bypass.clone(); + + // let default = format!("{}={:?}", module_path!(), args.verbosity); + let default = format!("{:?}", args.verbosity); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); + + let mut config = tun2::Configuration::default(); + config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); + config.destination(TUN_GATEWAY); + if let Some(tun_fd) = args.tun_fd { + config.raw_fd(tun_fd); + } else { + config.name(&args.tun); + } + + #[cfg(target_os = "linux")] + config.platform_config(|config| { + #[allow(deprecated)] + config.packet_information(true); + config.ensure_root_privileges(args.setup); + }); + + #[cfg(target_os = "windows")] + config.platform_config(|config| { + config.device_guid(Some(12324323423423434234_u128)); + }); + + #[allow(unused_variables)] + let mut tproxy_args = TproxyArgs::new() + .tun_dns(args.dns_addr) + .proxy_addr(args.proxy.addr) + .bypass_ips(&bypass_ips); + #[allow(unused_assignments)] + if args.tun_fd.is_none() { + tproxy_args = tproxy_args.tun_name(&args.tun); + } + + #[allow(unused_mut, unused_assignments, unused_variables)] + let mut setup = true; + + #[cfg(target_os = "linux")] + { + setup = args.setup; + if setup { + tproxy_config::tproxy_setup(&tproxy_args)?; + } + } + + let device = tun2::create_as_async(&config)?; + + #[cfg(any(target_os = "windows", target_os = "macos"))] + if setup { + tproxy_config::tproxy_setup(&tproxy_args)?; + } + + let tun2proxy = Builder::new(device, args).mtu(MTU).build(); + let (join_handle, quit) = tun2proxy.start(); + + ctrlc2::set_async_handler(async move { + quit.trigger().await.expect("quit error"); + }) + .await; + + if let Err(err) = join_handle.await { + log::trace!("main_entry error {}", err); + } + + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] + if setup { + tproxy_config::tproxy_remove(&tproxy_args)?; + } + + Ok(()) +} diff --git a/src/directions.rs b/src/directions.rs new file mode 100644 index 0000000..077d669 --- /dev/null +++ b/src/directions.rs @@ -0,0 +1,28 @@ +#![allow(dead_code)] + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub(crate) enum IncomingDirection { + FromServer, + FromClient, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub(crate) enum OutgoingDirection { + ToServer, + ToClient, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub(crate) enum Direction { + Incoming(IncomingDirection), + Outgoing(OutgoingDirection), +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub(crate) struct DataEvent<'a, T> { + pub(crate) direction: T, + pub(crate) buffer: &'a [u8], +} + +pub(crate) type IncomingDataEvent<'a> = DataEvent<'a, IncomingDirection>; +pub(crate) type OutgoingDataEvent<'a> = DataEvent<'a, OutgoingDirection>; diff --git a/src/dns.rs b/src/dns.rs index 2f95b22..a5ce30c 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,39 +1,10 @@ -#![allow(dead_code)] - -use std::{ - net::{IpAddr, Ipv4Addr, SocketAddr}, - str::FromStr, -}; +use std::{net::IpAddr, str::FromStr}; use trust_dns_proto::op::MessageType; use trust_dns_proto::{ op::{Message, ResponseCode}, rr::{record_type::RecordType, Name, RData, Record}, }; -#[cfg(feature = "use-rand")] -pub fn build_dns_request(domain: &str, query_type: RecordType, used_by_tcp: bool) -> Result, String> { - // [dependencies] - // rand = "0.8" - use rand::{rngs::StdRng, Rng, SeedableRng}; - use trust_dns_proto::op::{header::MessageType, op_code::OpCode, query::Query}; - let name = Name::from_str(domain).map_err(|e| e.to_string())?; - let query = Query::query(name, query_type); - let mut msg = Message::new(); - msg.add_query(query) - .set_id(StdRng::from_entropy().gen()) - .set_op_code(OpCode::Query) - .set_message_type(MessageType::Query) - .set_recursion_desired(true); - let mut msg_buf = msg.to_vec().map_err(|e| e.to_string())?; - if used_by_tcp { - let mut buf = (msg_buf.len() as u16).to_be_bytes().to_vec(); - buf.append(&mut msg_buf); - Ok(buf) - } else { - Ok(msg_buf) - } -} - pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result { let record = match ip { IpAddr::V4(ip) => { @@ -105,17 +76,3 @@ pub fn parse_data_to_dns_message(data: &[u8], used_by_tcp: bool) -> Result bool { - fn is_benchmarking(addr: &Ipv4Addr) -> bool { - addr.octets()[0] == 198 && (addr.octets()[1] & 0xfe) == 18 - } - fn addr_v4_is_private(addr: &Ipv4Addr) -> bool { - is_benchmarking(addr) || addr.is_private() || addr.is_loopback() || addr.is_link_local() - } - match addr { - SocketAddr::V4(addr) => addr_v4_is_private(addr.ip()), - SocketAddr::V6(_) => false, - } -} diff --git a/src/dump_logger.rs b/src/dump_logger.rs new file mode 100644 index 0000000..d2426e1 --- /dev/null +++ b/src/dump_logger.rs @@ -0,0 +1,71 @@ +use std::{ + os::raw::{c_char, c_int, c_void}, + sync::Mutex, +}; + +pub(crate) static DUMP_CALLBACK: Mutex> = Mutex::new(None); + +/// # Safety +/// +/// set dump log info callback. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_set_log_callback( + callback: Option, + ctx: *mut c_void, +) { + *DUMP_CALLBACK.lock().unwrap() = Some(DumpCallback(callback, ctx)); +} + +#[derive(Clone)] +pub struct DumpCallback(Option, *mut c_void); + +impl DumpCallback { + unsafe fn call(self, dump_level: c_int, info: *const c_char) { + if let Some(cb) = self.0 { + cb(dump_level, info, self.1); + } + } +} + +unsafe impl Send for DumpCallback {} +unsafe impl Sync for DumpCallback {} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DumpLogger {} + +impl log::Log for DumpLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= log::Level::Trace + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + let current_crate_name = env!("CARGO_CRATE_NAME"); + if record.module_path().unwrap_or("").starts_with(current_crate_name) { + self.do_dump_log(record); + } + } + } + + fn flush(&self) {} +} + +impl DumpLogger { + fn do_dump_log(&self, record: &log::Record) { + let timestamp: chrono::DateTime = chrono::Local::now(); + let msg = format!( + "[{} {:<5} {}] - {}", + timestamp.format("%Y-%m-%d %H:%M:%S"), + record.level(), + record.module_path().unwrap_or(""), + record.args() + ); + let c_msg = std::ffi::CString::new(msg).unwrap(); + let ptr = c_msg.as_ptr(); + if let Some(cb) = DUMP_CALLBACK.lock().unwrap().clone() { + unsafe { + cb.call(record.level() as c_int, ptr); + } + } + } +} diff --git a/src/error.rs b/src/error.rs index e2360ed..caf26de 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,9 +3,6 @@ pub enum Error { #[error("std::ffi::NulError {0:?}")] Nul(#[from] std::ffi::NulError), - #[error("ctrlc2::Error {0:?}")] - InterruptHandler(#[from] ctrlc2::Error), - #[error(transparent)] Io(#[from] std::io::Error), @@ -15,35 +12,23 @@ pub enum Error { #[error("std::net::AddrParseError {0}")] AddrParse(#[from] std::net::AddrParseError), - #[error("smoltcp::iface::RouteTableFull {0:?}")] - RouteTableFull(#[from] smoltcp::iface::RouteTableFull), - - #[error("smoltcp::socket::tcp::RecvError {0:?}")] - Recv(#[from] smoltcp::socket::tcp::RecvError), - - #[error("smoltcp::socket::tcp::ListenError {0:?}")] - Listen(#[from] smoltcp::socket::tcp::ListenError), - - #[error("smoltcp::socket::udp::BindError {0:?}")] - Bind(#[from] smoltcp::socket::udp::BindError), - - #[error("smoltcp::socket::tcp::SendError {0:?}")] - Send(#[from] smoltcp::socket::tcp::SendError), - - #[error("smoltcp::socket::udp::SendError {0:?}")] - UdpSend(#[from] smoltcp::socket::udp::SendError), - - #[error("smoltcp::wire::Error {0:?}")] - Wire(#[from] smoltcp::wire::Error), - #[error("std::str::Utf8Error {0:?}")] Utf8(#[from] std::str::Utf8Error), #[error("TryFromSliceError {0:?}")] TryFromSlice(#[from] std::array::TryFromSliceError), - #[error("ProtoError {0:?}")] - ProtoError(#[from] trust_dns_proto::error::ProtoError), + #[error("IpStackError {0:?}")] + IpStack(#[from] ipstack::IpStackError), + + #[error("DnsProtoError {0:?}")] + DnsProto(#[from] trust_dns_proto::error::ProtoError), + + #[error("httparse::Error {0:?}")] + Httparse(#[from] httparse::Error), + + #[error("digest_auth::Error {0:?}")] + DigestAuth(#[from] digest_auth::Error), #[cfg(target_os = "android")] #[error("jni::errors::Error {0:?}")] @@ -52,18 +37,8 @@ pub enum Error { #[error("{0}")] String(String), - #[cfg(target_family = "unix")] - #[error("nix::errno::Errno {0:?}")] - OSError(#[from] nix::errno::Errno), - #[error("std::num::ParseIntError {0:?}")] IntParseError(#[from] std::num::ParseIntError), - - #[error("httparse::Error {0}")] - HttpError(#[from] httparse::Error), - - #[error("digest_auth::Error {0}")] - DigestAuthError(#[from] digest_auth::Error), } impl From<&str> for Error { diff --git a/src/http.rs b/src/http.rs index 81b05c5..c04f642 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,22 +1,20 @@ use crate::{ - error::Error, - tun2proxy::{ - ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection, - ProxyHandler, - }, + directions::{IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection}, + error::{Error, Result}, + proxy_handler::{ProxyHandler, ProxyHandlerManager}, + session_info::{IpProtocol, SessionInfo}, }; use base64::Engine; use httparse::Response; -use smoltcp::wire::IpProtocol; use socks5_impl::protocol::UserKey; use std::{ - cell::RefCell, collections::{hash_map::RandomState, HashMap, VecDeque}, iter::FromIterator, net::SocketAddr, - rc::Rc, str, + sync::Arc, }; +use tokio::sync::Mutex; use unicase::UniCase; #[derive(Eq, PartialEq, Debug)] @@ -48,10 +46,11 @@ pub struct HttpConnection { crlf_state: u8, counter: usize, skip: usize, - digest_state: Rc>>, + digest_state: Arc>>, before: bool, credentials: Option, - info: ConnectionInfo, + info: SessionInfo, + domain_name: Option, } static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate"; @@ -61,7 +60,12 @@ static TRANSFER_ENCODING: &str = "Transfer-Encoding"; static CONTENT_LENGTH: &str = "Content-Length"; impl HttpConnection { - fn new(info: &ConnectionInfo, credentials: Option, digest_state: Rc>>) -> Result { + async fn new( + info: SessionInfo, + domain_name: Option, + credentials: Option, + digest_state: Arc>>, + ) -> Result { let mut res = Self { state: HttpState::ExpectResponseHeaders, client_inbuf: VecDeque::default(), @@ -74,38 +78,50 @@ impl HttpConnection { digest_state, before: false, credentials, - info: info.clone(), + info, + domain_name, }; - res.send_tunnel_request()?; + res.send_tunnel_request().await?; Ok(res) } - fn send_tunnel_request(&mut self) -> Result<(), Error> { + async fn send_tunnel_request(&mut self) -> Result<(), Error> { + let host = if let Some(domain_name) = &self.domain_name { + format!("{}:{}", domain_name, self.info.dst.port()) + } else { + self.info.dst.to_string() + }; + self.server_outbuf.extend(b"CONNECT "); - self.server_outbuf.extend(self.info.dst.to_string().as_bytes()); + self.server_outbuf.extend(host.as_bytes()); self.server_outbuf.extend(b" HTTP/1.1\r\nHost: "); - self.server_outbuf.extend(self.info.dst.to_string().as_bytes()); + self.server_outbuf.extend(host.as_bytes()); self.server_outbuf.extend(b"\r\n"); - self.send_auth_data(if self.digest_state.borrow().is_none() { + let scheme = if self.digest_state.lock().await.is_none() { AuthenticationScheme::Basic } else { AuthenticationScheme::Digest - })?; + }; + self.send_auth_data(scheme).await?; self.server_outbuf.extend(b"\r\n"); Ok(()) } - fn send_auth_data(&mut self, scheme: AuthenticationScheme) -> Result<(), Error> { + async fn send_auth_data(&mut self, scheme: AuthenticationScheme) -> Result<()> { let Some(credentials) = &self.credentials else { return Ok(()); }; match scheme { AuthenticationScheme::Digest => { - let uri = self.info.dst.to_string(); + let uri = if let Some(domain_name) = &self.domain_name { + format!("{}:{}", domain_name, self.info.dst.port()) + } else { + self.info.dst.to_string() + }; let context = digest_auth::AuthContext::new_with_method( &credentials.username, @@ -115,8 +131,8 @@ impl HttpConnection { digest_auth::HttpMethod::CONNECT, ); - let mut state = self.digest_state.borrow_mut(); - let response = state.as_mut().unwrap().respond(&context)?; + let mut state = self.digest_state.lock().await; + let response = state.as_mut().unwrap().respond(&context).unwrap(); self.server_outbuf .extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes()); @@ -133,7 +149,8 @@ impl HttpConnection { Ok(()) } - fn state_change(&mut self) -> Result<(), Error> { + #[async_recursion::async_recursion] + async fn state_change(&mut self) -> Result<()> { match self.state { HttpState::ExpectResponseHeaders => { while self.counter < self.server_inbuf.len() { @@ -176,7 +193,7 @@ impl HttpConnection { // Connection successful self.state = HttpState::Established; self.server_inbuf.clear(); - return self.state_change(); + return self.state_change().await; } if status_code != 407 { @@ -209,7 +226,7 @@ impl HttpConnection { } // Update the digest state - self.digest_state.replace(Some(state)); + self.digest_state.lock().await.replace(state); self.before = true; let closed = match headers_map.get(&UniCase::new(CONNECTION)) { @@ -222,7 +239,7 @@ impl HttpConnection { // Reset all the buffers self.server_inbuf.clear(); self.server_outbuf.clear(); - self.send_tunnel_request()?; + self.send_tunnel_request().await?; self.state = HttpState::Reset; return Ok(()); @@ -260,7 +277,7 @@ impl HttpConnection { // Close the connection by information miss self.server_inbuf.clear(); self.server_outbuf.clear(); - self.send_tunnel_request()?; + self.send_tunnel_request().await?; self.state = HttpState::Reset; return Ok(()); @@ -271,7 +288,7 @@ impl HttpConnection { self.state = HttpState::ExpectResponse; self.skip = content_length + len; - return self.state_change(); + return self.state_change().await; } HttpState::ExpectResponse => { if self.skip > 0 { @@ -285,10 +302,10 @@ impl HttpConnection { // self.server_outbuf.append(&mut self.data_buf); // self.data_buf.clear(); - self.send_tunnel_request()?; + self.send_tunnel_request().await?; self.state = HttpState::ExpectResponseHeaders; - return self.state_change(); + return self.state_change().await; } } HttpState::Established => { @@ -299,7 +316,7 @@ impl HttpConnection { } HttpState::Reset => { self.state = HttpState::ExpectResponseHeaders; - return self.state_change(); + return self.state_change().await; } _ => {} } @@ -307,12 +324,17 @@ impl HttpConnection { } } +#[async_trait::async_trait] impl ProxyHandler for HttpConnection { - fn get_connection_info(&self) -> &ConnectionInfo { - &self.info + fn get_session_info(&self) -> SessionInfo { + self.info } - fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error> { + fn get_domain_name(&self) -> Option { + self.domain_name.clone() + } + + async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()> { let direction = event.direction; let buffer = event.buffer; match direction { @@ -324,7 +346,8 @@ impl ProxyHandler for HttpConnection { } } - self.state_change() + self.state_change().await?; + Ok(()) } fn consume_data(&mut self, dir: OutgoingDirection, size: usize) { @@ -352,16 +375,10 @@ impl ProxyHandler for HttpConnection { self.state == HttpState::Established } - fn data_len(&self, dir: Direction) -> usize { + fn data_len(&self, dir: OutgoingDirection) -> usize { match dir { - Direction::Incoming(incoming) => match incoming { - IncomingDirection::FromServer => self.server_inbuf.len(), - IncomingDirection::FromClient => self.client_inbuf.len(), - }, - Direction::Outgoing(outgoing) => match outgoing { - OutgoingDirection::ToServer => self.server_outbuf.len(), - OutgoingDirection::ToClient => self.client_outbuf.len(), - }, + OutgoingDirection::ToServer => self.server_outbuf.len(), + OutgoingDirection::ToClient => self.client_outbuf.len(), } } @@ -377,19 +394,23 @@ impl ProxyHandler for HttpConnection { pub(crate) struct HttpManager { server: SocketAddr, credentials: Option, - digest_state: Rc>>, + digest_state: Arc>>, } -impl ConnectionManager for HttpManager { - fn new_proxy_handler(&self, info: &ConnectionInfo, _: bool) -> Result, Error> { +#[async_trait::async_trait] +impl ProxyHandlerManager for HttpManager { + async fn new_proxy_handler( + &self, + info: SessionInfo, + domain_name: Option, + _udp_associate: bool, + ) -> std::io::Result>> { if info.protocol != IpProtocol::Tcp { - return Err("Invalid protocol".into()); + return Err(Error::from("Invalid protocol").into()); } - Ok(Box::new(HttpConnection::new( - info, - self.credentials.clone(), - self.digest_state.clone(), - )?)) + Ok(Arc::new(Mutex::new( + HttpConnection::new(info, domain_name, self.credentials.clone(), self.digest_state.clone()).await?, + ))) } fn get_server_addr(&self) -> SocketAddr { @@ -402,7 +423,7 @@ impl HttpManager { Self { server, credentials, - digest_state: Rc::new(RefCell::new(None)), + digest_state: Arc::new(Mutex::new(None)), } } } diff --git a/src/ios.rs b/src/ios.rs new file mode 100644 index 0000000..57ce04d --- /dev/null +++ b/src/ios.rs @@ -0,0 +1,41 @@ +#![cfg(target_os = "ios")] + +use crate::{ + args::{ArgDns, ArgProxy}, + ArgVerbosity, Args, +}; +use std::os::raw::{c_char, c_int, c_uint}; + +/// # Safety +/// +/// Run the tun2proxy component with some arguments. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_run( + proxy_url: *const c_char, + tun_fd: c_int, + tun_mtu: c_uint, + dns_over_tcp: c_char, + verbose: c_char, +) -> c_int { + use log::LevelFilter; + let log_level = if verbose != 0 { LevelFilter::Trace } else { LevelFilter::Info }; + log::set_max_level(log_level); + log::set_boxed_logger(Box::::default()).unwrap(); + + let dns = if dns_over_tcp != 0 { ArgDns::OverTcp } else { ArgDns::Direct }; + let verbosity = if verbose != 0 { ArgVerbosity::Trace } else { ArgVerbosity::Info }; + let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy = ArgProxy::from_url(proxy_url).unwrap(); + + let args = Args::new(Some(tun_fd), proxy, dns, verbosity); + + crate::api::tun2proxy_internal_run(args, tun_mtu as _) +} + +/// # Safety +/// +/// Shutdown the tun2proxy component. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_stop() -> c_int { + crate::api::tun2proxy_internal_stop() +} diff --git a/src/lib.rs b/src/lib.rs index ebbc32e..dabc4a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,171 +1,472 @@ use crate::{ - error::Error, + args::ProxyType, + directions::{IncomingDataEvent, IncomingDirection, OutgoingDirection}, http::HttpManager, - socks::SocksProxyManager, - tun2proxy::{ConnectionManager, TunToProxy}, + session_info::{IpProtocol, SessionInfo}, + virtual_dns::VirtualDns, }; -use smoltcp::wire::IpCidr; -use socks5_impl::protocol::UserKey; -use std::{ - net::{SocketAddr, ToSocketAddrs}, - rc::Rc, +pub use clap; +use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; +use proxy_handler::{ProxyHandler, ProxyHandlerManager}; +use socks::SocksProxyManager; +use std::{collections::VecDeque, future::Future, net::SocketAddr, pin::Pin, sync::Arc}; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::TcpStream, + sync::{ + mpsc::{error::SendError, Receiver, Sender}, + Mutex, + }, +}; +use tproxy_config::is_private_ip; +use udp_stream::UdpStream; +pub use { + args::{ArgVerbosity, Args}, + error::{Error, Result}, }; mod android; +mod api; +mod args; +mod directions; mod dns; -pub mod error; +mod dump_logger; +mod error; mod http; -pub mod setup; +mod ios; +mod proxy_handler; +mod session_info; mod socks; -mod tun2proxy; -pub mod util; -mod virtdevice; -mod virtdns; -#[cfg(target_os = "windows")] -mod wintuninterface; +mod virtual_dns; -#[derive(Clone, Debug)] -pub struct Proxy { - pub proxy_type: ProxyType, - pub addr: SocketAddr, - pub credentials: Option, -} +const DNS_PORT: u16 = 53; -pub enum NetworkInterface { - Named(String), - #[cfg(target_family = "unix")] - Fd(std::os::fd::RawFd), -} +const MAX_SESSIONS: u64 = 200; -impl Proxy { - pub fn from_url(s: &str) -> Result { - let e = format!("`{s}` is not a valid proxy URL"); - let url = url::Url::parse(s).map_err(|_| Error::from(&e))?; - let e = format!("`{s}` does not contain a host"); - let host = url.host_str().ok_or(Error::from(e))?; +static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +use std::sync::atomic::Ordering::Relaxed; - let mut url_host = String::from(host); - let e = format!("`{s}` does not contain a port"); - let port = url.port().ok_or(Error::from(&e))?; - url_host.push(':'); - url_host.push_str(port.to_string().as_str()); - - let e = format!("`{host}` could not be resolved"); - let mut addr_iter = url_host.to_socket_addrs().map_err(|_| Error::from(&e))?; - - let e = format!("`{host}` does not resolve to a usable IP address"); - let addr = addr_iter.next().ok_or(Error::from(&e))?; - - let credentials = if url.username() == "" && url.password().is_none() { - None - } else { - let username = String::from(url.username()); - let password = String::from(url.password().unwrap_or("")); - Some(UserKey::new(username, password)) - }; - - let scheme = url.scheme(); - - let proxy_type = match url.scheme().to_ascii_lowercase().as_str() { - "socks4" => Some(ProxyType::Socks4), - "socks5" => Some(ProxyType::Socks5), - "http" => Some(ProxyType::Http), - _ => None, - } - .ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?; - - Ok(Proxy { - proxy_type, - addr, - credentials, - }) - } -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub enum ProxyType { - Socks4, - Socks5, - Http, -} - -impl std::fmt::Display for ProxyType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ProxyType::Socks4 => write!(f, "socks4"), - ProxyType::Socks5 => write!(f, "socks5"), - ProxyType::Http => write!(f, "http"), - } - } -} - -#[derive(Default)] -pub struct Options { - virtual_dns: Option, +pub struct Builder { + device: D, mtu: Option, - dns_over_tcp: bool, - dns_addr: Option, - ipv6_enabled: bool, - pub setup: bool, - bypass: Vec, + args: Args, } -impl Options { - pub fn new() -> Self { - Options::default() +impl Builder { + pub fn new(device: D, args: Args) -> Self { + Builder { device, args, mtu: None } } - - pub fn with_virtual_dns(mut self) -> Self { - self.virtual_dns = Some(virtdns::VirtualDns::new()); - self.dns_over_tcp = false; - self - } - - pub fn with_dns_over_tcp(mut self) -> Self { - self.dns_over_tcp = true; - self.virtual_dns = None; - self - } - - pub fn with_dns_addr(mut self, addr: Option) -> Self { - self.dns_addr = addr; - self - } - - pub fn with_ipv6_enabled(mut self) -> Self { - self.ipv6_enabled = true; - self - } - - pub fn with_mtu(mut self, mtu: usize) -> Self { + pub fn mtu(mut self, mtu: usize) -> Self { self.mtu = Some(mtu); self } + pub fn build(self) -> Tun2Socks5> + Send + 'static> { + let (tx, rx) = tokio::sync::mpsc::channel::<()>(1); - pub fn with_bypass_ips<'a>(mut self, bypass_ips: impl IntoIterator) -> Self { - for bypass_ip in bypass_ips { - self.bypass.push(*bypass_ip); - } - self + Tun2Socks5(run(self.device, self.mtu.unwrap_or(1500), self.args, rx), tx) } } -pub fn tun_to_proxy<'a>(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result, Error> { - let mut ttp = TunToProxy::new(interface, options)?; - let credentials = proxy.credentials.clone(); - let server = proxy.addr; - use socks5_impl::protocol::Version::{V4, V5}; - let mgr = match proxy.proxy_type { - ProxyType::Socks4 => Rc::new(SocksProxyManager::new(server, V4, credentials)) as Rc, - ProxyType::Socks5 => Rc::new(SocksProxyManager::new(server, V5, credentials)) as Rc, - ProxyType::Http => Rc::new(HttpManager::new(server, credentials)) as Rc, - }; - ttp.set_connection_manager(Some(mgr)); - Ok(ttp) +pub struct Tun2Socks5(F, Sender<()>); + +impl Tun2Socks5 +where + F::Output: Send, +{ + pub fn start(self) -> (JoinHandle, Quit) { + let r = tokio::spawn(self.0); + (JoinHandle(r), Quit(self.1)) + } } -pub fn main_entry(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result<(), Error> { - let mut ttp = tun_to_proxy(interface, proxy, options)?; - ttp.run()?; +pub struct Quit(Sender<()>); + +impl Quit { + pub async fn trigger(&self) -> Result<(), SendError<()>> { + self.0.send(()).await + } +} + +#[repr(transparent)] +struct TokioJoinError(tokio::task::JoinError); + +impl From for crate::Result<()> { + fn from(value: TokioJoinError) -> Self { + Err(crate::Error::Io(value.0.into())) + } +} + +pub struct JoinHandle(tokio::task::JoinHandle); + +impl> Future for JoinHandle { + type Output = R; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + match std::task::ready!(Pin::new(&mut self.0).poll(cx)) { + Ok(r) => std::task::Poll::Ready(r), + Err(e) => std::task::Poll::Ready(TokioJoinError(e).into()), + } + } +} + +pub async fn run(device: D, mtu: usize, args: Args, mut quit: Receiver<()>) -> crate::Result<()> +where + D: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let server_addr = args.proxy.addr; + let key = args.proxy.credentials.clone(); + let dns_addr = args.dns_addr; + let ipv6_enabled = args.ipv6_enabled; + let virtual_dns = if args.dns == args::ArgDns::Virtual { + Some(Arc::new(Mutex::new(VirtualDns::new()))) + } else { + None + }; + + use socks5_impl::protocol::Version::{V4, V5}; + let mgr = match args.proxy.proxy_type { + ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc, + ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc, + ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc, + }; + + let mut ipstack_config = ipstack::IpStackConfig::default(); + ipstack_config.mtu(mtu as _); + ipstack_config.tcp_timeout(std::time::Duration::from_secs(600)); // 10 minutes + ipstack_config.udp_timeout(std::time::Duration::from_secs(10)); // 10 seconds + + let mut ip_stack = ipstack::IpStack::new(ipstack_config, device); + + loop { + let virtual_dns = virtual_dns.clone(); + let ip_stack_stream = tokio::select! { + _ = quit.recv() => { + log::info!(""); + log::info!("Ctrl-C recieved, exiting..."); + break; + } + ip_stack_stream = ip_stack.accept() => { + ip_stack_stream? + } + }; + match ip_stack_stream { + IpStackStream::Tcp(tcp) => { + if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { + log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); + continue; + } + log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); + let info = SessionInfo::new(tcp.local_addr(), tcp.peer_addr(), IpProtocol::Tcp); + let domain_name = if let Some(virtual_dns) = &virtual_dns { + let mut virtual_dns = virtual_dns.lock().await; + virtual_dns.touch_ip(&tcp.peer_addr().ip()); + virtual_dns.resolve_ip(&tcp.peer_addr().ip()).cloned() + } else { + None + }; + let proxy_handler = mgr.new_proxy_handler(info, domain_name, false).await?; + tokio::spawn(async move { + if let Err(err) = handle_tcp_session(tcp, server_addr, proxy_handler).await { + log::error!("{} error \"{}\"", info, err); + } + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + }); + } + IpStackStream::Udp(udp) => { + if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { + log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); + continue; + } + log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); + let mut info = SessionInfo::new(udp.local_addr(), udp.peer_addr(), IpProtocol::Udp); + if info.dst.port() == DNS_PORT { + if is_private_ip(info.dst.ip()) { + info.dst.set_ip(dns_addr); + } + if args.dns == args::ArgDns::OverTcp { + let proxy_handler = mgr.new_proxy_handler(info, None, false).await?; + tokio::spawn(async move { + if let Err(err) = handle_dns_over_tcp_session(udp, server_addr, proxy_handler, ipv6_enabled).await { + log::error!("{} error \"{}\"", info, err); + } + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + }); + continue; + } + if args.dns == args::ArgDns::Virtual { + tokio::spawn(async move { + if let Some(virtual_dns) = virtual_dns { + if let Err(err) = handle_virtual_dns_session(udp, virtual_dns).await { + log::error!("{} error \"{}\"", info, err); + } + } + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + }); + continue; + } + assert_eq!(args.dns, args::ArgDns::Direct); + } + let domain_name = if let Some(virtual_dns) = &virtual_dns { + let mut virtual_dns = virtual_dns.lock().await; + virtual_dns.touch_ip(&udp.peer_addr().ip()); + virtual_dns.resolve_ip(&udp.peer_addr().ip()).cloned() + } else { + None + }; + let proxy_handler = mgr.new_proxy_handler(info, domain_name, true).await?; + tokio::spawn(async move { + if let Err(err) = handle_udp_associate_session(udp, server_addr, proxy_handler, ipv6_enabled).await { + log::error!("{} error \"{}\"", info, err); + } + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + }); + } + _ => { + log::trace!("Unknown transport"); + continue; + } + } + } Ok(()) } + +async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>) -> crate::Result<()> { + let mut buf = [0_u8; 4096]; + loop { + let len = udp.read(&mut buf).await?; + if len == 0 { + break; + } + let (msg, qname, ip) = dns.lock().await.generate_query(&buf[..len])?; + udp.write_all(&msg).await?; + log::debug!("Virtual DNS query: {} -> {}", qname, ip); + } + Ok(()) +} + +async fn handle_tcp_session( + tcp_stack: IpStackTcpStream, + server_addr: SocketAddr, + proxy_handler: Arc>, +) -> crate::Result<()> { + let mut server = TcpStream::connect(server_addr).await?; + + let session_info = proxy_handler.lock().await.get_session_info(); + log::info!("Beginning {}", session_info); + + let _ = handle_proxy_session(&mut server, proxy_handler).await?; + + let (mut t_rx, mut t_tx) = tokio::io::split(tcp_stack); + let (mut s_rx, mut s_tx) = tokio::io::split(server); + + let result = tokio::join! { + tokio::io::copy(&mut t_rx, &mut s_tx), + tokio::io::copy(&mut s_rx, &mut t_tx), + }; + let result = match result { + (Ok(t), Ok(s)) => Ok((t, s)), + (Err(e), _) | (_, Err(e)) => Err(e), + }; + + log::info!("Ending {} with {:?}", session_info, result); + + Ok(()) +} + +async fn handle_udp_associate_session( + mut udp_stack: IpStackUdpStream, + server_addr: SocketAddr, + proxy_handler: Arc>, + ipv6_enabled: bool, +) -> crate::Result<()> { + use socks5_impl::protocol::{Address, StreamOperation, UdpHeader}; + let mut server = TcpStream::connect(server_addr).await?; + let session_info = proxy_handler.lock().await.get_session_info(); + let domain_name = proxy_handler.lock().await.get_domain_name(); + log::info!("Beginning {}", session_info); + + let udp_addr = handle_proxy_session(&mut server, proxy_handler).await?; + let udp_addr = udp_addr.ok_or("udp associate failed")?; + + let mut udp_server = UdpStream::connect(udp_addr).await?; + + let mut buf1 = [0_u8; 4096]; + let mut buf2 = [0_u8; 4096]; + loop { + tokio::select! { + len = udp_stack.read(&mut buf1) => { + let len = len?; + if len == 0 { + break; + } + let buf1 = &buf1[..len]; + + let s5addr = if let Some(domain_name) = &domain_name { + Address::DomainAddress(domain_name.clone(), session_info.dst.port()) + } else { + session_info.dst.into() + }; + + // Add SOCKS5 UDP header to the incoming data + let mut s5_udp_data = Vec::::new(); + UdpHeader::new(0, s5addr).write_to_stream(&mut s5_udp_data)?; + s5_udp_data.extend_from_slice(buf1); + + udp_server.write_all(&s5_udp_data).await?; + } + len = udp_server.read(&mut buf2) => { + let len = len?; + if len == 0 { + break; + } + let buf2 = &buf2[..len]; + + // Remove SOCKS5 UDP header from the server data + let header = UdpHeader::retrieve_from_stream(&mut &buf2[..])?; + let data = &buf2[header.len()..]; + + let buf = if session_info.dst.port() == DNS_PORT { + let mut message = dns::parse_data_to_dns_message(data, false)?; + if !ipv6_enabled { + dns::remove_ipv6_entries(&mut message); + } + message.to_vec()? + } else { + data.to_vec() + }; + + udp_stack.write_all(&buf).await?; + } + } + } + + log::info!("Ending {}", session_info); + + Ok(()) +} + +async fn handle_dns_over_tcp_session( + mut udp_stack: IpStackUdpStream, + server_addr: SocketAddr, + proxy_handler: Arc>, + ipv6_enabled: bool, +) -> crate::Result<()> { + let mut server = TcpStream::connect(server_addr).await?; + + let session_info = proxy_handler.lock().await.get_session_info(); + log::info!("Beginning {}", session_info); + + let _ = handle_proxy_session(&mut server, proxy_handler).await?; + + let mut buf1 = [0_u8; 4096]; + let mut buf2 = [0_u8; 4096]; + loop { + tokio::select! { + len = udp_stack.read(&mut buf1) => { + let len = len?; + if len == 0 { + break; + } + let buf1 = &buf1[..len]; + + _ = dns::parse_data_to_dns_message(buf1, false)?; + + // Insert the DNS message length in front of the payload + let len = u16::try_from(buf1.len())?; + let mut buf = Vec::with_capacity(std::mem::size_of::() + usize::from(len)); + buf.extend_from_slice(&len.to_be_bytes()); + buf.extend_from_slice(buf1); + + server.write_all(&buf).await?; + } + len = server.read(&mut buf2) => { + let len = len?; + if len == 0 { + break; + } + let mut buf = buf2[..len].to_vec(); + + let mut to_send: VecDeque> = VecDeque::new(); + loop { + if buf.len() < 2 { + break; + } + let len = u16::from_be_bytes([buf[0], buf[1]]) as usize; + if buf.len() < len + 2 { + break; + } + + // remove the length field + let data = buf[2..len + 2].to_vec(); + + let mut message = dns::parse_data_to_dns_message(&data, false)?; + + let name = dns::extract_domain_from_dns_message(&message)?; + let ip = dns::extract_ipaddr_from_dns_message(&message); + log::trace!("DNS over TCP query result: {} -> {:?}", name, ip); + + if !ipv6_enabled { + dns::remove_ipv6_entries(&mut message); + } + + to_send.push_back(message.to_vec()?); + if len + 2 == buf.len() { + break; + } + buf = buf[len + 2..].to_vec(); + } + + while let Some(packet) = to_send.pop_front() { + udp_stack.write_all(&packet).await?; + } + } + } + } + + log::info!("Ending {}", session_info); + + Ok(()) +} + +async fn handle_proxy_session(server: &mut TcpStream, proxy_handler: Arc>) -> crate::Result> { + let mut launched = false; + let mut proxy_handler = proxy_handler.lock().await; + let dir = OutgoingDirection::ToServer; + + loop { + if proxy_handler.connection_established() { + break; + } + + if !launched { + let data = proxy_handler.peek_data(dir).buffer; + let len = data.len(); + if len == 0 { + return Err("proxy_handler launched went wrong".into()); + } + server.write_all(data).await?; + proxy_handler.consume_data(dir, len); + + launched = true; + } + + let mut buf = [0_u8; 4096]; + let len = server.read(&mut buf).await?; + if len == 0 { + return Err("server closed accidentially".into()); + } + let event = IncomingDataEvent { + direction: IncomingDirection::FromServer, + buffer: &buf[..len], + }; + proxy_handler.push_data(event).await?; + + let data = proxy_handler.peek_data(dir).buffer; + let len = data.len(); + if len > 0 { + server.write_all(data).await?; + proxy_handler.consume_data(dir, len); + } + } + Ok(proxy_handler.get_udp_associate()) +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 06d42c4..0000000 --- a/src/main.rs +++ /dev/null @@ -1,156 +0,0 @@ -use clap::Parser; -use smoltcp::wire::IpCidr; -use std::{net::IpAddr, process::ExitCode}; -use tun2proxy::util::str_to_cidr; -use tun2proxy::{error::Error, main_entry, NetworkInterface, Options, Proxy}; - -#[cfg(target_os = "linux")] -use tun2proxy::setup::{get_default_cidrs, Setup}; - -/// Tunnel interface to proxy -#[derive(Parser)] -#[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] -struct Args { - /// Name of the tun interface - #[arg(short, long, value_name = "name", default_value = "tun0")] - tun: String, - - /// File descriptor of the tun interface - #[arg(long, value_name = "fd")] - tun_fd: Option, - - /// MTU of the tun interface (only with tunnel file descriptor) - #[arg(long, value_name = "mtu", default_value = "1500")] - tun_mtu: usize, - - /// Proxy URL in the form proto://[username[:password]@]host:port - #[arg(short, long, value_parser = Proxy::from_url, value_name = "URL")] - proxy: Proxy, - - /// DNS handling strategy - #[arg(short, long, value_name = "strategy", value_enum, default_value = "virtual")] - dns: ArgDns, - - /// DNS resolver address - #[arg(long, value_name = "IP", default_value = "8.8.8.8")] - dns_addr: IpAddr, - - /// IPv6 enabled - #[arg(short = '6', long)] - ipv6_enabled: bool, - - /// Routing and system setup - #[arg(short, long, value_name = "method", value_enum, default_value = if cfg!(target_os = "linux") { "none" } else { "auto" })] - setup: Option, - - /// IPs used in routing setup which should bypass the tunnel - #[arg(short, long, value_name = "IP|CIDR")] - bypass: Vec, - - /// Verbosity level - #[arg(short, long, value_name = "level", value_enum, default_value = "info")] - verbosity: ArgVerbosity, -} - -/// DNS query handling strategy -/// - Virtual: Intercept DNS queries and resolve them locally with a fake IP address -/// - OverTcp: Use TCP to send DNS queries to the DNS server -/// - Direct: Do not handle DNS by relying on DNS server bypassing -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] -enum ArgDns { - Virtual, - OverTcp, - Direct, -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] -enum ArgSetup { - None, - Auto, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] -enum ArgVerbosity { - Off, - Error, - Warn, - Info, - Debug, - Trace, -} - -fn main() -> ExitCode { - dotenvy::dotenv().ok(); - let args = Args::parse(); - - let default = format!("{}={:?}", module_path!(), args.verbosity); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); - - let addr = args.proxy.addr; - let proxy_type = args.proxy.proxy_type; - log::info!("Proxy {proxy_type} server: {addr}"); - - let mut options = Options::new(); - match args.dns { - ArgDns::Virtual => { - options = options.with_virtual_dns(); - } - ArgDns::OverTcp => { - options = options.with_dns_over_tcp(); - } - _ => {} - } - - options = options.with_dns_addr(Some(args.dns_addr)); - - if args.ipv6_enabled { - options = options.with_ipv6_enabled(); - } - - #[allow(unused_assignments)] - let interface = match args.tun_fd { - None => NetworkInterface::Named(args.tun.clone()), - Some(_fd) => { - options = options.with_mtu(args.tun_mtu); - #[cfg(not(target_family = "unix"))] - panic!("Not supported file descriptor"); - #[cfg(target_family = "unix")] - NetworkInterface::Fd(_fd) - } - }; - - options.setup = args.setup.map(|s| s == ArgSetup::Auto).unwrap_or(false); - - let block = || -> Result<(), Error> { - let mut bypass_ips = Vec::::new(); - for cidr_str in args.bypass { - bypass_ips.push(str_to_cidr(&cidr_str)?); - } - if bypass_ips.is_empty() { - let prefix_len = if args.proxy.addr.ip().is_ipv6() { 128 } else { 32 }; - bypass_ips.push(IpCidr::new(args.proxy.addr.ip().into(), prefix_len)) - } - - options = options.with_bypass_ips(&bypass_ips); - - #[cfg(target_os = "linux")] - { - let mut setup: Setup; - if options.setup { - setup = Setup::new(&args.tun, bypass_ips, get_default_cidrs()); - setup.configure()?; - setup.drop_privileges()?; - } - } - - main_entry(&interface, &args.proxy, options)?; - - Ok(()) - }; - if let Err(e) = block() { - log::error!("{e}"); - return ExitCode::FAILURE; - } - - ExitCode::SUCCESS -} diff --git a/src/proxy_handler.rs b/src/proxy_handler.rs new file mode 100644 index 0000000..5621347 --- /dev/null +++ b/src/proxy_handler.rs @@ -0,0 +1,30 @@ +use crate::{ + directions::{IncomingDataEvent, OutgoingDataEvent, OutgoingDirection}, + session_info::SessionInfo, +}; +use std::{net::SocketAddr, sync::Arc}; +use tokio::sync::Mutex; + +#[async_trait::async_trait] +pub(crate) trait ProxyHandler: Send + Sync { + fn get_session_info(&self) -> SessionInfo; + fn get_domain_name(&self) -> Option; + async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()>; + fn consume_data(&mut self, dir: OutgoingDirection, size: usize); + fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent; + fn connection_established(&self) -> bool; + fn data_len(&self, dir: OutgoingDirection) -> usize; + fn reset_connection(&self) -> bool; + fn get_udp_associate(&self) -> Option; +} + +#[async_trait::async_trait] +pub(crate) trait ProxyHandlerManager: Send + Sync { + async fn new_proxy_handler( + &self, + info: SessionInfo, + domain_name: Option, + udp_associate: bool, + ) -> std::io::Result>>; + fn get_server_addr(&self) -> SocketAddr; +} diff --git a/src/session_info.rs b/src/session_info.rs new file mode 100644 index 0000000..dc73cf9 --- /dev/null +++ b/src/session_info.rs @@ -0,0 +1,53 @@ +use std::net::{Ipv4Addr, SocketAddr}; + +#[allow(dead_code)] +#[derive(Hash, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)] +pub(crate) enum IpProtocol { + #[default] + Tcp, + Udp, + Icmp, + Other(u8), +} + +impl std::fmt::Display for IpProtocol { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + IpProtocol::Tcp => write!(f, "TCP"), + IpProtocol::Udp => write!(f, "UDP"), + IpProtocol::Icmp => write!(f, "ICMP"), + IpProtocol::Other(v) => write!(f, "Other({})", v), + } + } +} + +#[derive(Hash, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] +pub(crate) struct SessionInfo { + pub(crate) src: SocketAddr, + pub(crate) dst: SocketAddr, + pub(crate) protocol: IpProtocol, + id: u64, +} + +impl Default for SessionInfo { + fn default() -> Self { + let src = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); + let dst = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); + Self::new(src, dst, IpProtocol::Tcp) + } +} + +static SESSION_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); + +impl SessionInfo { + pub fn new(src: SocketAddr, dst: SocketAddr, protocol: IpProtocol) -> Self { + let id = SESSION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + Self { src, dst, protocol, id } + } +} + +impl std::fmt::Display for SessionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "#{} {} {} -> {}", self.id, self.protocol, self.src, self.dst) + } +} diff --git a/src/setup.rs b/src/setup.rs deleted file mode 100644 index c3c69f1..0000000 --- a/src/setup.rs +++ /dev/null @@ -1,337 +0,0 @@ -#![cfg(target_os = "linux")] - -use crate::error::Error; -use fork::Fork; -use smoltcp::wire::IpCidr; -use std::{ - convert::TryFrom, - ffi::OsStr, - fs, - io::BufRead, - net::{Ipv4Addr, Ipv6Addr}, - os::unix::io::RawFd, - process::{Command, Output}, - str::FromStr, -}; - -#[derive(Clone)] -pub struct Setup { - routes: Vec, - tunnel_bypass_addrs: Vec, - tun: String, - set_up: bool, - delete_proxy_routes: Vec, - child: libc::pid_t, - unmount_resolvconf: bool, - restore_resolvconf_data: Option>, -} - -pub fn get_default_cidrs() -> [IpCidr; 4] { - [ - IpCidr::new(Ipv4Addr::from_str("0.0.0.0").unwrap().into(), 1), - IpCidr::new(Ipv4Addr::from_str("128.0.0.0").unwrap().into(), 1), - IpCidr::new(Ipv6Addr::from_str("::").unwrap().into(), 1), - IpCidr::new(Ipv6Addr::from_str("8000::").unwrap().into(), 1), - ] -} - -fn run_iproute(args: I, error: &str, require_success: bool) -> Result -where - I: IntoIterator, - S: AsRef, -{ - let mut command = Command::new(""); - for (i, arg) in args.into_iter().enumerate() { - if i == 0 { - command = Command::new(arg); - } else { - command.arg(arg); - } - } - - let e = Error::from(error); - let output = command.output().map_err(|_| e)?; - if !require_success || output.status.success() { - Ok(output) - } else { - let mut args: Vec<&str> = command.get_args().map(|x| x.to_str().unwrap()).collect(); - let program = command.get_program().to_str().unwrap(); - let mut cmdline = Vec::<&str>::new(); - cmdline.push(program); - cmdline.append(&mut args); - let command = cmdline.as_slice().join(" "); - match String::from_utf8(output.stderr.clone()) { - Ok(output) => Err(format!("[{}] Command `{}` failed: {}", nix::unistd::getpid(), command, output).into()), - Err(_) => Err(format!("Command `{:?}` failed with exit code {}", command, output.status.code().unwrap()).into()), - } - } -} - -impl Setup { - pub fn new( - tun: impl Into, - tunnel_bypass_addrs: impl IntoIterator, - routes: impl IntoIterator, - ) -> Self { - let routes_cidr = routes.into_iter().collect(); - let bypass_cidrs = tunnel_bypass_addrs.into_iter().collect(); - Self { - tun: tun.into(), - tunnel_bypass_addrs: bypass_cidrs, - routes: routes_cidr, - set_up: false, - delete_proxy_routes: Vec::::new(), - child: 0, - unmount_resolvconf: false, - restore_resolvconf_data: None, - } - } - - fn bypass_cidr(cidr: &IpCidr) -> Result { - let is_ipv6 = match cidr { - IpCidr::Ipv4(_) => false, - IpCidr::Ipv6(_) => true, - }; - let route_show_args = if is_ipv6 { - ["ip", "-6", "route", "show"] - } else { - ["ip", "-4", "route", "show"] - }; - - let routes = run_iproute(route_show_args, "failed to get routing table through the ip command", true)?; - - let mut route_info = Vec::<(IpCidr, Vec)>::new(); - for line in routes.stdout.lines() { - if line.is_err() { - break; - } - let line = line.unwrap(); - if line.starts_with([' ', '\t']) { - continue; - } - - let mut split = line.split_whitespace(); - let mut dst_str = split.next().unwrap(); - if dst_str == "default" { - dst_str = if is_ipv6 { "::/0" } else { "0.0.0.0/0" } - } - - let (addr_str, prefix_len_str) = match dst_str.split_once(['/']) { - None => (dst_str, if is_ipv6 { "128" } else { "32" }), - Some((addr_str, prefix_len_str)) => (addr_str, prefix_len_str), - }; - - let cidr: IpCidr = IpCidr::new( - std::net::IpAddr::from_str(addr_str).unwrap().into(), - u8::from_str(prefix_len_str).unwrap(), - ); - let route_components: Vec = split.map(String::from).collect(); - route_info.push((cidr, route_components)) - } - - // Sort routes by prefix length, the most specific route comes first. - route_info.sort_by(|entry1, entry2| entry2.0.prefix_len().cmp(&entry1.0.prefix_len())); - - for (route_cidr, route_components) in route_info { - if !route_cidr.contains_subnet(cidr) { - continue; - } - - // The IP address is routed through a more specific route than the default route. - // In this case, there is nothing to do. - if route_cidr.prefix_len() != 0 { - break; - } - - let mut proxy_route = vec!["ip".into(), "route".into(), "add".into()]; - proxy_route.push(cidr.to_string()); - proxy_route.extend(route_components.into_iter()); - run_iproute(proxy_route, "failed to clone route for proxy", false)?; - return Ok(true); - } - Ok(false) - } - - fn write_buffer_to_fd(fd: RawFd, data: &[u8]) -> Result<(), Error> { - let mut written = 0; - loop { - if written >= data.len() { - break; - } - written += nix::unistd::write(fd, &data[written..])?; - } - Ok(()) - } - - fn write_nameserver(fd: RawFd) -> Result<(), Error> { - let data = "nameserver 198.18.0.1\n".as_bytes(); - Self::write_buffer_to_fd(fd, data)?; - nix::sys::stat::fchmod(fd, nix::sys::stat::Mode::from_bits(0o444).unwrap())?; - Ok(()) - } - - fn setup_resolv_conf(&mut self) -> Result<(), Error> { - let mut fd = nix::fcntl::open( - "/tmp/tun2proxy-resolv.conf", - nix::fcntl::OFlag::O_RDWR | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT, - nix::sys::stat::Mode::from_bits(0o644).unwrap(), - )?; - Self::write_nameserver(fd)?; - let source = format!("/proc/self/fd/{}", fd); - if Ok(()) - != nix::mount::mount( - source.as_str().into(), - "/etc/resolv.conf", - "".into(), - nix::mount::MsFlags::MS_BIND, - "".into(), - ) - { - log::warn!("failed to bind mount custom resolv.conf onto /etc/resolv.conf, resorting to direct write"); - nix::unistd::close(fd)?; - - self.restore_resolvconf_data = Some(fs::read("/etc/resolv.conf")?); - - fd = nix::fcntl::open( - "/etc/resolv.conf", - nix::fcntl::OFlag::O_WRONLY | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_TRUNC, - nix::sys::stat::Mode::from_bits(0o644).unwrap(), - )?; - Self::write_nameserver(fd)?; - } else { - self.unmount_resolvconf = true; - } - nix::unistd::close(fd)?; - Ok(()) - } - - fn add_tunnel_routes(&self) -> Result<(), Error> { - for route in &self.routes { - run_iproute( - ["ip", "route", "add", route.to_string().as_str(), "dev", self.tun.as_str()], - "failed to add route", - true, - )?; - } - Ok(()) - } - - fn shutdown(&mut self) -> Result<(), Error> { - self.set_up = false; - log::info!("[{}] Restoring network configuration", nix::unistd::getpid()); - let _ = Command::new("ip").args(["link", "del", self.tun.as_str()]).output(); - - for cidr in &self.delete_proxy_routes { - let _ = Command::new("ip").args(["route", "del", cidr.to_string().as_str()]).output(); - } - - if self.unmount_resolvconf { - nix::mount::umount("/etc/resolv.conf")?; - } - - if let Some(data) = &self.restore_resolvconf_data { - fs::write("/etc/resolv.conf", data)?; - } - Ok(()) - } - - fn setup_and_handle_signals(&mut self, read_from_child: RawFd, write_to_parent: RawFd) { - if let Err(e) = (|| -> Result<(), Error> { - nix::unistd::close(read_from_child)?; - run_iproute( - ["ip", "tuntap", "add", "name", self.tun.as_str(), "mode", "tun"], - "failed to create tunnel device", - true, - )?; - - self.set_up = true; - - run_iproute( - ["ip", "link", "set", self.tun.as_str(), "up"], - "failed to bring up tunnel device", - true, - )?; - - let mut delete_proxy_route = Vec::::new(); - for cidr in &self.tunnel_bypass_addrs { - if Self::bypass_cidr(cidr)? { - delete_proxy_route.push(*cidr); - } - } - self.delete_proxy_routes = delete_proxy_route; - self.setup_resolv_conf()?; - self.add_tunnel_routes()?; - - // Signal to child that we are done setting up everything. - if nix::unistd::write(write_to_parent, &[1])? != 1 { - return Err("Failed to write to pipe".into()); - } - nix::unistd::close(write_to_parent)?; - - // Now wait for the termination signals. - let mut mask = nix::sys::signal::SigSet::empty(); - mask.add(nix::sys::signal::SIGINT); - mask.add(nix::sys::signal::SIGTERM); - mask.add(nix::sys::signal::SIGQUIT); - mask.thread_block().unwrap(); - - let mut fd = nix::sys::signalfd::SignalFd::new(&mask).unwrap(); - loop { - let res = fd.read_signal().unwrap().unwrap(); - let signo = nix::sys::signal::Signal::try_from(res.ssi_signo as i32).unwrap(); - if signo == nix::sys::signal::SIGINT || signo == nix::sys::signal::SIGTERM || signo == nix::sys::signal::SIGQUIT { - break; - } - } - - self.shutdown()?; - Ok(()) - })() { - log::error!("{e}"); - self.shutdown().unwrap(); - }; - } - - pub fn drop_privileges(&self) -> Result<(), Error> { - // 65534 is usually the nobody user. Even in cases it is not, it is safer to use this ID - // than running with UID and GID 0. - nix::unistd::setgid(nix::unistd::Gid::from_raw(65534))?; - nix::unistd::setuid(nix::unistd::Uid::from_raw(65534))?; - - Ok(()) - } - - pub fn configure(&mut self) -> Result<(), Error> { - log::info!("[{}] Setting up network configuration", nix::unistd::getpid()); - if nix::unistd::getuid() != 0.into() { - return Err("Automatic setup requires root privileges".into()); - } - - let (read_from_child, write_to_parent) = nix::unistd::pipe()?; - match fork::fork() { - Ok(Fork::Child) => { - prctl::set_death_signal(nix::sys::signal::SIGINT as isize).unwrap(); - self.setup_and_handle_signals(read_from_child, write_to_parent); - std::process::exit(0); - } - Ok(Fork::Parent(child)) => { - self.child = child; - nix::unistd::close(write_to_parent)?; - let mut buf = [0]; - if nix::unistd::read(read_from_child, &mut buf)? != 1 { - return Err("Failed to read from pipe".into()); - } - nix::unistd::close(read_from_child)?; - - Ok(()) - } - _ => Err("Failed to fork".into()), - } - } - - pub fn restore(&mut self) -> Result<(), Error> { - nix::sys::signal::kill(nix::unistd::Pid::from_raw(self.child), nix::sys::signal::SIGINT)?; - nix::sys::wait::waitpid(nix::unistd::Pid::from_raw(self.child), None)?; - Ok(()) - } -} diff --git a/src/socks.rs b/src/socks.rs index 666eb11..90a6bcd 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -1,15 +1,14 @@ use crate::{ + directions::{IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection}, error::{Error, Result}, - tun2proxy::{ - ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection, - ProxyHandler, - }, + proxy_handler::{ProxyHandler, ProxyHandlerManager}, + session_info::SessionInfo, }; use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version}; -use std::{collections::VecDeque, convert::TryFrom, net::SocketAddr}; +use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; +use tokio::sync::Mutex; #[derive(Eq, PartialEq, Debug)] -#[allow(dead_code)] enum SocksState { ClientHello, ServerHello, @@ -21,7 +20,8 @@ enum SocksState { } struct SocksProxyImpl { - info: ConnectionInfo, + info: SessionInfo, + domain_name: Option, state: SocksState, client_inbuf: VecDeque, server_inbuf: VecDeque, @@ -34,10 +34,17 @@ struct SocksProxyImpl { } impl SocksProxyImpl { - fn new(info: &ConnectionInfo, credentials: Option, version: Version, command: protocol::Command) -> Result { + fn new( + info: SessionInfo, + domain_name: Option, + credentials: Option, + version: Version, + command: protocol::Command, + ) -> Result { let mut result = Self { - info: info.clone(), - state: SocksState::ServerHello, + info, + domain_name, + state: SocksState::ClientHello, client_inbuf: VecDeque::default(), server_inbuf: VecDeque::default(), client_outbuf: VecDeque::default(), @@ -58,16 +65,17 @@ impl SocksProxyImpl { let mut ip_vec = Vec::::new(); let mut name_vec = Vec::::new(); match &self.info.dst { - Address::SocketAddress(SocketAddr::V4(addr)) => { - ip_vec.extend(addr.ip().octets().as_ref()); + SocketAddr::V4(addr) => { + if let Some(host) = &self.domain_name { + ip_vec.extend(&[0, 0, 0, host.len() as u8]); + name_vec.extend(host.as_bytes()); + name_vec.push(0); + } else { + ip_vec.extend(addr.ip().octets().as_ref()); + } } - Address::SocketAddress(SocketAddr::V6(_)) => { - return Err("SOCKS4 does not support IPv6".into()); - } - Address::DomainAddress(host, _) => { - ip_vec.extend(&[0, 0, 0, host.len() as u8]); - name_vec.extend(host.as_bytes()); - name_vec.push(0); + SocketAddr::V6(addr) => { + return Err(format!("SOCKS4 does not support IPv6: {}", addr).into()); } } self.server_outbuf.extend(ip_vec); @@ -85,14 +93,7 @@ impl SocksProxyImpl { fn send_client_hello_socks5(&mut self) -> Result<(), Error> { let credentials = &self.credentials; - // Providing unassigned methods is supposed to bypass China's GFW. - // For details, refer to https://github.com/blechschmidt/tun2proxy/issues/35. - #[rustfmt::skip] - let mut methods = vec![ - AuthMethod::NoAuth, - AuthMethod::from(4_u8), - AuthMethod::from(100_u8), - ]; + let mut methods = vec![AuthMethod::NoAuth, AuthMethod::from(4_u8), AuthMethod::from(100_u8)]; if credentials.is_some() { methods.push(AuthMethod::UserPass); } @@ -113,29 +114,29 @@ impl SocksProxyImpl { Ok(()) } - fn receive_server_hello_socks4(&mut self) -> Result<(), Error> { + fn receive_server_hello_socks4(&mut self) -> std::io::Result<()> { if self.server_inbuf.len() < 8 { return Ok(()); } if self.server_inbuf[1] != 0x5a { - return Err("SOCKS4 server replied with an unexpected reply code.".into()); + return Err(crate::Error::from("SOCKS4 server replied with an unexpected reply code.").into()); } self.server_inbuf.drain(0..8); self.state = SocksState::Established; - self.state_change() + Ok(()) } - fn receive_server_hello_socks5(&mut self) -> Result<(), Error> { + fn receive_server_hello_socks5(&mut self) -> std::io::Result<()> { let response = handshake::Response::retrieve_from_stream(&mut self.server_inbuf.clone()); - if let Err(e) = &response { + if let Err(e) = response { if e.kind() == std::io::ErrorKind::UnexpectedEof { log::trace!("receive_server_hello_socks5 needs more data \"{}\"...", e); return Ok(()); } else { - return Err(e.to_string().into()); + return Err(e); } } let respones = response?; @@ -145,7 +146,7 @@ impl SocksProxyImpl { if auth_method != AuthMethod::NoAuth && self.credentials.is_none() || (auth_method != AuthMethod::NoAuth && auth_method != AuthMethod::UserPass) && self.credentials.is_some() { - return Err("SOCKS5 server requires an unsupported authentication method.".into()); + return Err(crate::Error::from("SOCKS5 server requires an unsupported authentication method.").into()); } self.state = if auth_method == AuthMethod::UserPass { @@ -156,75 +157,77 @@ impl SocksProxyImpl { self.state_change() } - fn receive_server_hello(&mut self) -> Result<(), Error> { + fn receive_server_hello(&mut self) -> std::io::Result<()> { match self.version { Version::V4 => self.receive_server_hello_socks4(), Version::V5 => self.receive_server_hello_socks5(), } } - fn send_auth_data(&mut self) -> Result<(), Error> { + fn send_auth_data(&mut self) -> std::io::Result<()> { let tmp = UserKey::default(); let credentials = self.credentials.as_ref().unwrap_or(&tmp); let request = password_method::Request::new(&credentials.username, &credentials.password); request.write_to_stream(&mut self.server_outbuf)?; self.state = SocksState::ReceiveAuthResponse; - self.state_change() + Ok(()) } - fn receive_auth_data(&mut self) -> Result<(), Error> { + fn receive_auth_data(&mut self) -> std::io::Result<()> { use password_method::Response; let response = Response::retrieve_from_stream(&mut self.server_inbuf.clone()); - if let Err(e) = &response { + if let Err(e) = response { if e.kind() == std::io::ErrorKind::UnexpectedEof { log::trace!("receive_auth_data needs more data \"{}\"...", e); return Ok(()); } else { - return Err(e.to_string().into()); + return Err(e); } } let response = response?; self.server_inbuf.drain(0..response.len()); if response.status != password_method::Status::Succeeded { - return Err(format!("SOCKS authentication failed: {:?}", response.status).into()); + return Err(crate::Error::from(format!("SOCKS authentication failed: {:?}", response.status)).into()); } self.state = SocksState::SendRequest; self.state_change() } - fn send_request_socks5(&mut self) -> Result<(), Error> { + fn send_request_socks5(&mut self) -> std::io::Result<()> { let addr = if self.command == protocol::Command::UdpAssociate { Address::unspecified() + } else if let Some(domain_name) = &self.domain_name { + Address::DomainAddress(domain_name.clone(), self.info.dst.port()) } else { - self.info.dst.clone() + self.info.dst.into() }; protocol::Request::new(self.command, addr).write_to_stream(&mut self.server_outbuf)?; self.state = SocksState::ReceiveResponse; - self.state_change() + Ok(()) } - fn receive_connection_status(&mut self) -> Result<(), Error> { + fn receive_connection_status(&mut self) -> std::io::Result<()> { let response = protocol::Response::retrieve_from_stream(&mut self.server_inbuf.clone()); - if let Err(e) = &response { + if let Err(e) = response { if e.kind() == std::io::ErrorKind::UnexpectedEof { log::trace!("receive_connection_status needs more data \"{}\"...", e); return Ok(()); } else { - return Err(e.to_string().into()); + return Err(e); } } let response = response?; self.server_inbuf.drain(0..response.len()); if response.reply != protocol::Reply::Succeeded { - return Err(format!("SOCKS connection failed: {}", response.reply).into()); + return Err(crate::Error::from(format!("SOCKS connection failed: {}", response.reply)).into()); } if self.command == protocol::Command::UdpAssociate { self.udp_associate = Some(SocketAddr::try_from(&response.address)?); - log::trace!("UDP associate recieved address {}", response.address); + // log::trace!("UDP associate recieved address {}", response.address); } self.state = SocksState::Established; - self.state_change() + Ok(()) } fn relay_traffic(&mut self) -> Result<(), Error> { @@ -235,31 +238,37 @@ impl SocksProxyImpl { Ok(()) } - fn state_change(&mut self) -> Result<(), Error> { + fn state_change(&mut self) -> std::io::Result<()> { match self.state { - SocksState::ServerHello => self.receive_server_hello(), + SocksState::ServerHello => self.receive_server_hello()?, - SocksState::SendAuthData => self.send_auth_data(), + SocksState::SendAuthData => self.send_auth_data()?, - SocksState::ReceiveAuthResponse => self.receive_auth_data(), + SocksState::ReceiveAuthResponse => self.receive_auth_data()?, - SocksState::SendRequest => self.send_request_socks5(), + SocksState::SendRequest => self.send_request_socks5()?, - SocksState::ReceiveResponse => self.receive_connection_status(), + SocksState::ReceiveResponse => self.receive_connection_status()?, - SocksState::Established => self.relay_traffic(), + SocksState::Established => self.relay_traffic()?, - _ => Ok(()), + _ => {} } + Ok(()) } } +#[async_trait::async_trait] impl ProxyHandler for SocksProxyImpl { - fn get_connection_info(&self) -> &ConnectionInfo { - &self.info + fn get_session_info(&self) -> SessionInfo { + self.info } - fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error> { + fn get_domain_name(&self) -> Option { + self.domain_name.clone() + } + + async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()> { let IncomingDataEvent { direction, buffer } = event; match direction { IncomingDirection::FromServer => { @@ -296,16 +305,10 @@ impl ProxyHandler for SocksProxyImpl { self.state == SocksState::Established } - fn data_len(&self, dir: Direction) -> usize { + fn data_len(&self, dir: OutgoingDirection) -> usize { match dir { - Direction::Incoming(incoming) => match incoming { - IncomingDirection::FromServer => self.server_inbuf.len(), - IncomingDirection::FromClient => self.client_inbuf.len(), - }, - Direction::Outgoing(outgoing) => match outgoing { - OutgoingDirection::ToServer => self.server_outbuf.len(), - OutgoingDirection::ToClient => self.client_outbuf.len(), - }, + OutgoingDirection::ToServer => self.server_outbuf.len(), + OutgoingDirection::ToClient => self.client_outbuf.len(), } } @@ -324,12 +327,24 @@ pub(crate) struct SocksProxyManager { version: Version, } -impl ConnectionManager for SocksProxyManager { - fn new_proxy_handler(&self, info: &ConnectionInfo, udp_associate: bool) -> Result> { +#[async_trait::async_trait] +impl ProxyHandlerManager for SocksProxyManager { + async fn new_proxy_handler( + &self, + info: SessionInfo, + domain_name: Option, + udp_associate: bool, + ) -> std::io::Result>> { use socks5_impl::protocol::Command::{Connect, UdpAssociate}; let command = if udp_associate { UdpAssociate } else { Connect }; let credentials = self.credentials.clone(); - Ok(Box::new(SocksProxyImpl::new(info, credentials, self.version, command)?)) + Ok(Arc::new(Mutex::new(SocksProxyImpl::new( + info, + domain_name, + credentials, + self.version, + command, + )?))) } fn get_server_addr(&self) -> SocketAddr { diff --git a/src/tun2proxy.rs b/src/tun2proxy.rs deleted file mode 100644 index fbad1bf..0000000 --- a/src/tun2proxy.rs +++ /dev/null @@ -1,1338 +0,0 @@ -#![allow(dead_code)] - -#[cfg(target_os = "windows")] -use crate::wintuninterface::{self, NamedPipeSource, WinTunInterface}; -use crate::{dns, error::Error, error::Result, virtdevice::VirtualTunDevice, NetworkInterface, Options}; -#[cfg(target_family = "unix")] -use mio::unix::SourceFd; -use mio::{event::Event, net::TcpStream, net::UdpSocket, Events, Interest, Poll, Token}; -#[cfg(any(target_os = "macos", target_os = "ios"))] -use smoltcp::phy::RawSocket; -#[cfg(any(target_os = "linux", target_os = "android"))] -use smoltcp::phy::TunTapInterface; -use smoltcp::{ - iface::{Config, Interface, SocketHandle, SocketSet}, - phy::{Device, Medium, RxToken, TxToken}, - socket::{tcp, tcp::State, udp, udp::UdpMetadata}, - time::Instant, - wire::{IpCidr, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket, UdpPacket, UDP_HEADER_LEN}, -}; -use socks5_impl::protocol::{Address, StreamOperation, UdpHeader}; -use std::collections::LinkedList; -#[cfg(target_family = "unix")] -use std::os::unix::io::AsRawFd; -use std::{ - collections::{HashMap, HashSet}, - convert::{From, TryFrom}, - io::{Read, Write}, - net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}, - rc::Rc, - str::FromStr, -}; - -#[derive(Hash, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] -pub(crate) struct ConnectionInfo { - pub(crate) src: SocketAddr, - pub(crate) dst: Address, - pub(crate) protocol: IpProtocol, -} - -impl Default for ConnectionInfo { - fn default() -> Self { - Self { - src: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0), - dst: Address::unspecified(), - protocol: IpProtocol::Tcp, - } - } -} - -impl ConnectionInfo { - pub fn new(src: SocketAddr, dst: Address, protocol: IpProtocol) -> Self { - Self { src, dst, protocol } - } - - fn to_named(&self, name: String) -> Self { - let mut result = self.clone(); - result.dst = Address::from((name, result.dst.port())); - log::trace!("{} replace dst \"{}\" -> \"{}\"", self.protocol, self.dst, result.dst); - result - } -} - -impl std::fmt::Display for ConnectionInfo { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{} {} -> {}", self.protocol, self.src, self.dst) - } -} - -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub(crate) enum IncomingDirection { - FromServer, - FromClient, -} - -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub(crate) enum OutgoingDirection { - ToServer, - ToClient, -} - -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub(crate) enum Direction { - Incoming(IncomingDirection), - Outgoing(OutgoingDirection), -} - -#[derive(Clone, Eq, PartialEq, Debug)] -pub(crate) struct DataEvent<'a, T> { - pub(crate) direction: T, - pub(crate) buffer: &'a [u8], -} - -pub(crate) type IncomingDataEvent<'a> = DataEvent<'a, IncomingDirection>; -pub(crate) type OutgoingDataEvent<'a> = DataEvent<'a, OutgoingDirection>; - -fn get_transport_info( - protocol: IpProtocol, - transport_offset: usize, - packet: &[u8], - is_closed: &mut bool, -) -> Result<((u16, u16), bool, usize, usize)> { - match protocol { - IpProtocol::Udp => UdpPacket::new_checked(packet) - .map(|result| { - ( - (result.src_port(), result.dst_port()), - false, - transport_offset + UDP_HEADER_LEN, - packet.len() - UDP_HEADER_LEN, - ) - }) - .map_err(|e| e.into()), - IpProtocol::Tcp => TcpPacket::new_checked(packet) - .map(|result| { - *is_closed = result.fin() || result.rst(); - let header_len = result.header_len() as usize; - ( - (result.src_port(), result.dst_port()), - result.syn() && !result.ack(), - transport_offset + header_len, - packet.len() - header_len, - ) - }) - .map_err(|e| e.into()), - _ => Err(format!("Unsupported protocol {protocol} in IP packet").into()), - } -} - -fn connection_tuple(frame: &[u8], is_closed: &mut bool) -> Result<(ConnectionInfo, bool, usize, usize)> { - if let Ok(packet) = Ipv4Packet::new_checked(frame) { - let protocol = packet.next_header(); - - let mut a = [0_u8; 4]; - a.copy_from_slice(packet.src_addr().as_bytes()); - let src_addr = IpAddr::from(a); - a.copy_from_slice(packet.dst_addr().as_bytes()); - let dst_addr = IpAddr::from(a); - let header_len = packet.header_len().into(); - - let (ports, first_packet, payload_offset, payload_size) = - get_transport_info(protocol, header_len, &frame[header_len..], is_closed)?; - let info = ConnectionInfo::new( - SocketAddr::new(src_addr, ports.0), - SocketAddr::new(dst_addr, ports.1).into(), - protocol, - ); - return Ok((info, first_packet, payload_offset, payload_size)); - } - - if let Ok(packet) = Ipv6Packet::new_checked(frame) { - // TODO: Support extension headers. - let protocol = packet.next_header(); - - let mut a = [0_u8; 16]; - a.copy_from_slice(packet.src_addr().as_bytes()); - let src_addr = IpAddr::from(a); - a.copy_from_slice(packet.dst_addr().as_bytes()); - let dst_addr = IpAddr::from(a); - let header_len = packet.header_len(); - - let (ports, first_packet, payload_offset, payload_size) = - get_transport_info(protocol, header_len, &frame[header_len..], is_closed)?; - let info = ConnectionInfo::new( - SocketAddr::new(src_addr, ports.0), - SocketAddr::new(dst_addr, ports.1).into(), - protocol, - ); - return Ok((info, first_packet, payload_offset, payload_size)); - } - Err("Neither IPv6 nor IPv4 packet".into()) -} - -const SERVER_WRITE_CLOSED: u8 = 1; -const CLIENT_WRITE_CLOSED: u8 = 2; - -const UDP_ASSO_TIMEOUT: u64 = 10; // seconds -const DNS_PORT: u16 = 53; -const IP_PACKAGE_MAX_SIZE: usize = 0xFFFF; - -struct ConnectionState { - smoltcp_handle: SocketHandle, - mio_stream: TcpStream, - token: Token, - proxy_handler: Box, - close_state: u8, - wait_read: bool, - wait_write: bool, - origin_dst: SocketAddr, - udp_acco_expiry: Option<::std::time::Instant>, - udp_socket: Option, - udp_token: Option, - udp_data_cache: LinkedList>, - dns_over_tcp_expiry: Option<::std::time::Instant>, - is_tcp_closed: bool, - continue_read: bool, -} - -pub(crate) trait ProxyHandler { - fn get_connection_info(&self) -> &ConnectionInfo; - fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error>; - fn consume_data(&mut self, dir: OutgoingDirection, size: usize); - fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent; - fn connection_established(&self) -> bool; - fn data_len(&self, dir: Direction) -> usize; - fn reset_connection(&self) -> bool; - fn get_udp_associate(&self) -> Option; -} - -pub(crate) trait ConnectionManager { - fn new_proxy_handler(&self, info: &ConnectionInfo, udp_associate: bool) -> Result>; - fn get_server_addr(&self) -> SocketAddr; -} - -const TUN_TOKEN: Token = Token(0); -const PIPE_TOKEN: Token = Token(1); -const EXIT_TRIGGER_TOKEN: Token = Token(2); -const EXIT_TOKEN: Token = Token(10); - -pub struct TunToProxy<'a> { - #[cfg(any(target_os = "linux", target_os = "android"))] - tun: TunTapInterface, - #[cfg(any(target_os = "macos", target_os = "ios"))] - tun: RawSocket, - #[cfg(target_os = "windows")] - tun: WinTunInterface, - poll: Poll, - iface: Interface, - connection_map: HashMap, - connection_manager: Option>, - next_token_seed: usize, - sockets: SocketSet<'a>, - device: VirtualTunDevice, - options: Options, - write_sockets: HashSet, - #[cfg(target_family = "unix")] - exit_receiver: mio::unix::pipe::Receiver, - #[cfg(target_family = "unix")] - exit_trigger: Option, - #[cfg(target_os = "windows")] - exit_receiver: mio::windows::NamedPipe, - #[cfg(target_os = "windows")] - exit_trigger: Option, -} - -impl<'a> TunToProxy<'a> { - pub fn new(_interface: &NetworkInterface, options: Options) -> Result { - #[cfg(any(target_os = "linux", target_os = "android"))] - let tun = match _interface { - NetworkInterface::Named(name) => TunTapInterface::new(name.as_str(), Medium::Ip)?, - NetworkInterface::Fd(fd) => TunTapInterface::from_fd(*fd, Medium::Ip, options.mtu.unwrap_or(1500))?, - }; - - #[cfg(any(target_os = "macos", target_os = "ios"))] - let tun = match _interface { - NetworkInterface::Named(name) => RawSocket::new(name.as_str(), Medium::Ip)?, - NetworkInterface::Fd(_fd) => panic!("Not supported"), - }; - - #[cfg(target_os = "windows")] - let mut tun = match _interface { - NetworkInterface::Named(name) => WinTunInterface::new(name.as_str(), Medium::Ip)?, - }; - - #[cfg(target_os = "windows")] - if options.setup { - tun.setup_config(&options.bypass, options.dns_addr)?; - } - - let poll = Poll::new()?; - - let interests = Interest::READABLE | Interest::WRITABLE; - - #[cfg(target_family = "unix")] - poll.registry().register(&mut SourceFd(&tun.as_raw_fd()), TUN_TOKEN, interests)?; - - #[cfg(target_os = "windows")] - { - poll.registry().register(&mut tun, TUN_TOKEN, interests)?; - let mut pipe = NamedPipeSource(tun.pipe_client()); - poll.registry().register(&mut pipe, PIPE_TOKEN, interests)?; - } - - #[cfg(target_family = "unix")] - let (mut exit_trigger, mut exit_receiver) = mio::unix::pipe::new()?; - #[cfg(target_family = "windows")] - let (mut exit_trigger, mut exit_receiver) = wintuninterface::pipe()?; - - poll.registry() - .register(&mut exit_trigger, EXIT_TRIGGER_TOKEN, Interest::WRITABLE)?; - poll.registry().register(&mut exit_receiver, EXIT_TOKEN, Interest::READABLE)?; - - let config = match tun.capabilities().medium { - Medium::Ethernet => Config::new(smoltcp::wire::EthernetAddress([0x02, 0, 0, 0, 0, 0x01]).into()), - Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), - Medium::Ieee802154 => todo!(), - }; - - let mut device = VirtualTunDevice::new(tun.capabilities()); - - let gateway4: Ipv4Addr = Ipv4Addr::from_str("0.0.0.1")?; - let gateway6: Ipv6Addr = Ipv6Addr::from_str("::1")?; - let mut iface = Interface::new(config, &mut device, Instant::now()); - iface.update_ip_addrs(|ip_addrs| { - ip_addrs.push(IpCidr::new(gateway4.into(), 0)).unwrap(); - ip_addrs.push(IpCidr::new(gateway6.into(), 0)).unwrap() - }); - iface.routes_mut().add_default_ipv4_route(gateway4.into())?; - iface.routes_mut().add_default_ipv6_route(gateway6.into())?; - iface.set_any_ip(true); - - let tun = Self { - tun, - poll, - iface, - connection_map: HashMap::default(), - next_token_seed: usize::from(EXIT_TOKEN), - connection_manager: None, - sockets: SocketSet::new([]), - device, - options, - write_sockets: HashSet::default(), - exit_receiver, - exit_trigger: Some(exit_trigger), - }; - Ok(tun) - } - - fn new_token(&mut self) -> Token { - self.next_token_seed += 1; - Token(self.next_token_seed) - } - - pub(crate) fn set_connection_manager(&mut self, manager: Option>) { - self.connection_manager = manager; - } - - /// Read data from virtual device (remote server) and inject it into tun interface. - fn expect_smoltcp_send(&mut self) -> Result<(), Error> { - self.iface.poll(Instant::now(), &mut self.device, &mut self.sockets); - - while let Some(vec) = self.device.exfiltrate_packet() { - let _slice = vec.as_slice(); - - // TODO: Actual write. Replace. - self.tun - .transmit(Instant::now()) - .ok_or("tx token not available")? - .consume(_slice.len(), |buf| { - buf[..].clone_from_slice(_slice); - }); - } - Ok(()) - } - - fn find_info_by_token(&self, token: Token) -> Option<&ConnectionInfo> { - self.connection_map - .iter() - .find_map(|(info, state)| if state.token == token { Some(info) } else { None }) - } - - fn find_info_by_udp_token(&self, token: Token) -> Option<&ConnectionInfo> { - self.connection_map.iter().find_map(|(info, state)| { - if let Some(udp_token) = state.udp_token { - if udp_token == token { - return Some(info); - } - } - None - }) - } - - /// Destroy connection state machine - fn remove_connection(&mut self, info: &ConnectionInfo) -> Result<(), Error> { - if let Some(mut state) = self.connection_map.remove(info) { - self.expect_smoltcp_send()?; - - { - let handle = state.smoltcp_handle; - let socket = self.sockets.get_mut::(handle); - socket.close(); - self.sockets.remove(handle); - } - - if let Err(e) = self.poll.registry().deregister(&mut state.mio_stream) { - // FIXME: The function `deregister` will frequently fail for unknown reasons. - log::trace!("{}", e); - } - - if let Some(mut udp_socket) = state.udp_socket { - if let Err(e) = self.poll.registry().deregister(&mut udp_socket) { - log::trace!("{}", e); - } - } - - if let Err(err) = state.mio_stream.shutdown(Shutdown::Both) { - log::trace!("Shutdown 0 {} error \"{}\"", info, err); - } - - log::info!("Close {}", info); - } - Ok(()) - } - - fn get_connection_manager(&self) -> Option> { - self.connection_manager.clone() - } - - /// Scan connection state machine and check if any connection should be closed. - fn check_change_close_state(&mut self, info: &ConnectionInfo) -> Result<(), Error> { - let state = match self.connection_map.get_mut(info) { - Some(state) => state, - None => return Ok(()), - }; - let mut closed_ends = 0; - let handler = state.proxy_handler.as_ref(); - if (state.close_state & SERVER_WRITE_CLOSED) == SERVER_WRITE_CLOSED - && handler.data_len(Direction::Incoming(IncomingDirection::FromServer)) == 0 - && handler.data_len(Direction::Outgoing(OutgoingDirection::ToClient)) == 0 - { - // Close tun interface - let socket = self.sockets.get_mut::(state.smoltcp_handle); - socket.close(); - - closed_ends += 1; - } - - if (state.close_state & CLIENT_WRITE_CLOSED) == CLIENT_WRITE_CLOSED - && handler.data_len(Direction::Incoming(IncomingDirection::FromClient)) == 0 - && handler.data_len(Direction::Outgoing(OutgoingDirection::ToServer)) == 0 - { - // Close remote server - if let Err(err) = state.mio_stream.shutdown(Shutdown::Write) { - log::trace!("Shutdown 1 {} error \"{}\"", info, err); - } - closed_ends += 1; - } - - if closed_ends == 2 { - // Close connection state machine - self.remove_connection(info)?; - } - Ok(()) - } - - fn tunsocket_read_and_forward(&mut self, info: &ConnectionInfo) -> Result<(), Error> { - // 1. Read data from tun and write to proxy handler (remote server). - // Scope for mutable borrow of self. - { - let state = match self.connection_map.get_mut(info) { - Some(state) => state, - None => return Ok(()), - }; - let socket = self.sockets.get_mut::(state.smoltcp_handle); - let mut error = Ok(()); - while socket.can_recv() && error.is_ok() { - let dir = Direction::Outgoing(OutgoingDirection::ToServer); - if state.proxy_handler.data_len(dir) >= IP_PACKAGE_MAX_SIZE { - break; - } - - socket.recv(|data| { - let event = IncomingDataEvent { - direction: IncomingDirection::FromClient, - buffer: data, - }; - error = state.proxy_handler.push_data(event); - (data.len(), ()) - })?; - } - - if !socket.may_recv() - && socket.state() != State::Listen - && socket.state() != State::SynSent - && socket.state() != State::SynReceived - { - // We cannot yet close the write end of the mio stream here because we may still - // need to send data. - state.close_state |= CLIENT_WRITE_CLOSED; - } - } - // 2. Write data from proxy handler (remote server) to tun. - // Expect ACKs etc. from smoltcp sockets. - self.expect_smoltcp_send()?; - - self.check_change_close_state(info)?; - - Ok(()) - } - - fn update_mio_socket_interest(poll: &mut Poll, state: &mut ConnectionState) -> Result<()> { - // Maybe we did not listen for any events before. Therefore, just swallow the error. - if let Err(err) = poll.registry().deregister(&mut state.mio_stream) { - log::trace!("{}", err); - } - - // If we do not wait for read or write events, we do not need to register them. - if !state.wait_read && !state.wait_write { - return Ok(()); - } - - // This ugliness is due to the way Interest is implemented (as a NonZeroU8 wrapper). - let interest = match (state.wait_read, state.wait_write) { - (true, false) => Interest::READABLE, - (false, true) => Interest::WRITABLE, - _ => Interest::READABLE | Interest::WRITABLE, - }; - - poll.registry().register(&mut state.mio_stream, state.token, interest)?; - Ok(()) - } - - fn preprocess_origin_connection_info(&mut self, info: ConnectionInfo) -> Result { - let origin_dst = SocketAddr::try_from(&info.dst)?; - let connection_info = match &mut self.options.virtual_dns { - None => { - let mut info = info; - let port = origin_dst.port(); - if port == DNS_PORT && info.protocol == IpProtocol::Udp && dns::addr_is_private(&origin_dst) { - let dns_addr: SocketAddr = (self.options.dns_addr.ok_or("dns_addr")?, DNS_PORT).into(); - info.dst = Address::from(dns_addr); - } - info - } - Some(virtual_dns) => { - let dst_ip = origin_dst.ip(); - virtual_dns.touch_ip(&dst_ip); - match virtual_dns.resolve_ip(&dst_ip) { - None => info, - Some(name) => info.to_named(name.clone()), - } - } - }; - Ok(connection_info) - } - - fn process_incoming_dns_over_tcp_packets( - &mut self, - manager: &Rc, - info: &ConnectionInfo, - origin_dst: SocketAddr, - payload: &[u8], - ) -> Result<()> { - _ = dns::parse_data_to_dns_message(payload, false)?; - - if !self.connection_map.contains_key(info) { - log::info!("DNS over TCP {} ({})", info, origin_dst); - - let proxy_handler = manager.new_proxy_handler(info, false)?; - let server_addr = manager.get_server_addr(); - let state = self.create_new_tcp_connection_state(server_addr, origin_dst, proxy_handler, false)?; - self.connection_map.insert(info.clone(), state); - - // TODO: Move this 3 lines to the function end? - self.expect_smoltcp_send()?; - self.tunsocket_read_and_forward(info)?; - self.write_to_server(info)?; - } else { - log::trace!("DNS over TCP subsequent packet {} ({})", info, origin_dst); - } - - // Insert the DNS message length in front of the payload - let len = u16::try_from(payload.len())?; - let mut buf = Vec::with_capacity(2 + usize::from(len)); - buf.extend_from_slice(&len.to_be_bytes()); - buf.extend_from_slice(payload); - - let err = "udp over tcp state not find"; - let state = self.connection_map.get_mut(info).ok_or(err)?; - state.dns_over_tcp_expiry = Some(Self::common_udp_life_timeout()); - - let data_event = IncomingDataEvent { - direction: IncomingDirection::FromClient, - buffer: &buf, - }; - state.proxy_handler.push_data(data_event)?; - Ok(()) - } - - fn receive_dns_over_tcp_packet_and_write_to_client(&mut self, info: &ConnectionInfo) -> Result<()> { - let err = "udp connection state not found"; - let state = self.connection_map.get_mut(info).ok_or(err)?; - assert!(state.dns_over_tcp_expiry.is_some()); - state.dns_over_tcp_expiry = Some(Self::common_udp_life_timeout()); - - let mut vecbuf = vec![]; - Self::read_data_from_tcp_stream(&mut state.mio_stream, IP_PACKAGE_MAX_SIZE, &mut state.is_tcp_closed, |data| { - vecbuf.extend_from_slice(data); - Ok(()) - })?; - - let data_event = IncomingDataEvent { - direction: IncomingDirection::FromServer, - buffer: &vecbuf, - }; - if let Err(error) = state.proxy_handler.push_data(data_event) { - log::error!("{}", error); - self.remove_connection(&info.clone())?; - return Ok(()); - } - - let dns_event = state.proxy_handler.peek_data(OutgoingDirection::ToClient); - - let mut buf = dns_event.buffer.to_vec(); - let mut to_send: LinkedList> = LinkedList::new(); - loop { - if buf.len() < 2 { - break; - } - let len = u16::from_be_bytes([buf[0], buf[1]]) as usize; - if buf.len() < len + 2 { - break; - } - let data = buf[2..len + 2].to_vec(); - - let mut message = dns::parse_data_to_dns_message(&data, false)?; - - let name = dns::extract_domain_from_dns_message(&message)?; - let ip = dns::extract_ipaddr_from_dns_message(&message); - log::trace!("DNS over TCP query result: {} -> {:?}", name, ip); - - state.proxy_handler.consume_data(OutgoingDirection::ToClient, len + 2); - - if !self.options.ipv6_enabled { - dns::remove_ipv6_entries(&mut message); - } - - to_send.push_back(message.to_vec()?); - if len + 2 == buf.len() { - break; - } - buf = buf[len + 2..].to_vec(); - } - - // Write to client - let src = state.origin_dst; - while let Some(packet) = to_send.pop_front() { - self.send_udp_packet_to_client(src, info.src, &packet)?; - } - Ok(()) - } - - fn dns_over_tcp_timeout_expired(&self, info: &ConnectionInfo) -> bool { - if let Some(state) = self.connection_map.get(info) { - if let Some(expiry) = state.dns_over_tcp_expiry { - return expiry < ::std::time::Instant::now(); - } - } - false - } - - fn clearup_expired_dns_over_tcp(&mut self) -> Result<()> { - let keys = self.connection_map.keys().cloned().collect::>(); - for key in keys { - if self.dns_over_tcp_timeout_expired(&key) { - log::trace!("DNS over TCP timeout: {}", key); - self.remove_connection(&key)?; - } - } - Ok(()) - } - - fn process_incoming_udp_packets( - &mut self, - manager: &Rc, - info: &ConnectionInfo, - origin_dst: SocketAddr, - payload: &[u8], - ) -> Result<()> { - if !self.connection_map.contains_key(info) { - log::info!("UDP associate session {} ({})", info, origin_dst); - let proxy_handler = manager.new_proxy_handler(info, true)?; - let server_addr = manager.get_server_addr(); - let state = self.create_new_tcp_connection_state(server_addr, origin_dst, proxy_handler, true)?; - self.connection_map.insert(info.clone(), state); - - self.expect_smoltcp_send()?; - self.tunsocket_read_and_forward(info)?; - self.write_to_server(info)?; - } else { - log::trace!("Subsequent udp packet {} ({})", info, origin_dst); - } - - let err = "udp associate state not find"; - let state = self.connection_map.get_mut(info).ok_or(err)?; - assert!(state.udp_acco_expiry.is_some()); - state.udp_acco_expiry = Some(Self::common_udp_life_timeout()); - - // Add SOCKS5 UDP header to the incoming data - let mut s5_udp_data = Vec::::new(); - UdpHeader::new(0, info.dst.clone()).write_to_stream(&mut s5_udp_data)?; - s5_udp_data.extend_from_slice(payload); - - if let Some(udp_associate) = state.proxy_handler.get_udp_associate() { - // UDP associate session has been established, we can send packets directly... - if let Some(socket) = state.udp_socket.as_ref() { - socket.send_to(&s5_udp_data, udp_associate)?; - } - } else { - // UDP associate tunnel not ready yet, we must cache the packets... - log::trace!("Cache udp packet {} ({})", info, origin_dst); - state.udp_data_cache.push_back(s5_udp_data); - } - Ok(()) - } - - fn process_incoming_tcp_packets( - &mut self, - first_packet: bool, - manager: &Rc, - info: &ConnectionInfo, - origin_dst: SocketAddr, - frame: &[u8], - is_closed: bool, - ) -> Result<()> { - if first_packet { - let proxy_handler = manager.new_proxy_handler(info, false)?; - let server = manager.get_server_addr(); - let state = self.create_new_tcp_connection_state(server, origin_dst, proxy_handler, false)?; - self.connection_map.insert(info.clone(), state); - - log::info!("{} ({})", info, origin_dst); - } else if !self.connection_map.contains_key(info) { - log::trace!("Drop middle session {} ({})", info, origin_dst); - return Ok(()); - } else { - log::trace!("Subsequent packet {} ({})", info, origin_dst); - } - - if let Some(state) = self.connection_map.get_mut(info) { - state.is_tcp_closed = is_closed; - } - - // Inject the packet to advance the remote proxy server smoltcp socket state - self.device.inject_packet(frame); - - // Having advanced the socket state, we expect the socket to ACK - // Exfiltrate the response packets generated by the socket and inject them - // into the tunnel interface. - self.expect_smoltcp_send()?; - - // Read from the smoltcp socket and push the data to the connection handler. - self.tunsocket_read_and_forward(info)?; - - // The connection handler builds up the connection or encapsulates the data. - // Therefore, we now expect it to write data to the server. - self.write_to_server(info)?; - Ok(()) - } - - // A raw packet was received on the tunnel interface. - fn receive_tun(&mut self, frame: &mut [u8]) -> Result<(), Error> { - let mut handler = || -> Result<(), Error> { - let mut is_closed = false; - let result = connection_tuple(frame, &mut is_closed); - if let Err(error) = result { - log::debug!("{}, ignored", error); - return Ok(()); - } - let (info, first_packet, payload_offset, payload_size) = result?; - let origin_dst = SocketAddr::try_from(&info.dst)?; - let info = self.preprocess_origin_connection_info(info)?; - - let manager = self.get_connection_manager().ok_or("get connection manager")?; - - if info.protocol == IpProtocol::Tcp { - self.process_incoming_tcp_packets(first_packet, &manager, &info, origin_dst, frame, is_closed)?; - } else if info.protocol == IpProtocol::Udp { - let port = info.dst.port(); - let payload = &frame[payload_offset..payload_offset + payload_size]; - if self.options.virtual_dns.is_some() && port == DNS_PORT { - log::info!("DNS query via virtual DNS {} ({})", info, origin_dst); - let virtual_dns = self.options.virtual_dns.as_mut().ok_or("")?; - let response = virtual_dns.receive_query(payload)?; - self.send_udp_packet_to_client(origin_dst, info.src, response.as_slice())?; - } else if self.options.dns_over_tcp && port == DNS_PORT { - self.process_incoming_dns_over_tcp_packets(&manager, &info, origin_dst, payload)?; - } else { - self.process_incoming_udp_packets(&manager, &info, origin_dst, payload)?; - } - } else { - log::warn!("Unsupported protocol: {} ({})", info, origin_dst); - } - Ok::<(), Error>(()) - }; - if let Err(error) = handler() { - log::error!("{}", error); - } - Ok(()) - } - - fn create_new_tcp_connection_state( - &mut self, - server_addr: SocketAddr, - dst: SocketAddr, - proxy_handler: Box, - udp_associate: bool, - ) -> Result { - #[cfg(any(target_os = "linux", target_os = "android"))] - let mut socket = tcp::Socket::new( - tcp::SocketBuffer::new(vec![0; 1024 * 128]), - tcp::SocketBuffer::new(vec![0; 1024 * 128]), - ); - #[cfg(any(target_os = "ios", target_os = "macos", target_os = "windows"))] - let mut socket = tcp::Socket::new( - // TODO: Look into how the buffer size affects IP header length and fragmentation - tcp::SocketBuffer::new(vec![0; 1024 * 2]), - tcp::SocketBuffer::new(vec![0; 1024 * 2]), - ); - socket.set_ack_delay(None); - socket.listen(dst)?; - let handle = self.sockets.add(socket); - - let mut client = TcpStream::connect(server_addr)?; - let token = self.new_token(); - let i = Interest::READABLE | Interest::WRITABLE; - self.poll.registry().register(&mut client, token, i)?; - - let expiry = if udp_associate { - Some(Self::common_udp_life_timeout()) - } else { - None - }; - - let (udp_socket, udp_token) = if udp_associate { - let addr = (Ipv4Addr::UNSPECIFIED, 0).into(); - let mut socket = UdpSocket::bind(addr)?; - let token = self.new_token(); - self.poll.registry().register(&mut socket, token, Interest::READABLE)?; - (Some(socket), Some(token)) - } else { - (None, None) - }; - let state = ConnectionState { - smoltcp_handle: handle, - mio_stream: client, - token, - proxy_handler, - close_state: 0, - wait_read: true, - wait_write: true, - udp_acco_expiry: expiry, - udp_socket, - udp_token, - origin_dst: dst, - udp_data_cache: LinkedList::new(), - dns_over_tcp_expiry: None, - is_tcp_closed: false, - continue_read: false, - }; - Ok(state) - } - - fn common_udp_life_timeout() -> ::std::time::Instant { - ::std::time::Instant::now() + ::std::time::Duration::from_secs(UDP_ASSO_TIMEOUT) - } - - fn udp_associate_timeout_expired(&self, info: &ConnectionInfo) -> bool { - if let Some(state) = self.connection_map.get(info) { - if let Some(expiry) = state.udp_acco_expiry { - return expiry < ::std::time::Instant::now(); - } - } - false - } - - fn tcp_is_closed(&self, info: &ConnectionInfo) -> bool { - if let Some(state) = self.connection_map.get(info) { - return state.is_tcp_closed; - } - false - } - - fn clearup_expired_connection(&mut self) -> Result<()> { - let keys = self.connection_map.keys().cloned().collect::>(); - for key in keys { - if self.udp_associate_timeout_expired(&key) { - log::trace!("UDP associate timeout: {}", key); - self.remove_connection(&key)?; - } - - if self.tcp_is_closed(&key) { - log::trace!("TCP closed: {}", key); - self.remove_connection(&key)?; - } - } - Ok(()) - } - - fn send_udp_packet_to_client(&mut self, src: SocketAddr, dst: SocketAddr, data: &[u8]) -> Result<()> { - let rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 4096]); - let tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 4096]); - let mut socket = udp::Socket::new(rx_buffer, tx_buffer); - socket.bind(src)?; - socket.send_slice(data, UdpMetadata::from(dst))?; - let handle = self.sockets.add(socket); - self.expect_smoltcp_send()?; - self.sockets.remove(handle); - Ok(()) - } - - fn write_to_server(&mut self, info: &ConnectionInfo) -> Result<(), Error> { - if let Some(state) = self.connection_map.get_mut(info) { - let event = state.proxy_handler.peek_data(OutgoingDirection::ToServer); - let buffer_size = event.buffer.len(); - if buffer_size == 0 { - state.wait_write = false; - Self::update_mio_socket_interest(&mut self.poll, state)?; - self.check_change_close_state(info)?; - return Ok(()); - } - let result = state.mio_stream.write(event.buffer); - match result { - Ok(written) => { - state.proxy_handler.consume_data(OutgoingDirection::ToServer, written); - state.wait_write = written < buffer_size; - Self::update_mio_socket_interest(&mut self.poll, state)?; - } - Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { - state.wait_write = true; - Self::update_mio_socket_interest(&mut self.poll, state)?; - } - Err(_) => { - return Ok(()); - } - } - } - self.check_change_close_state(info)?; - Ok(()) - } - - fn write_to_client(&mut self, info: &ConnectionInfo) -> Result<(), Error> { - while let Some(state) = self.connection_map.get_mut(info) { - let event = state.proxy_handler.peek_data(OutgoingDirection::ToClient); - let buflen = event.buffer.len(); - let consumed; - { - let socket = self.sockets.get_mut::(state.smoltcp_handle); - if socket.may_send() { - if let Some(virtual_dns) = &mut self.options.virtual_dns { - // Unwrapping is fine because every smoltcp socket is bound to an. - virtual_dns.touch_ip(&IpAddr::from(socket.local_endpoint().unwrap().addr)); - } - consumed = socket.send_slice(event.buffer)?; - state.proxy_handler.consume_data(OutgoingDirection::ToClient, consumed); - let token = state.token; - self.expect_smoltcp_send()?; - if consumed < buflen { - self.write_sockets.insert(token); - break; - } else { - self.write_sockets.remove(&token); - if consumed == 0 { - break; - } - } - } else { - break; - } - } - - self.check_change_close_state(info)?; - } - Ok(()) - } - - fn tun_event(&mut self, event: &Event) -> Result<(), Error> { - if event.is_readable() { - while let Some((rx_token, _)) = self.tun.receive(Instant::now()) { - rx_token.consume(|frame| self.receive_tun(frame))?; - } - } - - if event.is_writable() { - let items = self - .connection_map - .iter() - .filter(|(_, state)| state.continue_read) - .map(|(info, _)| info.clone()) - .collect::>(); - for conn_info in items { - let (success, len) = self.read_server_n_write_proxy_handler(&conn_info)?; - if !success { - return Ok(()); - } - let e = "connection state not found"; - let state = self.connection_map.get_mut(&conn_info).ok_or(e)?; - - if len == 0 || event.is_read_closed() { - state.wait_read = false; - state.close_state |= SERVER_WRITE_CLOSED; - Self::update_mio_socket_interest(&mut self.poll, state)?; - self.check_change_close_state(&conn_info)?; - self.expect_smoltcp_send()?; - } - self.write_to_client(&conn_info)?; - } - } - - #[cfg(target_os = "windows")] - if event.is_writable() { - // log::trace!("Tun writable"); - let tx_token = self.tun.transmit(Instant::now()).ok_or("tx token not available")?; - // Just consume the cached packets, do nothing else. - tx_token.consume(0, |_buf| {}); - } - Ok(()) - } - - fn pipe_event(&mut self, _event: &Event) -> Result<(), Error> { - #[cfg(target_os = "windows")] - self.tun.pipe_client_event(_event)?; - Ok(()) - } - - fn send_to_smoltcp(&mut self) -> Result<(), Error> { - for token in self.write_sockets.clone().into_iter() { - if let Some(connection) = self.find_info_by_token(token) { - let connection = connection.clone(); - if let Err(error) = self.write_to_client(&connection) { - log::error!("Write to client {}", error); - self.remove_connection(&connection)?; - } - } - } - Ok(()) - } - - fn receive_udp_packet_and_write_to_client(&mut self, info: &ConnectionInfo) -> Result<()> { - let err = "udp connection state not found"; - let state = self.connection_map.get_mut(info).ok_or(err)?; - assert!(state.udp_acco_expiry.is_some()); - state.udp_acco_expiry = Some(Self::common_udp_life_timeout()); - let mut to_send: LinkedList> = LinkedList::new(); - if let Some(udp_socket) = state.udp_socket.as_ref() { - let mut buf = [0; 1 << 16]; - // Receive UDP packet from remote SOCKS5 server - while let Ok((packet_size, _svr_addr)) = udp_socket.recv_from(&mut buf) { - let buf = buf[..packet_size].to_vec(); - let header = UdpHeader::retrieve_from_stream(&mut &buf[..])?; - - let buf = if info.dst.port() == DNS_PORT { - let mut message = dns::parse_data_to_dns_message(&buf[header.len()..], false)?; - if !self.options.ipv6_enabled { - dns::remove_ipv6_entries(&mut message); - } - message.to_vec()? - } else { - buf[header.len()..].to_vec() - }; - - // Escape the borrow checker madness - to_send.push_back(buf); - } - } - - // Write to client - let src = state.origin_dst; - while let Some(packet) = to_send.pop_front() { - self.send_udp_packet_to_client(src, info.src, &packet)?; - } - Ok(()) - } - - fn consume_cached_udp_packets(&mut self, info: &ConnectionInfo) -> Result<()> { - // Try to send the first UDP packets to remote SOCKS5 server for UDP associate session - if let Some(state) = self.connection_map.get_mut(info) { - if let Some(udp_socket) = state.udp_socket.as_ref() { - if let Some(addr) = state.proxy_handler.get_udp_associate() { - // Consume udp_data_cache data - while let Some(buf) = state.udp_data_cache.pop_front() { - udp_socket.send_to(&buf, addr)?; - } - } - } - } - Ok(()) - } - - fn read_server_n_write_proxy_handler(&mut self, conn_info: &ConnectionInfo) -> Result<(bool, usize), Error> { - let e = "connection state not found"; - let state = self.connection_map.get_mut(conn_info).ok_or(e)?; - state.continue_read = false; - - let mut vecbuf = vec![]; - use std::io::{Error, ErrorKind}; - let r = Self::read_data_from_tcp_stream(&mut state.mio_stream, IP_PACKAGE_MAX_SIZE, &mut state.is_tcp_closed, |data| { - vecbuf.extend_from_slice(data); - if vecbuf.len() >= IP_PACKAGE_MAX_SIZE { - return Err(Error::new(ErrorKind::OutOfMemory, "IP_PACKAGE_MAX_SIZE exceeded")); - } - Ok(()) - }); - let len = vecbuf.len(); - if let Err(error) = r { - if error.kind() == ErrorKind::OutOfMemory { - state.continue_read = true; - } else { - log::error!("{}", error); - self.remove_connection(conn_info)?; - return Ok((false, len)); - } - } - - let data_event = IncomingDataEvent { - direction: IncomingDirection::FromServer, - buffer: &vecbuf, - }; - if let Err(error) = state.proxy_handler.push_data(data_event) { - log::error!("{}", error); - self.remove_connection(conn_info)?; - return Ok((false, len)); - } - Ok((true, len)) - } - - fn mio_socket_event(&mut self, event: &Event) -> Result<(), Error> { - if let Some(info) = self.find_info_by_udp_token(event.token()) { - return self.receive_udp_packet_and_write_to_client(&info.clone()); - } - - let conn_info = match self.find_info_by_token(event.token()) { - Some(conn_info) => conn_info.clone(), - None => { - // We may have closed the connection in an earlier iteration over the poll events, - // e.g. because an event through the tunnel interface indicated that the connection - // should be closed. - log::trace!("Connection info not found"); - return Ok(()); - } - }; - - let e = "connection manager not found"; - let server = self.get_connection_manager().ok_or(e)?.get_server_addr(); - - let mut block = || -> Result<(), Error> { - if event.is_readable() || event.is_read_closed() { - let established = self - .connection_map - .get(&conn_info) - .ok_or("")? - .proxy_handler - .connection_established(); - if self.options.dns_over_tcp && conn_info.dst.port() == DNS_PORT && established { - self.receive_dns_over_tcp_packet_and_write_to_client(&conn_info)?; - return Ok(()); - } else { - let (success, len) = self.read_server_n_write_proxy_handler(&conn_info)?; - if !success { - return Ok(()); - } - - let e = "connection state not found"; - let state = self.connection_map.get_mut(&conn_info).ok_or(e)?; - - // The handler request for reset the server connection - if state.proxy_handler.reset_connection() { - if let Err(err) = self.poll.registry().deregister(&mut state.mio_stream) { - log::trace!("{}", err); - } - // Closes the connection with the proxy - if let Err(err) = state.mio_stream.shutdown(Shutdown::Both) { - log::trace!("Shutdown 2 error \"{}\"", err); - } - - log::info!("RESET {}", conn_info); - - state.mio_stream = TcpStream::connect(server)?; - - state.wait_read = true; - state.wait_write = true; - - Self::update_mio_socket_interest(&mut self.poll, state)?; - - return Ok(()); - } - - if len == 0 || event.is_read_closed() { - state.wait_read = false; - state.close_state |= SERVER_WRITE_CLOSED; - Self::update_mio_socket_interest(&mut self.poll, state)?; - self.check_change_close_state(&conn_info)?; - self.expect_smoltcp_send()?; - } - } - - // We have read from the proxy server and pushed the data to the connection handler. - // Thus, expect data to be processed (e.g. decapsulated) and forwarded to the client. - self.write_to_client(&conn_info)?; - - // The connection handler could have produced data that is to be written to the - // server. - self.write_to_server(&conn_info)?; - - self.consume_cached_udp_packets(&conn_info)?; - } - - if event.is_writable() { - self.write_to_server(&conn_info)?; - } - Ok::<(), Error>(()) - }; - if let Err(error) = block() { - log::error!("{}", error); - self.remove_connection(&conn_info)?; - } - Ok(()) - } - - fn read_data_from_tcp_stream( - stream: &mut dyn std::io::Read, - buffer_size: usize, - is_closed: &mut bool, - mut callback: F, - ) -> std::io::Result<()> - where - F: FnMut(&mut [u8]) -> std::io::Result<()>, - { - assert!(buffer_size > 0); - let mut tmp = vec![0_u8; buffer_size]; - loop { - match stream.read(&mut tmp) { - Ok(0) => { - // The tcp connection closed - *is_closed = true; - break; - } - Ok(read_result) => { - callback(&mut tmp[0..read_result])?; - } - Err(error) => { - if error.kind() == std::io::ErrorKind::WouldBlock { - // We have read all available data. - break; - } else if error.kind() == std::io::ErrorKind::Interrupted { - // Hardware or software interrupt, continue polling. - continue; - } else { - *is_closed = true; - return Err(error); - } - } - }; - } - Ok(()) - } - - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - fn prepare_exiting_signal_trigger(&mut self) -> Result> { - let mut exit_trigger = self.exit_trigger.take().ok_or("Already running")?; - let mut count = 0; - let handle = ctrlc2::set_handler(move || -> bool { - match exit_trigger.write(b"EXIT") { - Ok(_) => { - log::trace!("Exit signal triggered successfully"); - true - } - Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { - if count > 5 { - log::error!("Send exit signal failed 5 times, exit anyway"); - return true; // std::process::exit(1); - } - count += 1; - false - } - Err(err) => { - log::error!("Failed to send exit signal: \"{}\"", err); - true - } - } - })?; - Ok(handle) - } - - pub fn run(&mut self) -> Result<(), Error> { - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - let handle = self.prepare_exiting_signal_trigger()?; - - let mut events = Events::with_capacity(1024); - let ret = 'exit_point: loop { - if let Err(err) = self.poll.poll(&mut events, None) { - if err.kind() == std::io::ErrorKind::Interrupted { - log::debug!("Poll interrupted: \"{err}\", ignored, continue polling"); - continue; - } - break 'exit_point Err(Error::from(err)); - } - - log::trace!("Polling events count {}", events.iter().count()); - - for event in events.iter() { - match event.token() { - EXIT_TOKEN => { - if self.exiting_event_handler()? { - break 'exit_point Ok(()); - } - } - EXIT_TRIGGER_TOKEN => { - log::trace!("Exiting trigger is ready, {:?}", self.exit_trigger); - } - TUN_TOKEN => self.tun_event(event)?, - PIPE_TOKEN => self.pipe_event(event)?, - _ => self.mio_socket_event(event)?, - } - } - self.send_to_smoltcp()?; - self.clearup_expired_connection()?; - self.clearup_expired_dns_over_tcp()?; - - log::trace!("connection count: {}", self.connection_map.len()); - }; - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] - handle.join().unwrap(); - log::trace!("{:?}", ret); - ret - } - - fn exiting_event_handler(&mut self) -> Result { - let mut buffer = vec![0; 100]; - match self.exit_receiver.read(&mut buffer) { - Ok(size) => { - log::trace!("Received exit signal: {:?}", &buffer[..size]); - log::info!("Exiting tun2proxy..."); - Ok(true) - } - Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { - log::trace!("Exiting reciever is ready"); - Ok(false) - } - Err(err) => Err(err.into()), - } - } - - pub fn shutdown(&mut self) -> Result<(), Error> { - log::debug!("Shutdown tun2proxy..."); - _ = self.exit_trigger.as_mut().ok_or("Already triggered")?.write(b"EXIT")?; - Ok(()) - } -} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index dff0b53..0000000 --- a/src/util.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::error::Error; -use smoltcp::wire::IpCidr; -use std::net::IpAddr; -use std::str::FromStr; - -pub fn str_to_cidr(s: &str) -> Result { - // IpCidr's FromString implementation requires the netmask to be specified. - // Try to parse as IP address without netmask before falling back. - match IpAddr::from_str(s) { - Err(_) => (), - Ok(cidr) => { - let prefix_len = if cidr.is_ipv4() { 32 } else { 128 }; - return Ok(IpCidr::new(cidr.into(), prefix_len)); - } - }; - - let cidr = IpCidr::from_str(s); - match cidr { - Err(()) => Err("Invalid CIDR: ".into()), - Ok(cidr) => Ok(cidr), - } -} diff --git a/src/virtdevice.rs b/src/virtdevice.rs deleted file mode 100644 index 721466c..0000000 --- a/src/virtdevice.rs +++ /dev/null @@ -1,80 +0,0 @@ -use smoltcp::{ - phy::{self, Device, DeviceCapabilities}, - time::Instant, -}; - -/// Virtual device representing the remote proxy server. -#[derive(Default)] -pub struct VirtualTunDevice { - capabilities: DeviceCapabilities, - inbuf: Vec>, - outbuf: Vec>, -} - -impl VirtualTunDevice { - pub fn inject_packet(&mut self, buffer: &[u8]) { - self.inbuf.push(buffer.to_vec()); - } - - pub fn exfiltrate_packet(&mut self) -> Option> { - self.outbuf.pop() - } -} - -pub struct VirtRxToken { - buffer: Vec, -} - -impl phy::RxToken for VirtRxToken { - fn consume(mut self, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - f(&mut self.buffer[..]) - } -} - -pub struct VirtTxToken<'a>(&'a mut VirtualTunDevice); - -impl<'a> phy::TxToken for VirtTxToken<'a> { - fn consume(self, len: usize, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - let mut buffer = vec![0; len]; - let result = f(&mut buffer); - self.0.outbuf.push(buffer); - result - } -} - -impl Device for VirtualTunDevice { - type RxToken<'a> = VirtRxToken; - type TxToken<'a> = VirtTxToken<'a>; - - fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - if let Some(buffer) = self.inbuf.pop() { - let rx = Self::RxToken { buffer }; - let tx = VirtTxToken(self); - return Some((rx, tx)); - } - None - } - - fn transmit(&mut self, _timestamp: Instant) -> Option> { - return Some(VirtTxToken(self)); - } - - fn capabilities(&self) -> DeviceCapabilities { - self.capabilities.clone() - } -} - -impl VirtualTunDevice { - pub fn new(capabilities: DeviceCapabilities) -> Self { - Self { - capabilities, - ..VirtualTunDevice::default() - } - } -} diff --git a/src/virtdns.rs b/src/virtual_dns.rs similarity index 72% rename from src/virtdns.rs rename to src/virtual_dns.rs index 01f4363..be32c71 100644 --- a/src/virtdns.rs +++ b/src/virtual_dns.rs @@ -1,8 +1,5 @@ -#![allow(dead_code)] - use crate::error::Result; use hashlink::{linked_hash_map::RawEntryMut, LruCache}; -use smoltcp::wire::Ipv4Cidr; use std::{ collections::HashMap, convert::TryInto, @@ -18,6 +15,9 @@ struct NameCacheEntry { expiry: Instant, } +/// A virtual DNS server which allocates IP addresses to clients. +/// The IP addresses are in the range of private IP addresses. +/// The DNS server is implemented as a LRU cache. pub struct VirtualDns { lru_cache: LruCache, name_to_ip: HashMap, @@ -29,13 +29,16 @@ pub struct VirtualDns { impl Default for VirtualDns { fn default() -> Self { let start_addr = Ipv4Addr::from_str("198.18.0.0").unwrap(); - let cidr = Ipv4Cidr::new(start_addr.into(), 15); + let prefix_len = 15; + + let network_addr = calculate_network_addr(start_addr, prefix_len); + let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len); Self { next_addr: start_addr.into(), name_to_ip: HashMap::default(), - network_addr: IpAddr::from(cidr.network().address().into_address()), - broadcast_addr: IpAddr::from(cidr.broadcast().unwrap().into_address()), + network_addr: IpAddr::from(network_addr), + broadcast_addr: IpAddr::from(broadcast_addr), lru_cache: LruCache::new_unbounded(), } } @@ -46,13 +49,14 @@ impl VirtualDns { VirtualDns::default() } - pub fn receive_query(&mut self, data: &[u8]) -> Result> { + /// Returns the DNS response to send back to the client. + pub fn generate_query(&mut self, data: &[u8]) -> Result<(Vec, String, IpAddr)> { use crate::dns; let message = dns::parse_data_to_dns_message(data, false)?; let qname = dns::extract_domain_from_dns_message(&message)?; let ip = self.allocate_ip(qname.clone())?; let message = dns::build_dns_response(message, &qname, ip, 5)?; - Ok(message.to_vec()?) + Ok((message.to_vec()?, qname, ip)) } fn increment_ip(addr: IpAddr) -> Result { @@ -140,3 +144,30 @@ impl VirtualDns { } } } + +fn calculate_network_addr(ip: std::net::Ipv4Addr, prefix_len: u8) -> std::net::Ipv4Addr { + let mask = (!0u32) << (32 - prefix_len); + let ip_u32 = u32::from_be_bytes(ip.octets()); + std::net::Ipv4Addr::from((ip_u32 & mask).to_be_bytes()) +} + +fn calculate_broadcast_addr(ip: std::net::Ipv4Addr, prefix_len: u8) -> std::net::Ipv4Addr { + let mask = (!0u32) >> prefix_len; + let ip_u32 = u32::from_be_bytes(ip.octets()); + std::net::Ipv4Addr::from((ip_u32 | mask).to_be_bytes()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cidr_addr() { + let start_addr = Ipv4Addr::from_str("198.18.0.0").unwrap(); + let prefix_len = 15; + let network_addr = calculate_network_addr(start_addr, prefix_len); + let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len); + assert_eq!(network_addr, Ipv4Addr::from_str("198.18.0.0").unwrap()); + assert_eq!(broadcast_addr, Ipv4Addr::from_str("198.19.255.255").unwrap()); + } +} diff --git a/src/wintuninterface.rs b/src/wintuninterface.rs deleted file mode 100644 index fe9abbe..0000000 --- a/src/wintuninterface.rs +++ /dev/null @@ -1,546 +0,0 @@ -use mio::{event, windows::NamedPipe, Interest, Registry, Token}; -use smoltcp::wire::IpCidr; -use smoltcp::{ - phy::{self, Device, DeviceCapabilities, Medium}, - time::Instant, -}; -use std::{ - cell::RefCell, - fs::OpenOptions, - io::{self, Read, Write}, - net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, - os::windows::prelude::{FromRawHandle, IntoRawHandle, OpenOptionsExt}, - rc::Rc, - sync::{Arc, Mutex}, - thread::JoinHandle, - vec::Vec, -}; -use windows::{ - core::{GUID, PWSTR}, - Win32::{ - Foundation::{ERROR_BUFFER_OVERFLOW, WIN32_ERROR}, - NetworkManagement::{ - IpHelper::{ - GetAdaptersAddresses, SetInterfaceDnsSettings, DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, - DNS_SETTING_NAMESERVER, GAA_FLAG_INCLUDE_GATEWAYS, GAA_FLAG_INCLUDE_PREFIX, IF_TYPE_ETHERNET_CSMACD, IF_TYPE_IEEE80211, - IP_ADAPTER_ADDRESSES_LH, - }, - Ndis::IfOperStatusUp, - }, - Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, - Storage::FileSystem::FILE_FLAG_OVERLAPPED, - }, -}; - -fn server() -> io::Result<(NamedPipe, String)> { - use rand::Rng; - let num: u64 = rand::thread_rng().gen(); - let name = format!(r"\\.\pipe\my-pipe-{}", num); - let pipe = NamedPipe::new(&name)?; - Ok((pipe, name)) -} - -fn client(name: &str) -> io::Result { - let mut opts = OpenOptions::new(); - opts.read(true).write(true).custom_flags(FILE_FLAG_OVERLAPPED.0); - let file = opts.open(name)?; - unsafe { Ok(NamedPipe::from_raw_handle(file.into_raw_handle())) } -} - -pub(crate) fn pipe() -> io::Result<(NamedPipe, NamedPipe)> { - let (pipe, name) = server()?; - Ok((pipe, client(&name)?)) -} - -/// A virtual TUN (IP) interface. -pub struct WinTunInterface { - wintun_session: Arc, - mtu: usize, - medium: Medium, - pipe_server: Rc>, - pipe_server_cache: Rc>>, - pipe_client: Arc>, - pipe_client_cache: Arc>>, - wintun_reader_thread: Option>, - old_gateway: Option, -} - -impl event::Source for WinTunInterface { - fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { - self.pipe_server.borrow_mut().register(registry, token, interests)?; - Ok(()) - } - - fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { - self.pipe_server.borrow_mut().reregister(registry, token, interests)?; - Ok(()) - } - - fn deregister(&mut self, registry: &Registry) -> io::Result<()> { - self.pipe_server.borrow_mut().deregister(registry)?; - Ok(()) - } -} - -impl WinTunInterface { - pub fn new(tun_name: &str, medium: Medium) -> io::Result { - let wintun = unsafe { wintun::load() }.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let guid = 324435345345345345_u128; - let adapter = match wintun::Adapter::open(&wintun, tun_name) { - Ok(a) => a, - Err(_) => { - wintun::Adapter::create(&wintun, tun_name, tun_name, Some(guid)).map_err(|e| io::Error::new(io::ErrorKind::Other, e))? - } - }; - - let session = adapter - .start_session(wintun::MAX_RING_CAPACITY) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let wintun_session = Arc::new(session); - - let (pipe_server, pipe_client) = pipe()?; - - let pipe_client = Arc::new(Mutex::new(pipe_client)); - let pipe_client_cache = Arc::new(Mutex::new(Vec::new())); - - let mtu = adapter.get_mtu().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - let reader_session = wintun_session.clone(); - let pipe_client_clone = pipe_client.clone(); - let pipe_client_cache_clone = pipe_client_cache.clone(); - let reader_thread = std::thread::spawn(move || { - let block = || -> Result<(), Box> { - loop { - // Take the old data from pipe_client_cache and append the new data - let cached_data = pipe_client_cache_clone.lock()?.drain(..).collect::>(); - let bytes = if cached_data.len() >= mtu { - // if the cached data is greater than mtu, then sleep 1ms and return the data - std::thread::sleep(std::time::Duration::from_millis(1)); - cached_data - } else { - // read data from tunnel interface - let packet = reader_session.receive_blocking()?; - let bytes = packet.bytes().to_vec(); - // and append to the end of cached data - cached_data.into_iter().chain(bytes).collect::>() - }; - - if bytes.is_empty() { - continue; - } - let len = bytes.len(); - - // write data to named pipe_server - let result = { pipe_client_clone.lock()?.write(&bytes) }; - match result { - Ok(n) => { - if n < len { - log::trace!("Wintun pipe_client write data {} less than buffer {}", n, len); - pipe_client_cache_clone.lock()?.extend_from_slice(&bytes[n..]); - } - } - Err(err) if err.kind() == io::ErrorKind::WouldBlock => { - log::trace!("Wintun pipe_client write WouldBlock (1) len {}", len); - pipe_client_cache_clone.lock()?.extend_from_slice(&bytes); - } - Err(err) => log::error!("Wintun pipe_client write data len {} error \"{}\"", len, err), - } - } - }; - if let Err(err) = block() { - log::trace!("Reader {}", err); - } - }); - - Ok(WinTunInterface { - wintun_session, - mtu, - medium, - pipe_server: Rc::new(RefCell::new(pipe_server)), - pipe_server_cache: Rc::new(RefCell::new(Vec::new())), - pipe_client, - pipe_client_cache, - wintun_reader_thread: Some(reader_thread), - old_gateway: None, - }) - } - - pub fn pipe_client(&self) -> Arc> { - self.pipe_client.clone() - } - - pub fn pipe_client_event(&self, event: &event::Event) -> Result<(), io::Error> { - if event.is_readable() { - self.pipe_client_event_readable() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; - } else if event.is_writable() { - self.pipe_client_event_writable() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; - } - Ok(()) - } - - fn pipe_client_event_readable(&self) -> Result<(), Box> { - let mut reader = self.pipe_client.lock()?; - let mut buffer = vec![0; self.mtu]; - loop { - // some data arieved to pipe_client from pipe_server - match reader.read(&mut buffer[..]) { - Ok(len) => match self.wintun_session.allocate_send_packet(len as u16) { - Ok(mut write_pack) => { - write_pack.bytes_mut().copy_from_slice(&buffer[..len]); - // write data to tunnel interface - self.wintun_session.send_packet(write_pack); - } - Err(err) => { - log::error!("Wintun: failed to allocate send packet: {}", err); - } - }, - Err(err) if err.kind() == io::ErrorKind::WouldBlock => break, - Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, - Err(err) => return Err(err.into()), - } - } - Ok(()) - } - - fn pipe_client_event_writable(&self) -> Result<(), Box> { - let cache = self.pipe_client_cache.lock()?.drain(..).collect::>(); - if cache.is_empty() { - return Ok(()); - } - let len = cache.len(); - let result = self.pipe_client.lock()?.write(&cache[..]); - match result { - Ok(n) => { - if n < len { - log::trace!("Wintun pipe_client write data {} less than buffer {}", n, len); - self.pipe_client_cache.lock()?.extend_from_slice(&cache[n..]); - } - } - Err(err) if err.kind() == io::ErrorKind::WouldBlock => { - log::trace!("Wintun pipe_client write WouldBlock (2) len {}", len); - self.pipe_client_cache.lock()?.extend_from_slice(&cache); - } - Err(err) => log::error!("Wintun pipe_client write data len {} error \"{}\"", len, err), - } - Ok(()) - } - - pub fn setup_config<'a>( - &mut self, - bypass_ips: impl IntoIterator, - dns_addr: Option, - ) -> Result<(), io::Error> { - let adapter = self.wintun_session.get_adapter(); - - // Setup the adapter's address/mask/gateway - let address = "10.1.0.33".parse::().unwrap(); - let mask = "255.255.255.0".parse::().unwrap(); - let gateway = "10.1.0.1".parse::().unwrap(); - adapter - .set_network_addresses_tuple(address, mask, Some(gateway)) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - // 1. Setup the adapter's DNS - let interface = GUID::from(adapter.get_guid()); - let dns = dns_addr.unwrap_or("8.8.8.8".parse::().unwrap()); - let dns2 = "8.8.4.4".parse::().unwrap(); - set_interface_dns_settings(interface, &[dns, dns2])?; - - // 2. Route all traffic to the adapter, here the destination is adapter's gateway - // command: `route add 0.0.0.0 mask 0.0.0.0 10.1.0.1 metric 6` - let unspecified = Ipv4Addr::UNSPECIFIED.to_string(); - let gateway = gateway.to_string(); - let args = &["add", &unspecified, "mask", &unspecified, &gateway, "metric", "6"]; - run_command("route", args)?; - log::info!("route {:?}", args); - - let old_gateways = get_active_network_interface_gateways()?; - // find ipv4 gateway address, or error return - let old_gateway = old_gateways - .iter() - .find(|addr| addr.is_ipv4()) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "No ipv4 gateway found"))?; - let old_gateway = old_gateway.ip(); - self.old_gateway = Some(old_gateway); - - // 3. route the bypass ip to the old gateway - // command: `route add bypass_ip old_gateway metric 1` - for bypass_ip in bypass_ips { - let args = &["add", &bypass_ip.to_string(), &old_gateway.to_string(), "metric", "1"]; - run_command("route", args)?; - log::info!("route {:?}", args); - } - - Ok(()) - } - - pub fn restore_config(&mut self) -> Result<(), io::Error> { - if self.old_gateway.is_none() { - return Ok(()); - } - let unspecified = Ipv4Addr::UNSPECIFIED.to_string(); - - // 1. Remove current adapter's route - // command: `route delete 0.0.0.0 mask 0.0.0.0` - let args = &["delete", &unspecified, "mask", &unspecified]; - run_command("route", args)?; - - // 2. Add back the old gateway route - // command: `route add 0.0.0.0 mask 0.0.0.0 old_gateway metric 200` - let old_gateway = self.old_gateway.take().unwrap().to_string(); - let args = &["add", &unspecified, "mask", &unspecified, &old_gateway, "metric", "200"]; - run_command("route", args)?; - - Ok(()) - } -} - -impl Drop for WinTunInterface { - fn drop(&mut self) { - if let Err(e) = self.restore_config() { - log::error!("Faild to unsetup config: {}", e); - } - if let Err(e) = self.wintun_session.shutdown() { - log::error!("phy: failed to shutdown interface: {}", e); - } - if let Some(thread) = self.wintun_reader_thread.take() { - if let Err(e) = thread.join() { - log::error!("phy: failed to join reader thread: {:?}", e); - } - } - } -} - -impl Device for WinTunInterface { - type RxToken<'a> = RxToken; - type TxToken<'a> = TxToken; - - fn capabilities(&self) -> DeviceCapabilities { - let mut v = DeviceCapabilities::default(); - v.max_transmission_unit = self.mtu; - v.medium = self.medium; - v - } - - fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - let mut buffer = vec![0; self.mtu]; - match self.pipe_server.borrow_mut().read(&mut buffer[..]) { - Ok(size) => { - buffer.resize(size, 0); - let rx = RxToken { buffer }; - let tx = TxToken { - pipe_server: self.pipe_server.clone(), - pipe_server_cache: self.pipe_server_cache.clone(), - }; - Some((rx, tx)) - } - Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, - Err(err) => panic!("{}", err), - } - } - - fn transmit(&mut self, _timestamp: Instant) -> Option> { - Some(TxToken { - pipe_server: self.pipe_server.clone(), - pipe_server_cache: self.pipe_server_cache.clone(), - }) - } -} - -#[doc(hidden)] -pub struct RxToken { - buffer: Vec, -} - -impl phy::RxToken for RxToken { - fn consume(mut self, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - f(&mut self.buffer[..]) - } -} - -#[doc(hidden)] -pub struct TxToken { - pipe_server: Rc>, - pipe_server_cache: Rc>>, -} - -impl phy::TxToken for TxToken { - fn consume(self, len: usize, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - let mut buffer = vec![0; len]; - let result = f(&mut buffer); - - let buffer = self.pipe_server_cache.borrow_mut().drain(..).chain(buffer).collect::>(); - if buffer.is_empty() { - // log::trace!("Wintun TxToken (pipe_server) is empty"); - return result; - } - let len = buffer.len(); - - match self.pipe_server.borrow_mut().write(&buffer[..]) { - Ok(n) => { - if n < len { - log::trace!("Wintun TxToken (pipe_server) sent {} less than buffer len {}", n, len); - self.pipe_server_cache.borrow_mut().extend_from_slice(&buffer[n..]); - } - } - Err(err) if err.kind() == io::ErrorKind::WouldBlock => { - self.pipe_server_cache.borrow_mut().extend_from_slice(&buffer[..]); - log::trace!("Wintun TxToken (pipe_server) WouldBlock data len: {}", len) - } - Err(err) => log::error!("Wintun TxToken (pipe_server) len {} error \"{}\"", len, err), - } - result - } -} - -pub struct NamedPipeSource(pub Arc>); - -impl event::Source for NamedPipeSource { - fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { - self.0 - .lock() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? - .register(registry, token, interests) - } - - fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { - self.0 - .lock() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? - .reregister(registry, token, interests) - } - - fn deregister(&mut self, registry: &Registry) -> io::Result<()> { - self.0 - .lock() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? - .deregister(registry) - } -} - -pub(crate) fn run_command(command: &str, args: &[&str]) -> io::Result<()> { - let out = std::process::Command::new(command).args(args).output()?; - if !out.status.success() { - let err = String::from_utf8_lossy(if out.stderr.is_empty() { &out.stdout } else { &out.stderr }); - let info = format!("{} failed with: \"{}\"", command, err); - return Err(std::io::Error::new(std::io::ErrorKind::Other, info)); - } - Ok(()) -} - -pub(crate) fn set_interface_dns_settings(interface: GUID, dns: &[IpAddr]) -> io::Result<()> { - // format L"1.1.1.1 8.8.8.8", or L"1.1.1.1,8.8.8.8". - let dns = dns.iter().map(|ip| ip.to_string()).collect::>().join(","); - let dns = dns.encode_utf16().chain(std::iter::once(0)).collect::>(); - - let settings = DNS_INTERFACE_SETTINGS { - Version: DNS_INTERFACE_SETTINGS_VERSION1, - Flags: DNS_SETTING_NAMESERVER as _, - NameServer: PWSTR(dns.as_ptr() as _), - ..DNS_INTERFACE_SETTINGS::default() - }; - - unsafe { SetInterfaceDnsSettings(interface, &settings as *const _)? }; - Ok(()) -} - -pub(crate) fn get_active_network_interface_gateways() -> io::Result> { - let mut addrs = vec![]; - get_adapters_addresses(|adapter| { - if adapter.OperStatus == IfOperStatusUp && [IF_TYPE_ETHERNET_CSMACD, IF_TYPE_IEEE80211].contains(&adapter.IfType) { - let mut current_gateway = adapter.FirstGatewayAddress; - while !current_gateway.is_null() { - let gateway = unsafe { &*current_gateway }; - { - let sockaddr_ptr = gateway.Address.lpSockaddr; - let sockaddr = unsafe { &*(sockaddr_ptr as *const SOCKADDR) }; - let a = unsafe { sockaddr_to_socket_addr(sockaddr) }?; - addrs.push(a); - } - current_gateway = gateway.Next; - } - } - Ok(()) - })?; - Ok(addrs) -} - -pub(crate) fn get_adapters_addresses(mut callback: F) -> io::Result<()> -where - F: FnMut(IP_ADAPTER_ADDRESSES_LH) -> io::Result<()>, -{ - let mut size = 0; - let flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS; - let family = AF_UNSPEC.0 as u32; - - // Make an initial call to GetAdaptersAddresses to get the - // size needed into the size variable - let result = unsafe { GetAdaptersAddresses(family, flags, None, None, &mut size) }; - - if WIN32_ERROR(result) != ERROR_BUFFER_OVERFLOW { - WIN32_ERROR(result).ok()?; - } - // Allocate memory for the buffer - let mut addresses: Vec = vec![0; (size + 4) as usize]; - - // Make a second call to GetAdaptersAddresses to get the actual data we want - let result = unsafe { - let addr = Some(addresses.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH); - GetAdaptersAddresses(family, flags, None, addr, &mut size) - }; - - WIN32_ERROR(result).ok()?; - - // If successful, output some information from the data we received - let mut current_addresses = addresses.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH; - while !current_addresses.is_null() { - unsafe { - callback(*current_addresses)?; - current_addresses = (*current_addresses).Next; - } - } - Ok(()) -} - -pub(crate) unsafe fn sockaddr_to_socket_addr(sock_addr: *const SOCKADDR) -> io::Result { - let address = match (*sock_addr).sa_family { - AF_INET => sockaddr_in_to_socket_addr(&*(sock_addr as *const SOCKADDR_IN)), - AF_INET6 => sockaddr_in6_to_socket_addr(&*(sock_addr as *const SOCKADDR_IN6)), - _ => return Err(io::Error::new(io::ErrorKind::Other, "Unsupported address type")), - }; - Ok(address) -} - -pub(crate) unsafe fn sockaddr_in_to_socket_addr(sockaddr_in: &SOCKADDR_IN) -> SocketAddr { - let ip = Ipv4Addr::new( - sockaddr_in.sin_addr.S_un.S_un_b.s_b1, - sockaddr_in.sin_addr.S_un.S_un_b.s_b2, - sockaddr_in.sin_addr.S_un.S_un_b.s_b3, - sockaddr_in.sin_addr.S_un.S_un_b.s_b4, - ); - let port = u16::from_be(sockaddr_in.sin_port); - SocketAddr::new(ip.into(), port) -} - -pub(crate) unsafe fn sockaddr_in6_to_socket_addr(sockaddr_in6: &SOCKADDR_IN6) -> SocketAddr { - let ip = IpAddr::V6(Ipv6Addr::new( - u16::from_be(sockaddr_in6.sin6_addr.u.Word[0]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[1]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[2]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[3]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[4]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[5]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[6]), - u16::from_be(sockaddr_in6.sin6_addr.u.Word[7]), - )); - let port = u16::from_be(sockaddr_in6.sin6_port); - SocketAddr::new(ip, port) -} diff --git a/tests/proxy.rs b/tests/proxy.rs deleted file mode 100644 index 4dcc043..0000000 --- a/tests/proxy.rs +++ /dev/null @@ -1,151 +0,0 @@ -#[cfg(target_os = "linux")] -#[cfg(test)] -mod tests { - extern crate reqwest; - - use fork::Fork; - use nix::sys::signal; - use nix::unistd::Pid; - use serial_test::serial; - use smoltcp::wire::IpCidr; - use std::env; - - use tun2proxy::setup::{get_default_cidrs, Setup}; - use tun2proxy::util::str_to_cidr; - use tun2proxy::{main_entry, NetworkInterface, Options, Proxy, ProxyType}; - - #[derive(Clone, Debug)] - struct Test { - proxy: Proxy, - } - - static TUN_TEST_DEVICE: &str = "tun0"; - - fn proxy_from_env(env_var: &str) -> Result { - let url = env::var(env_var).map_err(|_| format!("{env_var} environment variable not found"))?; - Proxy::from_url(url.as_str()).map_err(|_| format!("{env_var} URL cannot be parsed")) - } - - fn test_from_env(env_var: &str) -> Result { - let proxy = proxy_from_env(env_var)?; - Ok(Test { proxy }) - } - - fn tests() -> [Result; 3] { - [ - test_from_env("SOCKS4_SERVER"), - test_from_env("SOCKS5_SERVER"), - test_from_env("HTTP_SERVER"), - ] - } - - #[cfg(test)] - #[ctor::ctor] - fn init() { - dotenvy::dotenv().ok(); - } - - fn request_ip_host_http() { - reqwest::blocking::get("http://1.1.1.1").expect("failed to issue HTTP request"); - } - - fn request_example_https() { - reqwest::blocking::get("https://example.org").expect("failed to issue HTTPs request"); - } - - fn run_test(filter: F, test_function: T) - where - F: Fn(&Test) -> bool, - T: Fn(), - { - for potential_test in tests() { - match potential_test { - Ok(test) => { - if !filter(&test) { - continue; - } - - let mut bypass_ips = Vec::::new(); - - match env::var("BYPASS_IP") { - Err(_) => { - let prefix_len = if test.proxy.addr.ip().is_ipv6() { 128 } else { 32 }; - bypass_ips.push(IpCidr::new(test.proxy.addr.ip().into(), prefix_len)); - } - Ok(ip_str) => bypass_ips.push(str_to_cidr(&ip_str).expect("Invalid bypass IP")), - }; - - let mut setup = Setup::new(TUN_TEST_DEVICE, bypass_ips, get_default_cidrs()); - setup.configure().unwrap(); - - match fork::fork() { - Ok(Fork::Parent(child)) => { - test_function(); - signal::kill(Pid::from_raw(child), signal::SIGINT).expect("failed to kill child"); - setup.restore().unwrap(); - } - Ok(Fork::Child) => { - prctl::set_death_signal(signal::SIGINT as isize).unwrap(); - let _ = main_entry( - &NetworkInterface::Named(TUN_TEST_DEVICE.into()), - &test.proxy, - Options::new().with_virtual_dns(), - ); - std::process::exit(0); - } - Err(_) => panic!(), - } - } - Err(_) => { - continue; - } - } - } - } - - fn require_var(var: &str) { - env::var(var).unwrap_or_else(|_| panic!("{} environment variable required", var)); - } - - #[serial] - #[test_log::test] - fn test_socks4() { - require_var("SOCKS4_SERVER"); - run_test(|test| test.proxy.proxy_type == ProxyType::Socks4, request_ip_host_http) - } - - #[serial] - #[test_log::test] - fn test_socks5() { - require_var("SOCKS5_SERVER"); - run_test(|test| test.proxy.proxy_type == ProxyType::Socks5, request_ip_host_http) - } - - #[serial] - #[test_log::test] - fn test_http() { - require_var("HTTP_SERVER"); - run_test(|test| test.proxy.proxy_type == ProxyType::Http, request_ip_host_http) - } - - #[serial] - #[test_log::test] - fn test_socks4_dns() { - require_var("SOCKS4_SERVER"); - run_test(|test| test.proxy.proxy_type == ProxyType::Socks4, request_example_https) - } - - #[serial] - #[test_log::test] - fn test_socks5_dns() { - require_var("SOCKS5_SERVER"); - run_test(|test| test.proxy.proxy_type == ProxyType::Socks5, request_example_https) - } - - #[serial] - #[test_log::test] - fn test_http_dns() { - require_var("HTTP_SERVER"); - run_test(|test| test.proxy.proxy_type == ProxyType::Http, request_example_https) - } -}