diff --git a/.dockerignore b/.dockerignore deleted file mode 120000 index 3e4e48b..0000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore \ No newline at end of file diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml deleted file mode 100644 index 1280f57..0000000 --- a/.github/workflows/auto-merge.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: Dependabot Auto Merge - -on: - pull_request_target: - types: [labeled] - -jobs: - auto: - if: github.actor == 'dependabot[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: Auto approve pull request, then squash and merge - uses: ahmadnassri/action-dependabot-auto-merge@v2 - with: - # target: minor - # here `PAT_REPO_ADMIN` is a user's passkey provided by github. - github-token: ${{ secrets.PAT_REPO_ADMIN }} diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml deleted file mode 100644 index d06ff8a..0000000 --- a/.github/workflows/close-stale-issues.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Close stale issues and PRs - -on: - schedule: - - cron: "0 0 * * *" # run a cron job every day at midnight - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - name: Close stale issues and PRs - uses: actions/stale@v9 - with: - stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' - stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' - close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' - close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' - days-before-issue-stale: 30 - days-before-pr-stale: 45 - days-before-issue-close: 5 - days-before-pr-close: 10 - stale-issue-label: 'no-issue-activity' - exempt-issue-labels: 'keep-open,awaiting-approval,work-in-progress' - stale-pr-label: 'no-pr-activity' - exempt-pr-labels: 'awaiting-approval,work-in-progress' - # only-labels: 'awaiting-feedback,awaiting-answers' diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 541e53e..10db660 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -1,5 +1,7 @@ -name: Publish Docker Images +# +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*.*.*' ] @@ -7,20 +9,12 @@ on: # 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 - # This also contains the owner, i.e. tun2proxy/tun2proxy. - IMAGE_PATH: ${{ github.repository }} - IMAGE_NAME: ${{ github.event.repository.name }} - DEFAULT_OS: scratch + 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: - name: Build and push Docker image runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - os: [ 'scratch', 'ubuntu', 'alpine' ] # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: contents: read @@ -37,36 +31,30 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - + # 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@v3 + 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 Image + - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: - # We publish the images with an OS-suffix. - # The image based on a default OS is also published without a suffix. - images: | - ${{ env.REGISTRY }}/${{ env.IMAGE_PATH }}-${{ matrix.os }} - ${{ env.DEFAULT_OS == matrix.os && format('{0}/{1}', env.REGISTRY, env.IMAGE_PATH) || '' }} - + 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@v6 + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: platforms: linux/amd64,linux/arm64 context: . - file: Dockerfile - target: ${{ env.IMAGE_NAME }}-${{ matrix.os }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 697f352..f9c20a0 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -1,5 +1,4 @@ on: - workflow_dispatch: push: tags: - "v*.*.*" @@ -16,7 +15,6 @@ jobs: attestations: write strategy: - fail-fast: false matrix: target: - x86_64-unknown-linux-gnu @@ -35,7 +33,7 @@ jobs: include: - target: x86_64-unknown-linux-gnu - host_os: ubuntu-22.04 + host_os: ubuntu-latest - target: x86_64-unknown-linux-musl host_os: ubuntu-latest - target: i686-unknown-linux-musl @@ -74,15 +72,14 @@ jobs: rustup target add ${{ matrix.target }} fi cargo install cbindgen - if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-22.04" ]]; then + if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then sudo .github/workflows/install-cross.sh fi - name: Build - if: ${{ !cancelled() }} shell: bash run: | - if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-22.04" ]]; then + if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then cross build --all-features --release --target ${{ matrix.target }} else if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then @@ -93,21 +90,21 @@ jobs: cargo build --all-features --release --target ${{ matrix.target }} fi fi - cbindgen --config cbindgen.toml -o target/tun2proxy.h + cbindgen --config cbindgen.toml -l C --cpp-compat -o target/tun2proxy-ffi.h if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then powershell -Command "(Get-Item README.md).LastWriteTime = Get-Date" powershell -Command "(Get-Item target/${{ matrix.target }}/release/wintun.dll).LastWriteTime = Get-Date" - powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, target/${{ matrix.target }}/release/udpgw-server.exe, README.md, target/tun2proxy.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip + powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, target/${{ matrix.target }}/release/udpgw-server.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy.h target/${{ matrix.target }}/release/libtun2proxy.dylib + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then ./build-aarch64-apple-ios.sh zip -r mypubdir4/tun2proxy-aarch64-apple-ios-xcframework.zip ./tun2proxy.xcframework/ ./build-apple.sh zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi - elif [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy.h target/${{ matrix.target }}/release/libtun2proxy.so + elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then ./build-android.sh cp ./tun2proxy-android-libs.zip ./mypubdir4/ @@ -115,26 +112,20 @@ jobs: fi - name: Upload artifacts - if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: bin-${{ matrix.target }} path: mypubdir4/* - name: Generate artifact attestation - if: ${{ !cancelled() }} uses: actions/attest-build-provenance@v1 with: subject-path: mypubdir4/* - name: Publish - if: ${{ !cancelled() }} uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: mypubdir4/* - - name: Abort on error - if: ${{ failure() }} - run: echo "Some of jobs failed" && false diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2e57100..13992eb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,15 +1,12 @@ name: Push or PR on: - workflow_dispatch: push: branches: - '**' pull_request: branches: - '**' - schedule: - - cron: '0 0 * * 0' # Every Sunday at midnight UTC env: CARGO_TERM_COLOR: always @@ -50,53 +47,6 @@ jobs: if: ${{ failure() }} run: echo "Some of jobs failed" && false - build_n_test_android: - strategy: - fail-fast: false - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Install cargo ndk and rust compiler for android target - if: ${{ !cancelled() }} - run: | - cargo install --locked cargo-ndk - rustup target add x86_64-linux-android - - name: clippy - if: ${{ !cancelled() }} - run: cargo ndk -t x86_64 clippy --all-features -- -D warnings - - name: Build - if: ${{ !cancelled() }} - run: | - cargo ndk -t x86_64 rustc --verbose --all-features --lib --crate-type=cdylib - - name: Abort on error - if: ${{ failure() }} - run: echo "Android build job failed" && false - - build_n_test_ios: - strategy: - fail-fast: false - runs-on: macos-latest - - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - name: Install cargo lipo and rust compiler for ios target - if: ${{ !cancelled() }} - run: | - cargo install --locked cargo-lipo - rustup target add x86_64-apple-ios aarch64-apple-ios - - name: clippy - if: ${{ !cancelled() }} - run: cargo clippy --target x86_64-apple-ios --all-features -- -D warnings - - name: Build - if: ${{ !cancelled() }} - run: | - cargo lipo --verbose --all-features - - name: Abort on error - if: ${{ failure() }} - run: echo "iOS build job failed" && false - semver: name: Check semver strategy: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 32d02f5..c274674 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,16 +12,18 @@ 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')) && github.actor != 'dependabot[bot]' && github.actor != 'github-actions[bot]' + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test') steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true - name: Populate .env env: DOTENV: ${{ secrets.DOTENV }} - run: | - echo "$DOTENV" > tests/.env - ln -s tests/.env + run: echo "$DOTENV" > .env - name: Set up Python uses: actions/setup-python@v2 @@ -38,7 +40,7 @@ jobs: - name: Build project run: cargo build --release - + - name: Run tests run: | source venv/bin/activate diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 4a7b842..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,11 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace - - repo: https://github.com/rhysd/actionlint - rev: v1.7.7 - hooks: - - id: actionlint diff --git a/Cargo.toml b/Cargo.toml index 04c93b1..f2d171d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,70 @@ [package] name = "tun2proxy" -version = "0.7.11" -edition = "2024" +version = "0.6.3" +edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" homepage = "https://github.com/tun2proxy/tun2proxy" authors = ["B. Blechschmidt", "ssrlive"] description = "Tunnel interface to proxy" readme = "README.md" -rust-version = "1.85" +rust-version = "1.80" [lib] crate-type = ["staticlib", "cdylib", "lib"] +[features] +default = ["udpgw"] +udpgw = [] + +[dependencies] +async-trait = "0.1" +base64 = { version = "0.22" } +chrono = "0.4" +clap = { version = "4", features = ["derive", "wrap_help", "color"] } +ctrlc2 = { version = "3", features = ["tokio", "termination"] } +digest_auth = "0.3" +dotenvy = "0.15" +env_logger = "0.11" +hashlink = "0.9" +hickory-proto = "0.24" +httparse = "1" +ipstack = { version = "0.1" } +log = { version = "0.4", features = ["std"] } +mimalloc = { version = "0.1", default-features = false, optional = true } +percent-encoding = "2" +socks5-impl = { version = "0.5" } +thiserror = "1" +tokio = { version = "1", features = ["full"] } +tokio-util = "0.7" +tproxy-config = { version = "6", default-features = false } +tun = { version = "0.7", features = ["async"] } +udp-stream = { version = "0.0.12", default-features = false } +unicase = "2" +url = "2" + +[target.'cfg(target_os="linux")'.dependencies] +serde = { version = "1", features = ["derive"] } +bincode = "1" + +[target.'cfg(target_os="android")'.dependencies] +android_logger = "0.14" +jni = { version = "0.21", default-features = false } + +[target.'cfg(unix)'.dependencies] +daemonize = "0.5" +nix = { version = "0.29", default-features = false, features = [ + "fs", + "socket", + "uio", +] } + +[target.'cfg(target_os = "windows")'.dependencies] +windows-service = "0.7" + +[build-dependencies] +serde_json = "1" + [[bin]] name = "tun2proxy-bin" path = "src/bin/main.rs" @@ -22,61 +74,5 @@ name = "udpgw-server" path = "src/bin/udpgw_server.rs" required-features = ["udpgw"] -[features] -default = ["udpgw"] -udpgw = [] - -[dependencies] -async-trait = "0.1" -base64easy = "0.1" -chrono = "0.4" -clap = { version = "4", features = ["derive", "wrap_help", "color"] } -ctrlc2 = { version = "3.6.5", features = ["async", "termination"] } -digest_auth = "0.3" -dotenvy = "0.15" -env_logger = "0.11" -hashlink = "0.10" -hickory-proto = "0.25" -httparse = "1" -ipstack = { version = "0.4" } -log = { version = "0.4", features = ["std"] } -mimalloc = { version = "0.1", default-features = false, optional = true } -percent-encoding = "2" -shlex = "1.3.0" -socks5-impl = { version = "0.7", default-features = false, features = [ - "tokio", -] } -thiserror = "2" -tokio = { version = "1", features = ["full"] } -tokio-util = "0.7" -tproxy-config = { version = "7", default-features = false } -tun = { version = "0.8", features = ["async"] } -udp-stream = { version = "0.0.12", default-features = false } -unicase = "2" -url = "2" - -[target.'cfg(target_os="android")'.dependencies] -android_logger = "0.15" -jni = { version = "0.21", default-features = false } - -[target.'cfg(target_os="linux")'.dependencies] -bincode = "2" -serde = { version = "1", features = ["derive"] } - -[target.'cfg(target_os="windows")'.dependencies] -windows-service = "0.8" - -[target.'cfg(unix)'.dependencies] -daemonize = "0.5" -nix = { version = "0.30", default-features = false, features = [ - "fs", - "socket", - "uio", -] } - -[build-dependencies] -chrono = "0.4" -serde_json = "1" - -# [profile.release] -# strip = "symbols" +[profile.release] +strip = "symbols" diff --git a/Dockerfile b/Dockerfile index f7aafdc..e6ad592 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,61 +1,20 @@ #################################################################################################### -# This is a multi-stage Dockerfile. -# Build with `docker buildx build -t --target .` -# For example, to build the Alpine-based image while naming it tun2proxy, run: -# `docker buildx build -t tun2proxy --target tun2proxy-alpine .` +## Builder #################################################################################################### +FROM rust:latest AS builder + +WORKDIR /worker +COPY ./ . +RUN cargo build --release + #################################################################################################### -## glibc builder +## Final image #################################################################################################### -FROM rust:latest AS glibc-builder +FROM ubuntu:latest - WORKDIR /worker - COPY ./ . - RUN cargo build --release +RUN apt update && apt install -y iproute2 && apt clean all -#################################################################################################### -## musl builder -#################################################################################################### -FROM rust:latest AS musl-builder +COPY --from=builder /worker/target/release/tun2proxy-bin /usr/bin/tun2proxy-bin - WORKDIR /worker - COPY ./ . - RUN ARCH=$(rustc -vV | sed -nE 's/host:\s*([^-]+).*/\1/p') \ - && rustup target add "$ARCH-unknown-linux-musl" \ - && cargo build --release --target "$ARCH-unknown-linux-musl" - - RUN mkdir /.etc \ - && touch /.etc/resolv.conf \ - && mkdir /.tmp \ - && chmod 777 /.tmp \ - && chmod +t /.tmp - -#################################################################################################### -## Alpine image -#################################################################################################### -FROM alpine:latest AS tun2proxy-alpine - - COPY --from=musl-builder /worker/target/*/release/tun2proxy-bin /usr/bin/tun2proxy-bin - - ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"] - -#################################################################################################### -## Ubuntu image -#################################################################################################### -FROM ubuntu:latest AS tun2proxy-ubuntu - - COPY --from=glibc-builder /worker/target/release/tun2proxy-bin /usr/bin/tun2proxy-bin - - ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"] - -#################################################################################################### -## OS-less image (default) -#################################################################################################### -FROM scratch AS tun2proxy-scratch - - COPY --from=musl-builder ./tmp /tmp - COPY --from=musl-builder ./etc /etc - COPY --from=musl-builder /worker/target/*/release/tun2proxy-bin /usr/bin/tun2proxy-bin - - ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"] +ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"] diff --git a/README.md b/README.md index e813cb0..06f0601 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -[![tun2proxy](https://socialify.git.ci/tun2proxy/tun2proxy/image?description=1&language=1&name=1&stargazers=1&theme=Light)](https://github.com/tun2proxy/tun2proxy) - # tun2proxy A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows. [![Crates.io](https://img.shields.io/crates/v/tun2proxy.svg)](https://crates.io/crates/tun2proxy) -[![tun2proxy](https://docs.rs/tun2proxy/badge.svg)](https://docs.rs/tun2proxy) +![tun2proxy](https://docs.rs/tun2proxy/badge.svg) [![Documentation](https://img.shields.io/badge/docs-release-brightgreen.svg?style=flat)](https://docs.rs/tun2proxy) [![Download](https://img.shields.io/crates/d/tun2proxy.svg)](https://crates.io/crates/tun2proxy) [![License](https://img.shields.io/crates/l/tun2proxy.svg?style=flat)](https://github.com/tun2proxy/tun2proxy/blob/master/LICENSE) @@ -29,7 +27,7 @@ cargo build --release ``` ### Building Framework for Apple Devices -To build an XCFramework for macOS and iOS, run the following: +To build an XCFramework for macOS and iOS, run the following: ``` ./build-apple.sh ``` @@ -149,8 +147,8 @@ Options: --unshare-pidfile Create a pidfile of `unshare` process when using `--unshare` -6, --ipv6-enabled IPv6 enabled -s, --setup Routing and system setup, which decides whether to setup the routing and system - configuration. This option requires root-like privileges on every platform. - It is very important on Linux, see `capabilities(7)` + configuration. This option is only available on Linux and requires root-like privileges. + See `capabilities(7)` -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] --dns-addr DNS resolver address [default: 8.8.8.8] --virtual-dns-pool IP address pool to be used by virtual DNS in CIDR notation [default: 198.18.0.0/15] @@ -172,21 +170,11 @@ Currently, tun2proxy supports HTTP, SOCKS4/SOCKS4a and SOCKS5. A proxy is suppli URL format. For example, an HTTP proxy at `1.2.3.4:3128` with a username of `john.doe` and a password of `secret` is supplied as `--proxy http://john.doe:secret@1.2.3.4:3128`. This works analogously to curl's `--proxy` argument. -## Container Support -### Docker +## Docker Support Tun2proxy can serve as a proxy for other Docker containers. To make use of that feature, first build the image: ```bash -docker buildx build -t tun2proxy . -``` - -This will build an image containing a statically linked `tun2proxy` binary (based on `musl`) without OS. - -Alternatively, you can build images based on Ubuntu or Alpine as follows: - -```bash -docker buildx build -t tun2proxy --target tun2proxy-ubuntu . -docker buildx build -t tun2proxy --target tun2proxy-alpine . +docker build -t tun2proxy . ``` Next, start a container from the tun2proxy image: @@ -197,7 +185,7 @@ docker run -d \ --sysctl net.ipv6.conf.default.disable_ipv6=0 \ --cap-add NET_ADMIN \ --name tun2proxy \ - tun2proxy --proxy proto://[username[:password]@]host:port + tun2proxy-bin --proxy proto://[username[:password]@]host:port ``` You can then provide the running container's network to another worker container by sharing the network namespace (like kubernetes sidecar): @@ -207,36 +195,6 @@ docker run -it \ --network "container:tun2proxy" \ ubuntu:latest ``` -### Docker Compose - -Create a `docker-compose.yaml` file with the following content: - -```yaml -services: - tun2proxy: - volumes: - - /dev/net/tun:/dev/net/tun - sysctls: - - net.ipv6.conf.default.disable_ipv6=0 - cap_add: - - NET_ADMIN - container_name: tun2proxy - image: ghcr.io/tun2proxy/tun2proxy-ubuntu:latest - command: --proxy proto://[username[:password]@]host:port - alpine: - stdin_open: true - tty: true - network_mode: container:tun2proxy - image: alpine:latest - command: apk add curl && curl ifconfig.icu && sleep 10 -``` - -Then run the compose file - -```bash -docker compose up -d tun2proxy -docker compose up alpine -``` ## Configuration Tips ### DNS @@ -258,10 +216,3 @@ asked to open connections to IPv6 destinations. In such a case, you can disable either through `sysctl -w net.ipv6.conf.all.disable_ipv6=1` and `sysctl -w net.ipv6.conf.default.disable_ipv6=1` or through `ip -6 route del default`, which causes the `libc` resolver (and other software) to not issue DNS AAAA requests for IPv6 addresses. - -## Contributors ✨ -Thanks goes to these wonderful people: - - - - diff --git a/build-aarch64-apple-ios-debug.sh b/build-aarch64-apple-ios-debug.sh index 6c97921..7642758 100755 --- a/build-aarch64-apple-ios-debug.sh +++ b/build-aarch64-apple-ios-debug.sh @@ -10,7 +10,7 @@ cargo build --target aarch64-apple-ios --features mimalloc echo "Generating includes..." mkdir -p target/include/ rm -rf target/include/* -cbindgen --config cbindgen.toml -o target/include/tun2proxy.h +cbindgen --config cbindgen.toml -l C --cpp-compat -o target/include/tun2proxy.h cat > target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < Result<(), Box> { - if let Ok(git_hash) = get_git_hash() { - // Set the environment variables - println!("cargo:rustc-env=GIT_HASH={}", git_hash.trim()); - } - - // Get the build time - let build_time = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); - println!("cargo:rustc-env=BUILD_TIME={build_time}"); - #[cfg(target_os = "windows")] if let Ok(cargo_target_dir) = get_cargo_target_dir() { let mut f = std::fs::File::create(cargo_target_dir.join("build.log"))?; @@ -28,7 +19,7 @@ fn main() -> Result<(), Box> { // Copy to the target directory if let Err(e) = std::fs::copy(src_path, &dst_path) { - f.write_all(format!("Failed to copy 'wintun.dll': {e}\r\n").as_bytes())?; + f.write_all(format!("Failed to copy 'wintun.dll': {}\r\n", e).as_bytes())?; } else { f.write_all(format!("Copied 'wintun.dll' to '{}'\r\n", dst_path.display()).as_bytes())?; @@ -94,10 +85,3 @@ fn get_crate_dir(crate_name: &str) -> Result std::io::Result { - use std::process::Command; - let git_hash = Command::new("git").args(["rev-parse", "--short", "HEAD"]).output()?.stdout; - let git_hash = String::from_utf8(git_hash).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; - Ok(git_hash) -} diff --git a/cbindgen.toml b/cbindgen.toml index 50ea65f..bed1b79 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -1,19 +1,15 @@ -language = "C" -cpp_compat = true - [export] include = [ - "tun2proxy_run_with_cli", "tun2proxy_with_fd_run", "tun2proxy_with_name_run", - "tun2proxy_stop", + "tun2proxy_with_name_stop", + "tun2proxy_with_fd_stop", "tun2proxy_set_log_callback", "tun2proxy_set_traffic_status_callback", ] exclude = [ "Java_com_github_shadowsocks_bg_Tun2proxy_run", "Java_com_github_shadowsocks_bg_Tun2proxy_stop", - "UdpFlag", ] [export.rename] diff --git a/src/android.rs b/src/android.rs index 030e921..cca174c 100644 --- a/src/android.rs +++ b/src/android.rs @@ -1,14 +1,14 @@ #![cfg(target_os = "android")] use crate::{ - Args, args::ArgProxy, error::{Error, Result}, + Args, }; use jni::{ - JNIEnv, objects::{JClass, JString}, sys::{jboolean, jchar, jint}, + JNIEnv, }; /// # Safety @@ -21,7 +21,7 @@ use jni::{ /// - tun_mtu: the tun mtu /// - dns_strategy: the dns strategy, see ArgDns enum /// - verbosity: the verbosity level, see ArgVerbosity enum -#[unsafe(no_mangle)] +#[no_mangle] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( mut env: JNIEnv, _clazz: JClass, @@ -52,15 +52,15 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( .close_fd_on_drop(close_fd_on_drop) .dns(dns) .verbosity(verbosity); - crate::general_api::general_run_for_api(args, tun_mtu, false) + crate::mobile_api::mobile_run(args, tun_mtu, false) } /// # Safety /// /// Shutdown tun2proxy -#[unsafe(no_mangle)] +#[no_mangle] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint { - crate::general_api::tun2proxy_stop_internal() + crate::mobile_api::mobile_stop() } fn get_java_string(env: &mut JNIEnv, string: &JString) -> Result { diff --git a/src/apple.rs b/src/apple.rs new file mode 100644 index 0000000..155b101 --- /dev/null +++ b/src/apple.rs @@ -0,0 +1,54 @@ +#![cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))] + +use crate::{ + args::{ArgDns, ArgProxy}, + ArgVerbosity, Args, +}; +use std::os::raw::{c_char, c_int, c_ushort}; + +/// # Safety +/// +/// Run the tun2proxy component with some arguments. +/// Parameters: +/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" +/// - tun_fd: the tun file descriptor, it will be owned by tun2proxy +/// - close_fd_on_drop: whether close the tun_fd on drop +/// - packet_information: whether exists packet information in tun_fd +/// - tun_mtu: the tun mtu +/// - dns_strategy: the dns strategy, see ArgDns enum +/// - verbosity: the verbosity level, see ArgVerbosity enum +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_with_fd_run( + proxy_url: *const c_char, + tun_fd: c_int, + close_fd_on_drop: bool, + packet_information: bool, + tun_mtu: c_ushort, + dns_strategy: ArgDns, + verbosity: ArgVerbosity, +) -> c_int { + log::set_max_level(verbosity.into()); + if let Err(err) = log::set_boxed_logger(Box::::default()) { + log::warn!("failed to set logger: {:?}", err); + } + + let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); + + let mut args = Args::default(); + args.proxy(proxy) + .tun_fd(Some(tun_fd)) + .close_fd_on_drop(close_fd_on_drop) + .dns(dns_strategy) + .verbosity(verbosity); + + crate::mobile_api::mobile_run(args, tun_mtu, packet_information) +} + +/// # Safety +/// +/// Shutdown the tun2proxy component. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_with_fd_stop() -> c_int { + crate::mobile_api::mobile_stop() +} diff --git a/src/args.rs b/src/args.rs index 333e758..85904db 100644 --- a/src/args.rs +++ b/src/args.rs @@ -8,19 +8,8 @@ use std::ffi::OsString; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::str::FromStr; -#[macro_export] -macro_rules! version_info { - () => { - concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")") - }; -} - -fn about_info() -> &'static str { - concat!("Tunnel interface to proxy.\nVersion: ", version_info!()) -} - #[derive(Debug, Clone, clap::Parser)] -#[command(author, version = version_info!(), about = about_info(), long_about = None)] +#[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] pub struct Args { /// Proxy URL in the form proto://[username[:password]@]host:port, /// where proto is one of socks4, socks5, http. @@ -76,9 +65,8 @@ pub struct Args { pub ipv6_enabled: bool, /// Routing and system setup, which decides whether to setup the routing and system configuration. - /// This option requires root-like privileges on every platform. - /// It is very important on Linux, see `capabilities(7)`. - #[arg(short, long)] + /// This option is only available on Linux and requires root-like privileges. See `capabilities(7)`. + #[arg(short, long, default_value = if cfg!(target_os = "linux") { "false" } else { "true" })] pub setup: bool, /// DNS handling strategy @@ -193,7 +181,8 @@ impl Default for Args { impl Args { #[allow(clippy::let_and_return)] pub fn parse_args() -> Self { - let args = ::parse(); + use clap::Parser; + let args = Self::parse(); #[cfg(target_os = "linux")] if !args.setup && args.tun.is_none() { eprintln!("Missing required argument, '--tun' must present when '--setup' is not used."); @@ -379,7 +368,7 @@ impl Default for ArgProxy { impl std::fmt::Display for ArgProxy { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let auth = match &self.credentials { - Some(creds) => format!("{creds}"), + Some(creds) => format!("{}", creds), None => "".to_owned(), }; if auth.is_empty() { @@ -406,11 +395,17 @@ impl TryFrom<&str> for ArgProxy { 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_or_known_default().ok_or(Error::from(&e))?; + let port = url.port().ok_or(Error::from(&e))?; + url_host.push(':'); + url_host.push_str(port.to_string().as_str()); - let e2 = format!("`{host}` does not resolve to a usable IP address"); - let addr = (host, port).to_socket_addrs()?.next().ok_or(Error::from(&e2))?; + 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 diff --git a/src/bin/main.rs b/src/bin/main.rs index 1f5142f..e39b7b4 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,4 +1,4 @@ -use tun2proxy::{ArgVerbosity, Args, BoxError}; +use tun2proxy::{Args, BoxError}; fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); @@ -27,68 +27,48 @@ fn main() -> Result<(), BoxError> { rt.block_on(main_async(args)) } -fn setup_logging(args: &Args) { - let avoid_trace = match args.verbosity { - ArgVerbosity::Trace => ArgVerbosity::Debug, - _ => args.verbosity, - }; - let default = format!( - "{:?},hickory_proto=warn,ipstack={:?},netlink_proto={:?},netlink_sys={:?}", - args.verbosity, avoid_trace, avoid_trace, avoid_trace - ); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); -} - async fn main_async(args: Args) -> Result<(), BoxError> { - setup_logging(&args); + let default = format!("{:?},hickory_proto=warn", args.verbosity); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); let shutdown_token = tokio_util::sync::CancellationToken::new(); let main_loop_handle = tokio::spawn({ - let args = args.clone(); let shutdown_token = shutdown_token.clone(); async move { #[cfg(target_os = "linux")] if args.unshare && args.socket_transfer_fd.is_none() { if let Err(err) = namespace_proxy_main(args, shutdown_token).await { - log::error!("namespace proxy error: {err}"); + log::error!("namespace proxy error: {}", err); } - return Ok(0); + return; } unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) { - let status = unsafe { &*status }; + let status = &*status; log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); } unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - let ret = tun2proxy::general_run_async(args, tun::DEFAULT_MTU, cfg!(target_os = "macos"), shutdown_token).await; - if let Err(err) = &ret { - log::error!("main loop error: {err}"); + if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); } - ret } }); let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let ctrlc_fired_clone = ctrlc_fired.clone(); - let ctrlc_handel = ctrlc2::AsyncCtrlC::new(move || { + let ctrlc_handel = ctrlc2::set_async_handler(async move { log::info!("Ctrl-C received, exiting..."); ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst); shutdown_token.cancel(); - true - })?; + }) + .await; - let tasks = main_loop_handle.await??; + main_loop_handle.await?; if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { log::info!("Ctrl-C fired, waiting the handler to finish..."); - ctrlc_handel.await?; - } - - if args.exit_on_fatal_error && tasks >= args.max_sessions { - // Because `main_async` function perhaps stuck in `await` state, so we need to exit the process forcefully - log::info!("Internal fatal error, max sessions reached ({tasks}/{})", args.max_sessions); - std::process::exit(-1); + ctrlc_handel.await.map_err(|err| err.to_string())?; } Ok(()) @@ -99,7 +79,7 @@ async fn namespace_proxy_main( _args: Args, _shutdown_token: tokio_util::sync::CancellationToken, ) -> Result { - use nix::fcntl::{OFlag, open}; + use nix::fcntl::{open, OFlag}; use nix::sys::stat::Mode; use std::os::fd::AsRawFd; @@ -109,7 +89,7 @@ async fn namespace_proxy_main( let child = tokio::process::Command::new("unshare") .args("--user --map-current-user --net --mount --keep-caps --kill-child --fork".split(' ')) - .arg(format!("/proc/self/fd/{}", fd.as_raw_fd())) + .arg(format!("/proc/self/fd/{}", fd)) .arg("--socket-transfer-fd") .arg(remote_fd.as_raw_fd().to_string()) .args(std::env::args().skip(1)) @@ -133,10 +113,13 @@ async fn namespace_proxy_main( log::info!("Use `tun2proxy-bin --unshare --setup [...] -- openvpn --config [...]`"); log::info!(""); log::info!("To run a new process in the created namespace (e.g. a flatpak app)"); - log::info!("Use `nsenter --preserve-credentials --user --net --mount --target {unshare_pid} /bin/sh`"); + log::info!( + "Use `nsenter --preserve-credentials --user --net --mount --target {} /bin/sh`", + unshare_pid + ); log::info!(""); if let Some(pidfile) = _args.unshare_pidfile.as_ref() { - log::info!("Writing unshare pid to {pidfile}"); + log::info!("Writing unshare pid to {}", pidfile); std::fs::write(pidfile, unshare_pid.to_string()).ok(); } tokio::spawn(async move { tun2proxy::socket_transfer::process_socket_requests(&socket).await }); diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index b6c2dc6..550f51f 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -1,16 +1,16 @@ -use socks5_impl::protocol::AsyncStreamOperation; +use socks5_impl::protocol::{AddressType, AsyncStreamOperation}; use std::net::SocketAddr; use tokio::{ io::AsyncWriteExt, net::{ - UdpSocket, tcp::{ReadHalf, WriteHalf}, + UdpSocket, }, sync::mpsc::{Receiver, Sender}, }; use tun2proxy::{ - ArgVerbosity, BoxError, Error, Result, udpgw::{Packet, UdpFlag}, + ArgVerbosity, BoxError, Error, Result, }; pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60); @@ -28,12 +28,7 @@ impl Client { } } -fn about_info() -> &'static str { - concat!("UDP Gateway Server for tun2proxy\nVersion: ", tun2proxy::version_info!()) -} - #[derive(Debug, Clone, clap::Parser)] -#[command(author, version = tun2proxy::version_info!(), about = about_info(), long_about = None)] pub struct UdpGwArgs { /// UDP gateway listen address #[arg(short, long, value_name = "IP:PORT", default_value = "127.0.0.1:7300")] @@ -49,7 +44,7 @@ pub struct UdpGwArgs { /// Daemonize for unix family or run as Windows service #[cfg(unix)] - #[arg(short, long)] + #[arg(long)] pub daemonize: bool, /// Verbosity level @@ -58,43 +53,48 @@ pub struct UdpGwArgs { } impl UdpGwArgs { + #[allow(clippy::let_and_return)] pub fn parse_args() -> Self { - ::parse() + use clap::Parser; + Self::parse() } } async fn send_error_response(tx: Sender, conn_id: u16) { let error_packet = Packet::build_error_packet(conn_id); if let Err(e) = tx.send(error_packet).await { - log::error!("send error response error {e:?}"); + log::error!("send error response error {:?}", e); } } async fn send_keepalive_response(tx: Sender, conn_id: u16) { let keepalive_packet = Packet::build_keepalive_packet(conn_id); if let Err(e) = tx.send(keepalive_packet).await { - log::error!("send keepalive response error {e:?}"); + log::error!("send keepalive response error {:?}", e); } } /// Send data field of packet from client to destination server and receive response, /// then wrap response data to the packet's data field and send packet back to client. -async fn process_udp(udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { +async fn process_udp(client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { let Some(dst_addr) = &packet.address else { - return Err(std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "udp request address is None").into()); + log::error!("client {} udp request address is None", client); + return Ok(()); }; - use std::net::ToSocketAddrs; - let Some(dst_addr) = dst_addr.to_socket_addrs()?.next() else { - return Err(std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "to_socket_addrs").into()); - }; - let std_sock = match dst_addr { - std::net::SocketAddr::V6(_) => std::net::UdpSocket::bind("[::]:0")?, - std::net::SocketAddr::V4(_) => std::net::UdpSocket::bind("0.0.0.0:0")?, + let std_sock = if dst_addr.get_type() == AddressType::IPv6 { + std::net::UdpSocket::bind("[::]:0")? + } else { + std::net::UdpSocket::bind("0.0.0.0:0")? }; std_sock.set_nonblocking(true)?; #[cfg(unix)] nix::sys::socket::setsockopt(&std_sock, nix::sys::socket::sockopt::ReuseAddr, &true)?; let socket = UdpSocket::from_std(std_sock)?; + use std::net::ToSocketAddrs; + let Some(dst_addr) = dst_addr.to_socket_addrs()?.next() else { + log::error!("client {} udp request address to_socket_addrs", client); + return Ok(()); + }; // 1. send udp data to destination server socket.send_to(&packet.data, &dst_addr).await?; // 2. receive response from destination server @@ -109,53 +109,22 @@ async fn process_udp(udp_mtu: u16, udp_timeout: u64, tx: Sender, mut pac Ok(()) } -fn mask_ip(ip: &str) -> String { - if ip.len() <= 2 { - return ip.to_string(); - } - let mut masked_ip = String::new(); - for (i, c) in ip.chars().enumerate() { - if i == 0 || i == ip.len() - 1 || c == '.' || c == ':' { - masked_ip.push(c); - } else { - masked_ip.push('*'); - } - } - masked_ip -} - -fn mask_socket_addr(socket_addr: std::net::SocketAddr) -> String { - match socket_addr { - std::net::SocketAddr::V4(addr) => { - let masked_ip = mask_ip(&addr.ip().to_string()); - format!("{}:{}", masked_ip, addr.port()) - } - std::net::SocketAddr::V6(addr) => { - let masked_ip = mask_ip(&addr.ip().to_string()); - format!("[{}]:{}", masked_ip, addr.port()) - } - } -} - async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client: Client, mut reader: ReadHalf<'_>) -> std::io::Result<()> { let udp_timeout = args.udp_timeout; let udp_mtu = args.udp_mtu; - let masked_addr = mask_socket_addr(client.addr); - loop { - let masked_addr = masked_addr.clone(); // 1. read udpgw packet from client let res = tokio::time::timeout(tokio::time::Duration::from_secs(2), Packet::retrieve_from_async_stream(&mut reader)).await; let packet = match res { Ok(Ok(packet)) => packet, Ok(Err(e)) => { - log::debug!("client {masked_addr} retrieve_from_async_stream \"{e}\""); + log::debug!("client {} retrieve_from_async_stream \"{}\"", client.addr, e); break; } Err(e) => { if client.last_activity.elapsed() >= CLIENT_DISCONNECT_TIMEOUT { - log::debug!("client {masked_addr} last_activity elapsed \"{e}\""); + log::debug!("client {} last_activity elapsed \"{e}\"", client.addr); break; } continue; @@ -166,19 +135,19 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client let flags = packet.header.flags; let conn_id = packet.header.conn_id; if flags & UdpFlag::KEEPALIVE == UdpFlag::KEEPALIVE { - log::trace!("client {masked_addr} send keepalive"); + log::trace!("client {} send keepalive", client.addr); // 2. if keepalive packet, do nothing, send keepalive response to client send_keepalive_response(tx.clone(), conn_id).await; continue; } - log::trace!("client {masked_addr} received udp data {packet}"); + log::trace!("client {} received udp data {}", client.addr, packet); // 3. process client udpgw packet in a new task let tx = tx.clone(); tokio::spawn(async move { - if let Err(e) = process_udp(udp_mtu, udp_timeout, tx.clone(), packet).await { + if let Err(e) = process_udp(client.addr, udp_mtu, udp_timeout, tx.clone(), packet).await { send_error_response(tx, conn_id).await; - log::debug!("client {masked_addr} process udp function \"{e}\""); + log::debug!("client {} process udp function \"{e}\"", client.addr); } }); } @@ -186,18 +155,17 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client } async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Receiver) -> std::io::Result<()> { - let masked_addr = mask_socket_addr(addr); loop { use std::io::{Error, ErrorKind::BrokenPipe}; let packet = rx.recv().await.ok_or(Error::new(BrokenPipe, "recv error"))?; - log::trace!("send response to client {masked_addr} with {packet}"); + log::trace!("send response to client {} with {}", addr, packet); let data: Vec = packet.into(); let _r = writer.write(&data).await?; } } async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> { - log::info!("{} {} starting...", module_path!(), tun2proxy::version_info!()); + log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION")); log::info!("UDP Gateway Server running at {}", args.listen_addr); let shutdown_token = tokio_util::sync::CancellationToken::new(); @@ -205,18 +173,18 @@ async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> { let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let ctrlc_fired_clone = ctrlc_fired.clone(); - let ctrlc_handel = ctrlc2::AsyncCtrlC::new(move || { + let ctrlc_handel = ctrlc2::set_async_handler(async move { log::info!("Ctrl-C received, exiting..."); ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst); shutdown_token.cancel(); - true - })?; + }) + .await; let _ = main_loop_handle.await?; if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { log::info!("Ctrl-C fired, waiting the handler to finish..."); - ctrlc_handel.await?; + ctrlc_handel.await.map_err(|err| err.to_string())?; } Ok(()) @@ -230,8 +198,7 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation _ = shutdown_token.cancelled() => break, }; let client = Client::new(addr); - let masked_addr = mask_socket_addr(addr); - log::info!("client {masked_addr} connected"); + log::info!("client {} connected", addr); let params = args.clone(); tokio::spawn(async move { let (tx, rx) = tokio::sync::mpsc::channel::(100); @@ -240,7 +207,7 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation v = process_client_udp_req(¶ms, tx, client, tcp_read_stream) => v, v = write_to_client(addr, tcp_write_stream, rx) => v, }; - log::info!("client {masked_addr} disconnected with {res:?}"); + log::info!("client {} disconnected with {:?}", addr, res); }); } Ok::<(), Error>(()) @@ -263,7 +230,9 @@ fn main() -> Result<(), BoxError> { .stdout(stdout) .stderr(stderr) .privileged_action(|| "Executed before drop privileges"); - let _ = daemonize.start().map_err(|e| format!("Failed to daemonize process, error:{e:?}"))?; + let _ = daemonize + .start() + .map_err(|e| format!("Failed to daemonize process, error:{:?}", e))?; } let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; diff --git a/src/desktop_api.rs b/src/desktop_api.rs new file mode 100644 index 0000000..17428fb --- /dev/null +++ b/src/desktop_api.rs @@ -0,0 +1,187 @@ +#![cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] + +use crate::{ + args::{ArgDns, ArgProxy}, + ArgVerbosity, Args, +}; +use std::os::raw::{c_char, c_int}; +use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; +use tun::{AbstractDevice, DEFAULT_MTU as MTU}; + +static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); + +/// # Safety +/// +/// Run the tun2proxy component with some arguments. +/// Parameters: +/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" +/// - tun: the tun device name, e.g. "utun5" +/// - bypass: the bypass IP/CIDR, e.g. "123.45.67.0/24" +/// - dns_strategy: the dns strategy, see ArgDns enum +/// - root_privilege: whether to run with root privilege +/// - verbosity: the verbosity level, see ArgVerbosity enum +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_with_name_run( + proxy_url: *const c_char, + tun: *const c_char, + bypass: *const c_char, + dns_strategy: ArgDns, + _root_privilege: bool, + verbosity: ArgVerbosity, +) -> c_int { + let shutdown_token = tokio_util::sync::CancellationToken::new(); + { + if let Ok(mut lock) = TUN_QUIT.lock() { + if lock.is_some() { + return -1; + } + *lock = Some(shutdown_token.clone()); + } else { + return -2; + } + } + + log::set_max_level(verbosity.into()); + if let Err(err) = log::set_boxed_logger(Box::::default()) { + log::warn!("set logger error: {}", err); + } + + let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); + let tun = std::ffi::CStr::from_ptr(tun).to_str().unwrap().to_string(); + + let mut args = Args::default(); + args.proxy(proxy).tun(tun).dns(dns_strategy).verbosity(verbosity); + + #[cfg(target_os = "linux")] + args.setup(_root_privilege); + + if let Ok(bypass) = std::ffi::CStr::from_ptr(bypass).to_str() { + args.bypass(bypass.parse().unwrap()); + } + + let main_loop = async move { + if let Err(err) = desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); + return Err(err); + } + Ok(()) + }; + + let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + Err(_e) => -3, + Ok(rt) => match rt.block_on(main_loop) { + Ok(_) => 0, + Err(_e) => -4, + }, + }; + + exit_code +} + +/// Run the tun2proxy component with some arguments. +pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::CancellationToken) -> std::io::Result<()> { + let bypass_ips = args.bypass.clone(); + + let mut tun_config = tun::Configuration::default(); + tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); + tun_config.destination(TUN_GATEWAY); + #[cfg(unix)] + if let Some(fd) = args.tun_fd { + tun_config.raw_fd(fd); + if let Some(v) = args.close_fd_on_drop { + tun_config.close_fd_on_drop(v); + }; + } else if let Some(ref tun) = args.tun { + tun_config.tun_name(tun); + } + #[cfg(windows)] + if let Some(ref tun) = args.tun { + tun_config.tun_name(tun); + } + + #[cfg(target_os = "linux")] + tun_config.platform_config(|cfg| { + #[allow(deprecated)] + cfg.packet_information(true); + cfg.ensure_root_privileges(args.setup); + }); + + #[cfg(target_os = "windows")] + tun_config.platform_config(|cfg| { + cfg.device_guid(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) + .ipv6_default_route(args.ipv6_enabled); + + #[allow(unused_mut, unused_assignments, unused_variables)] + let mut setup = true; + + let device = tun::create_as_async(&tun_config)?; + + if let Ok(tun_name) = device.tun_name() { + tproxy_args = tproxy_args.tun_name(&tun_name); + } + + // TproxyState implements the Drop trait to restore network configuration, + // so we need to assign it to a variable, even if it is not used. + let mut _restore: Option = None; + + #[cfg(target_os = "linux")] + { + setup = args.setup; + } + + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] + if setup { + _restore = Some(tproxy_config::tproxy_setup(&tproxy_args)?); + } + + #[cfg(target_os = "linux")] + { + let mut admin_command_args = args.admin_command.iter(); + if let Some(command) = admin_command_args.next() { + let child = tokio::process::Command::new(command) + .args(admin_command_args) + .kill_on_drop(true) + .spawn(); + + match child { + Err(err) => { + log::warn!("Failed to start admin process: {err}"); + } + Ok(mut child) => { + tokio::spawn(async move { + if let Err(err) = child.wait().await { + log::warn!("Admin process terminated: {err}"); + } + }); + } + }; + } + } + + let join_handle = tokio::spawn(crate::run(device, MTU, args, shutdown_token)); + join_handle.await.map_err(std::io::Error::from)??; + + Ok::<(), std::io::Error>(()) +} + +/// # Safety +/// +/// Shutdown the tun2proxy component. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_with_name_stop() -> c_int { + if let Ok(mut lock) = TUN_QUIT.lock() { + if let Some(shutdown_token) = lock.take() { + shutdown_token.cancel(); + return 0; + } + } + -1 +} diff --git a/src/dns.rs b/src/dns.rs index ed36f5f..e18d218 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,16 +1,21 @@ use hickory_proto::{ op::{Message, MessageType, ResponseCode}, - rr::{ - Name, RData, Record, - rdata::{A, AAAA}, - }, + rr::{record_type::RecordType, Name, RData, Record}, }; use std::{net::IpAddr, str::FromStr}; pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result { let record = match ip { - IpAddr::V4(ip) => Record::from_rdata(Name::from_str(domain)?, ttl, RData::A(A(ip))), - IpAddr::V6(ip) => Record::from_rdata(Name::from_str(domain)?, ttl, RData::AAAA(AAAA(ip))), + IpAddr::V4(ip) => { + let mut record = Record::with(Name::from_str(domain)?, RecordType::A, ttl); + record.set_data(Some(RData::A(ip.into()))); + record + } + IpAddr::V6(ip) => { + let mut record = Record::with(Name::from_str(domain)?, RecordType::AAAA, ttl); + record.set_data(Some(RData::AAAA(ip.into()))); + record + } }; // We must indicate that this message is a response. Otherwise, implementations may not @@ -22,7 +27,9 @@ pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u } pub fn remove_ipv6_entries(message: &mut Message) { - message.answers_mut().retain(|answer| !matches!(answer.data(), RData::AAAA(_))); + message + .answers_mut() + .retain(|answer| !matches!(answer.data(), Some(RData::AAAA(_)))); } pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result { @@ -31,7 +38,7 @@ pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result { return Ok(IpAddr::V4((*addr).into())); } diff --git a/src/dump_logger.rs b/src/dump_logger.rs index aba05dd..b04ff09 100644 --- a/src/dump_logger.rs +++ b/src/dump_logger.rs @@ -9,7 +9,7 @@ pub(crate) static DUMP_CALLBACK: Mutex> = Mutex::new(None); /// # Safety /// /// set dump log info callback. -#[unsafe(no_mangle)] +#[no_mangle] pub unsafe extern "C" fn tun2proxy_set_log_callback( callback: Option, ctx: *mut c_void, @@ -23,7 +23,7 @@ pub struct DumpCallback(Option), + IpStack(#[from] ipstack::IpStackError), #[error("DnsProtoError {0:?}")] - DnsProto(#[from] hickory_proto::ProtoError), + DnsProto(#[from] hickory_proto::error::ProtoError), #[error("httparse::Error {0:?}")] Httparse(#[from] httparse::Error), @@ -43,12 +43,10 @@ pub enum Error { #[error("std::num::ParseIntError {0:?}")] IntParseError(#[from] std::num::ParseIntError), -} -impl From for Error { - fn from(err: ipstack::IpStackError) -> Self { - Self::IpStack(Box::new(err)) - } + #[cfg(target_os = "linux")] + #[error("bincode::Error {0:?}")] + BincodeError(#[from] bincode::Error), } impl From<&str> for Error { @@ -73,7 +71,7 @@ impl From for std::io::Error { fn from(err: Error) -> Self { match err { Error::Io(err) => err, - _ => std::io::Error::other(err), + _ => std::io::Error::new(std::io::ErrorKind::Other, err), } } } diff --git a/src/general_api.rs b/src/general_api.rs deleted file mode 100644 index e713409..0000000 --- a/src/general_api.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::{ - ArgVerbosity, Args, - args::{ArgDns, ArgProxy}, -}; -use std::os::raw::{c_char, c_int, c_ushort}; - -static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); - -/// # Safety -/// -/// Run the tun2proxy component with some arguments. -/// Parameters: -/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" -/// - tun: the tun device name, e.g. "utun5" -/// - bypass: the bypass IP/CIDR, e.g. "123.45.67.0/24" -/// - dns_strategy: the dns strategy, see ArgDns enum -/// - root_privilege: whether to run with root privilege -/// - verbosity: the verbosity level, see ArgVerbosity enum -#[unsafe(no_mangle)] -pub unsafe extern "C" fn tun2proxy_with_name_run( - proxy_url: *const c_char, - tun: *const c_char, - bypass: *const c_char, - dns_strategy: ArgDns, - _root_privilege: bool, - verbosity: ArgVerbosity, -) -> c_int { - let proxy_url = unsafe { std::ffi::CStr::from_ptr(proxy_url) }.to_str().unwrap(); - let proxy = ArgProxy::try_from(proxy_url).unwrap(); - let tun = unsafe { std::ffi::CStr::from_ptr(tun) }.to_str().unwrap().to_string(); - - let mut args = Args::default(); - if let Ok(bypass) = unsafe { std::ffi::CStr::from_ptr(bypass) }.to_str() { - args.bypass(bypass.parse().unwrap()); - } - args.proxy(proxy).tun(tun).dns(dns_strategy).verbosity(verbosity); - - #[cfg(target_os = "linux")] - args.setup(_root_privilege); - - general_run_for_api(args, tun::DEFAULT_MTU, false) -} - -/// # Safety -/// -/// Run the tun2proxy component with some arguments. -/// Parameters: -/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" -/// - tun_fd: the tun file descriptor, it will be owned by tun2proxy -/// - close_fd_on_drop: whether close the tun_fd on drop -/// - packet_information: indicates whether exists packet information in packet from TUN device -/// - tun_mtu: the tun mtu -/// - dns_strategy: the dns strategy, see ArgDns enum -/// - verbosity: the verbosity level, see ArgVerbosity enum -#[cfg(unix)] -#[unsafe(no_mangle)] -pub unsafe extern "C" fn tun2proxy_with_fd_run( - proxy_url: *const c_char, - tun_fd: c_int, - close_fd_on_drop: bool, - packet_information: bool, - tun_mtu: c_ushort, - dns_strategy: ArgDns, - verbosity: ArgVerbosity, -) -> c_int { - let proxy_url = unsafe { std::ffi::CStr::from_ptr(proxy_url) }.to_str().unwrap(); - let proxy = ArgProxy::try_from(proxy_url).unwrap(); - - let mut args = Args::default(); - args.proxy(proxy) - .tun_fd(Some(tun_fd)) - .close_fd_on_drop(close_fd_on_drop) - .dns(dns_strategy) - .verbosity(verbosity); - - general_run_for_api(args, tun_mtu, packet_information) -} - -/// # Safety -/// Run the tun2proxy component with command line arguments -/// Parameters: -/// - cli_args: The command line arguments, -/// e.g. `tun2proxy-bin --setup --proxy socks5://127.0.0.1:1080 --bypass 98.76.54.0/24 --dns over-tcp --verbosity trace` -/// - tun_mtu: The MTU of the TUN device, e.g. 1500 -/// - packet_information: Whether exists packet information in packet from TUN device -#[unsafe(no_mangle)] -pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char, tun_mtu: c_ushort, packet_information: bool) -> c_int { - let Ok(cli_args) = unsafe { std::ffi::CStr::from_ptr(cli_args) }.to_str() else { - log::error!("Failed to convert CLI arguments to string"); - return -5; - }; - let Some(args) = shlex::split(cli_args) else { - log::error!("Failed to split CLI arguments"); - return -6; - }; - let args = ::parse_from(args); - general_run_for_api(args, tun_mtu, packet_information) -} - -pub fn general_run_for_api(args: Args, tun_mtu: u16, packet_information: bool) -> c_int { - log::set_max_level(args.verbosity.into()); - if let Err(err) = log::set_boxed_logger(Box::::default()) { - log::debug!("set logger error: {err}"); - } - - let shutdown_token = tokio_util::sync::CancellationToken::new(); - if let Ok(mut lock) = TUN_QUIT.lock() { - if lock.is_some() { - log::error!("tun2proxy already started"); - return -1; - } - *lock = Some(shutdown_token.clone()); - } else { - log::error!("failed to lock tun2proxy quit token"); - return -2; - } - - let Ok(rt) = tokio::runtime::Builder::new_multi_thread().enable_all().build() else { - log::error!("failed to create tokio runtime with"); - return -3; - }; - match rt.block_on(async move { - let ret = general_run_async(args.clone(), tun_mtu, packet_information, shutdown_token).await; - match &ret { - Ok(sessions) => { - if args.exit_on_fatal_error && *sessions >= args.max_sessions { - log::error!("Forced exit due to max sessions reached ({sessions}/{})", args.max_sessions); - std::process::exit(-1); - } - log::debug!("tun2proxy exited normally, current sessions: {sessions}"); - } - Err(err) => log::error!("main loop error: {err}"), - } - ret - }) { - Ok(_) => 0, - Err(e) => { - log::error!("failed to run tun2proxy with error: {e:?}"); - -4 - } - } -} - -/// Run the tun2proxy component with some arguments. -pub async fn general_run_async( - args: Args, - tun_mtu: u16, - _packet_information: bool, - shutdown_token: tokio_util::sync::CancellationToken, -) -> std::io::Result { - let mut tun_config = tun::Configuration::default(); - - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - { - use tproxy_config::{TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; - tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(tun_mtu).up(); - tun_config.destination(TUN_GATEWAY); - } - - #[cfg(unix)] - if let Some(fd) = args.tun_fd { - tun_config.raw_fd(fd); - if let Some(v) = args.close_fd_on_drop { - tun_config.close_fd_on_drop(v); - }; - } else if let Some(ref tun) = args.tun { - tun_config.tun_name(tun); - } - #[cfg(windows)] - if let Some(ref tun) = args.tun { - tun_config.tun_name(tun); - } - - #[cfg(target_os = "linux")] - tun_config.platform_config(|cfg| { - #[allow(deprecated)] - cfg.packet_information(true); - cfg.ensure_root_privileges(args.setup); - }); - - #[cfg(target_os = "windows")] - tun_config.platform_config(|cfg| { - cfg.device_guid(12324323423423434234_u128); - }); - - #[cfg(any(target_os = "ios", target_os = "macos"))] - tun_config.platform_config(|cfg| { - cfg.packet_information(_packet_information); - }); - - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - #[allow(unused_variables)] - let mut tproxy_args = tproxy_config::TproxyArgs::new() - .tun_dns(args.dns_addr) - .proxy_addr(args.proxy.addr) - .bypass_ips(&args.bypass) - .ipv6_default_route(args.ipv6_enabled); - - let device = tun::create_as_async(&tun_config)?; - - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - if let Ok(tun_name) = tun::AbstractDevice::tun_name(&*device) { - // Above line is equivalent to: `use tun::AbstractDevice; if let Ok(tun_name) = device.tun_name() {` - tproxy_args = tproxy_args.tun_name(&tun_name); - } - - // TproxyState implements the Drop trait to restore network configuration, - // so we need to assign it to a variable, even if it is not used. - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - let mut restore: Option = None; - - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - if args.setup { - restore = Some(tproxy_config::tproxy_setup(&tproxy_args).await?); - } - - #[cfg(target_os = "linux")] - { - let mut admin_command_args = args.admin_command.iter(); - if let Some(command) = admin_command_args.next() { - let child = tokio::process::Command::new(command) - .args(admin_command_args) - .kill_on_drop(true) - .spawn(); - - match child { - Err(err) => { - log::warn!("Failed to start admin process: {err}"); - } - Ok(mut child) => { - tokio::spawn(async move { - if let Err(err) = child.wait().await { - log::warn!("Admin process terminated: {err}"); - } - }); - } - }; - } - } - - let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token.clone())); - - match join_handle.await? { - Ok(sessions) => { - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - tproxy_config::tproxy_remove(restore).await?; - Ok(sessions) - } - Err(err) => Err(std::io::Error::from(err)), - } -} - -/// # Safety -/// -/// Shutdown the tun2proxy component. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn tun2proxy_stop() -> c_int { - tun2proxy_stop_internal() -} - -pub(crate) fn tun2proxy_stop_internal() -> c_int { - if let Ok(mut lock) = TUN_QUIT.lock() { - if let Some(shutdown_token) = lock.take() { - shutdown_token.cancel(); - return 0; - } - } - -1 -} diff --git a/src/http.rs b/src/http.rs index 2b08ed1..2b5f491 100644 --- a/src/http.rs +++ b/src/http.rs @@ -4,10 +4,11 @@ use crate::{ proxy_handler::{ProxyHandler, ProxyHandlerManager}, session_info::{IpProtocol, SessionInfo}, }; +use base64::Engine; use httparse::Response; use socks5_impl::protocol::UserKey; use std::{ - collections::{HashMap, VecDeque, hash_map::RandomState}, + collections::{hash_map::RandomState, HashMap, VecDeque}, iter::FromIterator, net::SocketAddr, str, @@ -140,9 +141,10 @@ impl HttpConnection { .extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes()); } AuthenticationScheme::Basic => { - let auth_b64 = base64easy::encode(credentials.to_string(), base64easy::EngineKind::Standard); + let cred = format!("{}:{}", credentials.username, credentials.password); + let auth_b64 = base64::engine::general_purpose::STANDARD.encode(cred); self.server_outbuf - .extend(format!("{PROXY_AUTHORIZATION}: Basic {auth_b64}\r\n").as_bytes()); + .extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes()); } AuthenticationScheme::None => {} } @@ -250,7 +252,7 @@ impl HttpConnection { } // The HTTP/1.1 expected to be keep alive waiting for the next frame so, we must - // compute the length of the response in order to detect the next frame (response) + // compute the lenght of the response in order to detect the next frame (response) // [RFC-9112](https://datatracker.ietf.org/doc/html/rfc9112#body.content-length) // Transfer-Encoding isn't supported yet diff --git a/src/lib.rs b/src/lib.rs index 737a82d..932dda8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use crate::{ session_info::{IpProtocol, SessionInfo}, virtual_dns::VirtualDns, }; -use ipstack::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; +use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; use proxy_handler::{ProxyHandler, ProxyHandlerManager}; use socks::SocksProxyManager; pub use socks5_impl::protocol::UserKey; @@ -22,34 +22,43 @@ use std::{ use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::{TcpSocket, TcpStream, UdpSocket}, - sync::{Mutex, mpsc::Receiver}, + sync::{mpsc::Receiver, Mutex}, }; pub use tokio_util::sync::CancellationToken; use tproxy_config::is_private_ip; use udp_stream::UdpStream; #[cfg(feature = "udpgw")] -use udpgw::{UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS, UdpGwClientStream, UdpGwResponse}; +use udpgw::{UdpGwClientStream, UdpGwResponse, UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS}; pub use { args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType}, error::{BoxError, Error, Result}, - traffic_status::{TrafficStatus, tun2proxy_set_traffic_status_callback}, + traffic_status::{tun2proxy_set_traffic_status_callback, TrafficStatus}, }; #[cfg(feature = "mimalloc")] #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; -pub use general_api::general_run_async; +#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] +pub use desktop_api::desktop_run_async; + +#[cfg(any(target_os = "ios", target_os = "android"))] +pub use mobile_api::{desktop_run_async, mobile_run, mobile_stop}; + +#[cfg(target_os = "macos")] +pub use mobile_api::{mobile_run, mobile_stop}; mod android; +mod apple; mod args; +mod desktop_api; mod directions; mod dns; mod dump_logger; mod error; -mod general_api; mod http; +mod mobile_api; mod no_proxy; mod proxy_handler; mod session_info; @@ -64,12 +73,12 @@ pub mod win_svc; const DNS_PORT: u16 = 53; +static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +use std::sync::atomic::Ordering::Relaxed; + #[allow(unused)] #[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr( - target_os = "linux", - derive(bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize) -)] +#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))] pub enum SocketProtocol { Tcp, Udp, @@ -77,10 +86,7 @@ pub enum SocketProtocol { #[allow(unused)] #[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr( - target_os = "linux", - derive(bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize) -)] +#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))] pub enum SocketDomain { IpV4, IpV6, @@ -151,13 +157,11 @@ async fn create_udp_stream(socket_queue: &Option>, peer: Socket /// * `mtu` - The MTU of the network device /// * `args` - The arguments to use /// * `shutdown_token` - The token to exit the server -/// # Returns -/// * The number of sessions while exiting -pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result +pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result<()> where D: AsyncRead + AsyncWrite + Unpin + Send + 'static, { - log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), version_info!()); + log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); let server_addr = args.proxy.addr; @@ -221,11 +225,11 @@ where let socket_queue = None; use socks5_impl::protocol::Version::{V4, V5}; - let mgr: Arc = match args.proxy.proxy_type { - ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)), - ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)), - ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)), - ProxyType::None => Arc::new(NoProxyManager::new()), + 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, + ProxyType::None => Arc::new(NoProxyManager::new()) as Arc, }; let mut ipstack_config = ipstack::IpStackConfig::default(); @@ -237,7 +241,7 @@ where #[cfg(feature = "udpgw")] let udpgw_client = args.udpgw_server.map(|addr| { - log::info!("UDP Gateway enabled, server: {addr}"); + log::info!("UDP Gateway enabled, server: {}", addr); use std::time::Duration; let client = Arc::new(UdpGwClient::new( mtu, @@ -253,11 +257,7 @@ where client }); - let task_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); - use std::sync::atomic::Ordering::Relaxed; - loop { - let task_count = task_count.clone(); let virtual_dns = virtual_dns.clone(); let ip_stack_stream = tokio::select! { _ = shutdown_token.cancelled() => { @@ -268,10 +268,10 @@ where ip_stack_stream? } }; - let max_sessions = args.max_sessions; + let max_sessions = args.max_sessions as u64; match ip_stack_stream { IpStackStream::Tcp(tcp) => { - if task_count.load(Relaxed) >= max_sessions { + if TASK_COUNT.load(Relaxed) > max_sessions { if args.exit_on_fatal_error { log::info!("Too many sessions that over {max_sessions}, exiting..."); break; @@ -279,7 +279,7 @@ where log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } - log::trace!("Session count {}", task_count.fetch_add(1, Relaxed).saturating_add(1)); + 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; @@ -292,13 +292,13 @@ where let socket_queue = socket_queue.clone(); tokio::spawn(async move { if let Err(err) = handle_tcp_session(tcp, proxy_handler, socket_queue).await { - log::error!("{info} error \"{err}\""); + log::error!("{} error \"{}\"", info, err); } - log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); } IpStackStream::Udp(udp) => { - if task_count.load(Relaxed) >= max_sessions { + if TASK_COUNT.load(Relaxed) > max_sessions { if args.exit_on_fatal_error { log::info!("Too many sessions that over {max_sessions}, exiting..."); break; @@ -306,11 +306,11 @@ where log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } - log::trace!("Session count {}", task_count.fetch_add(1, Relaxed).saturating_add(1)); + 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); // !!! Here we change the destination address to remote DNS server!!! + info.dst.set_ip(dns_addr); } if args.dns == ArgDns::OverTcp { info.protocol = IpProtocol::Tcp; @@ -318,9 +318,9 @@ where let socket_queue = socket_queue.clone(); tokio::spawn(async move { if let Err(err) = handle_dns_over_tcp_session(udp, proxy_handler, socket_queue, ipv6_enabled).await { - log::error!("{info} error \"{err}\""); + log::error!("{} error \"{}\"", info, err); } - log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); continue; } @@ -328,10 +328,10 @@ where 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!("{info} error \"{err}\""); + log::error!("{} error \"{}\"", info, err); } } - log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); continue; } @@ -360,9 +360,9 @@ where None => dst.into(), }; if let Err(e) = handle_udp_gateway_session(udp, udpgw, &dst_addr, proxy_handler, queue, ipv6_enabled).await { - log::info!("Ending {info} with \"{e}\""); + log::info!("Ending {} with \"{}\"", info, e); } - log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); continue; } @@ -372,19 +372,19 @@ where tokio::spawn(async move { let ty = args.proxy.proxy_type; if let Err(err) = handle_udp_associate_session(udp, ty, proxy_handler, socket_queue, ipv6_enabled).await { - log::info!("Ending {info} with \"{err}\""); + log::info!("Ending {} with \"{}\"", info, err); } - log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); } Err(e) => { - log::error!("Failed to create UDP connection: {e}"); + log::error!("Failed to create UDP connection: {}", e); } } } IpStackStream::UnknownTransport(u) => { let len = u.payload().len(); - log::info!("#0 unhandled transport - Ip Protocol {:?}, length {}", u.ip_protocol(), len); + log::info!("#0 unhandled transport - Ip Protocol 0x{:02X}, length {}", u.ip_protocol(), len); continue; } IpStackStream::UnknownNetwork(pkt) => { @@ -393,7 +393,7 @@ where } } } - Ok(task_count.load(Relaxed)) + Ok(()) } async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>) -> crate::Result<()> { @@ -402,7 +402,7 @@ async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc { // indicate UDP read fails not an error. - log::debug!("Virtual DNS session error: {e}"); + log::debug!("Virtual DNS session error: {}", e); break; } Ok(len) => len, @@ -412,7 +412,7 @@ async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc {ip}"); + log::debug!("Virtual DNS query: {} -> {}", qname, ip); } Ok(()) } @@ -431,7 +431,7 @@ where total += n as u64; let (tx, rx) = if is_tx { (n, 0) } else { (0, n) }; if let Err(e) = crate::traffic_status::traffic_status_update(tx, rx) { - log::debug!("Record traffic status error: {e}"); + log::debug!("Record traffic status error: {}", e); } writer.write_all(&buf[..n]).await?; } @@ -453,7 +453,7 @@ async fn handle_tcp_session( let mut server = create_tcp_stream(&socket_queue, server_addr).await?; - log::info!("Beginning {session_info}"); + log::info!("Beginning {}", session_info); if let Err(e) = handle_proxy_session(&mut server, proxy_handler).await { tcp_stack.shutdown().await?; @@ -467,19 +467,19 @@ async fn handle_tcp_session( async move { let r = copy_and_record_traffic(&mut t_rx, &mut s_tx, true).await; if let Err(err) = s_tx.shutdown().await { - log::trace!("{session_info} s_tx shutdown error {err}"); + log::trace!("{} s_tx shutdown error {}", session_info, err); } r }, async move { let r = copy_and_record_traffic(&mut s_rx, &mut t_tx, false).await; if let Err(err) = t_tx.shutdown().await { - log::trace!("{session_info} t_tx shutdown error {err}"); + log::trace!("{} t_tx shutdown error {}", session_info, err); } r }, ); - log::info!("Ending {session_info} with {res:?}"); + log::info!("Ending {} with {:?}", session_info, res); Ok(()) } @@ -509,7 +509,7 @@ async fn handle_udp_gateway_session( None => { let mut tcp_server_stream = create_tcp_stream(&socket_queue, proxy_server_addr).await?; if let Err(e) = handle_proxy_session(&mut tcp_server_stream, proxy_handler).await { - return Err(format!("udpgw connection error: {e}").into()); + return Err(format!("udpgw connection error: {}", e).into()); } break UdpGwClientStream::new(tcp_server_stream); } @@ -555,16 +555,13 @@ async fn handle_udp_gateway_session( stream.update_activity(); } ret = UdpGwClient::recv_udpgw_packet(udp_mtu, udp_timeout, &mut reader) => { - if let Ok((len, _)) = ret { - crate::traffic_status::traffic_status_update(0, len)?; - } match ret { Err(e) => { log::warn!("[UdpGw] Ending stream {} {} <> {} with recv_udpgw_packet {}", sn, &tcp_local_addr, udp_dst, e); stream.close(); break; } - Ok((_, packet)) => match packet { + Ok(packet) => match packet { //should not received keepalive UdpGwResponse::KeepAlive => { log::error!("[UdpGw] Ending stream {} {} <> {} with recv keepalive", sn, &tcp_local_addr, udp_dst); @@ -591,6 +588,7 @@ async fn handle_udp_gateway_session( log::error!("[UdpGw] Ending stream {} {} <> {} with send_udp_packet {}", sn, &tcp_local_addr, udp_dst, e); break; } + crate::traffic_status::traffic_status_update(0, len)?; } } } @@ -625,7 +623,7 @@ async fn handle_udp_associate_session( ) }; - log::info!("Beginning {session_info}"); + log::info!("Beginning {}", session_info); // `_server` is meaningful here, it must be alive all the time // to ensure that UDP transmission will not be interrupted accidentally. @@ -702,7 +700,7 @@ async fn handle_udp_associate_session( } } - log::info!("Ending {session_info}"); + log::info!("Ending {}", session_info); Ok(()) } @@ -721,7 +719,7 @@ async fn handle_dns_over_tcp_session( let mut server = create_tcp_stream(&socket_queue, server_addr).await?; - log::info!("Beginning {session_info}"); + log::info!("Beginning {}", session_info); let _ = handle_proxy_session(&mut server, proxy_handler).await?; @@ -774,7 +772,7 @@ async fn handle_dns_over_tcp_session( 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:?}"); + log::trace!("DNS over TCP query result: {} -> {:?}", name, ip); if !ipv6_enabled { dns::remove_ipv6_entries(&mut message); @@ -794,7 +792,7 @@ async fn handle_dns_over_tcp_session( } } - log::info!("Ending {session_info}"); + log::info!("Ending {}", session_info); Ok(()) } diff --git a/src/mobile_api.rs b/src/mobile_api.rs new file mode 100644 index 0000000..a32f68d --- /dev/null +++ b/src/mobile_api.rs @@ -0,0 +1,83 @@ +#![cfg(any(target_os = "ios", target_os = "android", target_os = "macos"))] + +use crate::Args; +use std::os::raw::c_int; + +static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); + +/// Dummy function to make the build pass. +#[doc(hidden)] +#[cfg(not(target_os = "macos"))] +pub async fn desktop_run_async(_: Args, _: tokio_util::sync::CancellationToken) -> std::io::Result<()> { + Ok(()) +} + +pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int { + let shutdown_token = tokio_util::sync::CancellationToken::new(); + { + if let Ok(mut lock) = TUN_QUIT.lock() { + if lock.is_some() { + log::error!("tun2proxy already started"); + return -1; + } + *lock = Some(shutdown_token.clone()); + } else { + log::error!("failed to lock tun2proxy quit token"); + return -2; + } + } + + let block = async move { + let mut config = tun::Configuration::default(); + + #[cfg(unix)] + if let Some(fd) = args.tun_fd { + config.raw_fd(fd); + if let Some(v) = args.close_fd_on_drop { + config.close_fd_on_drop(v); + }; + } else if let Some(ref tun) = args.tun { + config.tun_name(tun); + } + #[cfg(windows)] + if let Some(ref tun) = args.tun { + config.tun_name(tun); + } + + #[cfg(any(target_os = "ios", target_os = "macos"))] + config.platform_config(|config| { + config.packet_information(_packet_information); + }); + + let device = tun::create_as_async(&config).map_err(std::io::Error::from)?; + let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); + + join_handle.await.map_err(std::io::Error::from)? + }; + + let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + Err(e) => { + log::error!("failed to create tokio runtime with error: {:?}", e); + -1 + } + Ok(rt) => match rt.block_on(block) { + Ok(_) => 0, + Err(e) => { + log::error!("failed to run tun2proxy with error: {:?}", e); + -2 + } + }, + }; + + exit_code +} + +pub fn mobile_stop() -> c_int { + if let Ok(mut lock) = TUN_QUIT.lock() { + if let Some(shutdown_token) = lock.take() { + shutdown_token.cancel(); + return 0; + } + } + -1 +} diff --git a/src/session_info.rs b/src/session_info.rs index a0784a9..fc4e938 100644 --- a/src/session_info.rs +++ b/src/session_info.rs @@ -16,7 +16,7 @@ impl std::fmt::Display for IpProtocol { IpProtocol::Tcp => write!(f, "TCP"), IpProtocol::Udp => write!(f, "UDP"), IpProtocol::Icmp => write!(f, "ICMP"), - IpProtocol::Other(v) => write!(f, "Other(0x{v:02X})"), + IpProtocol::Other(v) => write!(f, "Other(0x{:02X})", v), } } } diff --git a/src/socket_transfer.rs b/src/socket_transfer.rs index 4c81da7..f10b3f2 100644 --- a/src/socket_transfer.rs +++ b/src/socket_transfer.rs @@ -1,10 +1,10 @@ #![cfg(target_os = "linux")] -use crate::{SocketDomain, SocketProtocol, error}; +use crate::{error, SocketDomain, SocketProtocol}; use nix::{ errno::Errno, fcntl::{self, FdFlag}, - sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, SockType, cmsg_space, getsockopt, recvmsg, sendmsg, sockopt}, + sys::socket::{cmsg_space, getsockopt, recvmsg, sendmsg, sockopt, ControlMessage, ControlMessageOwned, MsgFlags, SockType}, }; use serde::{Deserialize, Serialize}; use std::{ @@ -16,31 +16,31 @@ use tokio::net::{TcpSocket, UdpSocket, UnixDatagram}; const REQUEST_BUFFER_SIZE: usize = 64; -#[derive(bincode::Encode, bincode::Decode, Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] struct Request { protocol: SocketProtocol, domain: SocketDomain, number: u32, } -#[derive(bincode::Encode, bincode::Decode, PartialEq, Debug, Hash, Copy, Clone, Eq, Serialize, Deserialize)] +#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] enum Response { Ok, } /// Reconstruct socket from raw `fd` pub fn reconstruct_socket(fd: RawFd) -> Result { + // Check if `fd` is valid + let fd_flags = fcntl::fcntl(fd, fcntl::F_GETFD)?; + // `fd` is confirmed to be valid so it should be closed let socket = unsafe { OwnedFd::from_raw_fd(fd) }; - // Check if `fd` is valid - let fd_flags = fcntl::fcntl(socket.as_fd(), fcntl::F_GETFD)?; - // Insert CLOEXEC flag to the `fd` to prevent further propagation across `execve(2)` calls let mut fd_flags = FdFlag::from_bits(fd_flags).ok_or(ErrorKind::Unsupported)?; if !fd_flags.contains(FdFlag::FD_CLOEXEC) { fd_flags.insert(FdFlag::FD_CLOEXEC); - fcntl::fcntl(socket.as_fd(), fcntl::F_SETFD(fd_flags))?; + fcntl::fcntl(fd, fcntl::F_SETFD(fd_flags))?; } Ok(socket) @@ -70,12 +70,12 @@ pub async fn create_transfer_socket_pair() -> std::io::Result<(UnixDatagram, Own let remote_fd: OwnedFd = remote.into_std().unwrap().into(); // Get `remote_fd` flags - let fd_flags = fcntl::fcntl(remote_fd.as_fd(), fcntl::F_GETFD)?; + let fd_flags = fcntl::fcntl(remote_fd.as_raw_fd(), fcntl::F_GETFD)?; // Remove CLOEXEC flag from the `remote_fd` to allow propagating across `execve(2)` let mut fd_flags = FdFlag::from_bits(fd_flags).ok_or(ErrorKind::Unsupported)?; fd_flags.remove(FdFlag::FD_CLOEXEC); - fcntl::fcntl(remote_fd.as_fd(), fcntl::F_SETFD(fd_flags))?; + fcntl::fcntl(remote_fd.as_raw_fd(), fcntl::F_SETFD(fd_flags))?; Ok((local, remote_fd)) } @@ -135,21 +135,14 @@ where // Borrow socket as mut to prevent multiple simultaneous requests let socket = socket.deref_mut(); - let mut request = [0u8; 1000]; - // Send request - let size = bincode::encode_into_slice( - Request { - protocol: T::domain(), - domain, - number, - }, - &mut request, - bincode::config::standard(), - ) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let request = bincode::serialize(&Request { + protocol: T::domain(), + domain, + number, + })?; - socket.send(&request[..size]).await?; + socket.send(&request[..]).await?; // Receive response loop { @@ -157,7 +150,8 @@ where let mut buf = [0_u8; REQUEST_BUFFER_SIZE]; let mut iov = [IoSliceMut::new(&mut buf[..])]; - let mut cmsg = vec![0; cmsg_space::() * number as usize]; + let mut cmsg = Vec::with_capacity(cmsg_space::() * number as usize); + let msg = recvmsg::<()>(socket.as_fd().as_raw_fd(), &mut iov, Some(&mut cmsg), MsgFlags::empty()); let msg = match msg { @@ -167,9 +161,7 @@ where // Parse response let response = &msg.iovs().next().unwrap()[..msg.bytes]; - let response: Response = bincode::decode_from_slice(response, bincode::config::standard()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))? - .0; + let response: Response = bincode::deserialize(response)?; if !matches!(response, Response::Ok) { return Err("Request for new sockets failed".into()); } @@ -202,14 +194,10 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()> let len = socket.recv(&mut buf[..]).await?; - let request: Request = bincode::decode_from_slice(&buf[..len], bincode::config::standard()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))? - .0; + let request: Request = bincode::deserialize(&buf[..len])?; let response = Response::Ok; - let mut buf = [0u8; 1000]; - let size = bincode::encode_into_slice(response, &mut buf, bincode::config::standard()) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let buf = bincode::serialize(&response)?; let mut owned_fd_buf: Vec = Vec::with_capacity(request.number as usize); for _ in 0..request.number { @@ -235,7 +223,7 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()> let raw_fd_buf: Vec = owned_fd_buf.iter().map(|fd| fd.as_raw_fd()).collect(); let cmsg = ControlMessage::ScmRights(&raw_fd_buf[..]); - let iov = [IoSlice::new(&buf[..size])]; + let iov = [IoSlice::new(&buf[..])]; sendmsg::<()>(socket.as_raw_fd(), &iov, &[cmsg], MsgFlags::empty(), None)?; } diff --git a/src/socks.rs b/src/socks.rs index e2265ba..3800c6a 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -4,7 +4,7 @@ use crate::{ proxy_handler::{ProxyHandler, ProxyHandlerManager}, session_info::SessionInfo, }; -use socks5_impl::protocol::{self, Address, AuthMethod, StreamOperation, UserKey, Version, handshake, password_method}; +use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version}; use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; use tokio::sync::Mutex; @@ -78,7 +78,7 @@ impl SocksProxyImpl { } } SocketAddr::V6(addr) => { - return Err(format!("SOCKS4 does not support IPv6: {addr}").into()); + return Err(format!("SOCKS4 does not support IPv6: {}", addr).into()); } } self.server_outbuf.extend(ip_vec); @@ -136,7 +136,7 @@ impl SocksProxyImpl { let response = handshake::Response::retrieve_from_stream(&mut self.server_inbuf.clone()); if let Err(e) = response { if e.kind() == std::io::ErrorKind::UnexpectedEof { - log::trace!("receive_server_hello_socks5 needs more data \"{e}\"..."); + log::trace!("receive_server_hello_socks5 needs more data \"{}\"...", e); return Ok(()); } else { return Err(e); @@ -181,7 +181,7 @@ impl SocksProxyImpl { let response = Response::retrieve_from_stream(&mut self.server_inbuf.clone()); if let Err(e) = response { if e.kind() == std::io::ErrorKind::UnexpectedEof { - log::trace!("receive_auth_data needs more data \"{e}\"..."); + log::trace!("receive_auth_data needs more data \"{}\"...", e); return Ok(()); } else { return Err(e); @@ -213,7 +213,7 @@ impl SocksProxyImpl { let response = protocol::Response::retrieve_from_stream(&mut self.server_inbuf.clone()); if let Err(e) = response { if e.kind() == std::io::ErrorKind::UnexpectedEof { - log::trace!("receive_connection_status needs more data \"{e}\"..."); + log::trace!("receive_connection_status needs more data \"{}\"...", e); return Ok(()); } else { return Err(e); diff --git a/src/traffic_status.rs b/src/traffic_status.rs index 9f32b7c..3117a22 100644 --- a/src/traffic_status.rs +++ b/src/traffic_status.rs @@ -5,7 +5,7 @@ use std::sync::{LazyLock, Mutex}; /// # Safety /// /// set traffic status callback. -#[unsafe(no_mangle)] +#[no_mangle] pub unsafe extern "C" fn tun2proxy_set_traffic_status_callback( send_interval_secs: u32, callback: Option, @@ -34,7 +34,7 @@ struct TrafficStatusCallback(Option> = LazyLock::new(|| Mutex: pub(crate) fn traffic_status_update(delta_tx: usize, delta_rx: usize) -> Result<()> { { let is_none_or_error = TRAFFIC_STATUS_CALLBACK.lock().map(|guard| guard.is_none()).unwrap_or_else(|e| { - log::error!("Failed to acquire lock: {e}"); + log::error!("Failed to acquire lock: {}", e); true }); if is_none_or_error { diff --git a/src/udpgw.rs b/src/udpgw.rs index 24edaad..be791f8 100644 --- a/src/udpgw.rs +++ b/src/udpgw.rs @@ -4,11 +4,11 @@ use std::{collections::VecDeque, hash::Hash, net::SocketAddr, sync::atomic::Orde use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::{ - TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}, + TcpStream, }, sync::Mutex, - time::{Duration, sleep}, + time::{sleep, Duration}, }; pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::(); @@ -32,9 +32,9 @@ impl std::fmt::Display for UdpFlag { 0x01 => "KEEPALIVE", 0x20 => "ERR", 0x02 => "DATA", - n => return write!(f, "Unknown UdpFlag(0x{n:02X})"), + n => return write!(f, "Unknown UdpFlag(0x{:02X})", n), }; - write!(f, "{flag}") + write!(f, "{}", flag) } } @@ -218,7 +218,7 @@ impl StreamOperation for Packet { impl AsyncStreamOperation for Packet { async fn retrieve_from_async_stream(r: &mut R) -> std::io::Result where - R: tokio::io::AsyncRead + Unpin + Send + ?Sized, + R: tokio::io::AsyncRead + Unpin + Send, Self: Sized, { let mut buf = [0; UDPGW_LENGTH_FIELD_SIZE]; @@ -277,7 +277,7 @@ impl StreamOperation for UdpgwHeader { impl AsyncStreamOperation for UdpgwHeader { async fn retrieve_from_async_stream(r: &mut R) -> std::io::Result where - R: tokio::io::AsyncRead + Unpin + Send + ?Sized, + R: tokio::io::AsyncRead + Unpin + Send, Self: Sized, { let mut buf = [0; UdpgwHeader::static_len()]; @@ -332,7 +332,7 @@ impl std::fmt::Display for UdpGwResponse { UdpGwResponse::KeepAlive => write!(f, "KeepAlive"), UdpGwResponse::Error => write!(f, "Error"), UdpGwResponse::TcpClose => write!(f, "TcpClose"), - UdpGwResponse::Data(packet) => write!(f, "Data({packet})"), + UdpGwResponse::Data(packet) => write!(f, "Data({})", packet), } } } @@ -467,8 +467,6 @@ impl UdpGwClient { } } - let (mut tx, mut rx) = (0, 0); - for mut stream in streams { if stream.last_activity.elapsed() < self.keepalive_time { self.store_server_connection(stream).await; @@ -485,26 +483,20 @@ impl UdpGwClient { let local_addr = stream_writer.local_addr()?; let sn = stream.serial_number(); let keepalive_packet: Vec = Packet::build_keepalive_packet(sn).into(); - tx += keepalive_packet.len(); if let Err(e) = stream_writer.write_all(&keepalive_packet).await { - log::warn!("stream {sn} {local_addr:?} send keepalive failed: {e}"); + log::warn!("stream {} {:?} send keepalive failed: {}", sn, local_addr, e); continue; } match UdpGwClient::recv_udpgw_packet(self.udp_mtu, self.udp_timeout, &mut stream_reader).await { - Ok((len, UdpGwResponse::KeepAlive)) => { + Ok(UdpGwResponse::KeepAlive) => { stream.update_activity(); self.store_server_connection_full(stream, stream_reader, stream_writer).await; - log::trace!("stream {sn} {local_addr:?} send keepalive and recieve it successfully"); - rx += len; + log::trace!("stream {sn} {:?} send keepalive and recieve it successfully", local_addr); } - Ok((len, v)) => { - log::debug!("stream {sn} {local_addr:?} keepalive unexpected response: {v}"); - rx += len; - } - Err(e) => log::debug!("stream {sn} {local_addr:?} keepalive no response, error \"{e}\""), + Ok(v) => log::debug!("stream {sn} {:?} keepalive unexpected response: {v}", local_addr), + Err(e) => log::debug!("stream {sn} {:?} keepalive no response, error \"{e}\"", local_addr), } } - crate::traffic_status::traffic_status_update(tx, rx)?; } } @@ -534,14 +526,14 @@ impl UdpGwClient { /// /// # Returns /// - `Result`: Returns a result type containing the parsed UDP gateway response, or an error if one occurs. - pub(crate) async fn recv_udpgw_packet(udp_mtu: u16, udp_timeout: u64, stream: &mut OwnedReadHalf) -> Result<(usize, UdpGwResponse)> { + pub(crate) async fn recv_udpgw_packet(udp_mtu: u16, udp_timeout: u64, stream: &mut OwnedReadHalf) -> Result { let packet = tokio::time::timeout( tokio::time::Duration::from_secs(udp_timeout + 2), Packet::retrieve_from_async_stream(stream), ) .await .map_err(std::io::Error::from)??; - Ok((packet.len(), UdpGwClient::parse_udp_response(udp_mtu, packet)?)) + UdpGwClient::parse_udp_response(udp_mtu, packet) } /// Sends a UDP gateway packet. diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index 7794d37..8dae2a7 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -1,5 +1,5 @@ use crate::error::Result; -use hashlink::{LruCache, linked_hash_map::RawEntryMut}; +use hashlink::{linked_hash_map::RawEntryMut, LruCache}; use std::{ collections::HashMap, convert::TryInto, diff --git a/src/win_svc.rs b/src/win_svc.rs index 2fed698..afca2f1 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -16,7 +16,7 @@ fn my_service_main(arguments: Vec) { // `service_dispatcher::start` from `main`. if let Err(_e) = run_service(arguments) { - log::error!("Error: {_e:?}"); + log::error!("Error: {:?}", _e); } } @@ -73,21 +73,13 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; rt.block_on(async { unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) { - let status = unsafe { &*status }; + let status = &*status; log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); } unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - let ret = crate::general_run_async(args.clone(), tun::DEFAULT_MTU, false, shutdown_token).await; - match &ret { - Ok(sessions) => { - if args.exit_on_fatal_error && *sessions >= args.max_sessions { - log::error!("Forced exit due to max sessions reached ({sessions}/{})", args.max_sessions); - std::process::exit(-1); - } - log::debug!("tun2proxy exited normally, current sessions: {sessions}"); - } - Err(err) => log::error!("main loop error: {err}"), + if let Err(err) = crate::desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); } Ok::<(), crate::Error>(()) })?;