diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..219c13a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "cargo"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml
new file mode 100644
index 0000000..1280f57
--- /dev/null
+++ b/.github/workflows/auto-merge.yaml
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 0000000..d06ff8a
--- /dev/null
+++ b/.github/workflows/close-stale-issues.yml
@@ -0,0 +1,26 @@
+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 ca6ae54..a848c80 100644
--- a/.github/workflows/publish-docker.yml
+++ b/.github/workflows/publish-docker.yml
@@ -23,6 +23,15 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
+
+ # Add support for more platforms with QEMU (optional)
+ # https://github.com/docker/setup-qemu-action
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - 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@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
@@ -30,19 +39,33 @@ jobs:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- - name: Build and push Docker image
+ - name: Build gnu and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
+ platforms: linux/amd64,linux/arm64
context: .
+ file: Dockerfile.ubuntu
push: true
- tags: ${{ steps.meta.outputs.tags }}
+ tags: ${{ steps.meta.outputs.tags }}-ubuntu
labels: ${{ steps.meta.outputs.labels }}
+
+ - name: Build musl and push Docker image
+ uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
+ with:
+ platforms: linux/amd64
+ context: .
+ file: Dockerfile.alpine
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}-alpine
+ labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml
index 5e9e500..697f352 100644
--- a/.github/workflows/publish-exe.yml
+++ b/.github/workflows/publish-exe.yml
@@ -1,4 +1,5 @@
on:
+ workflow_dispatch:
push:
tags:
- "v*.*.*"
@@ -8,29 +9,42 @@ name: Publish Releases
jobs:
build_publish:
name: Publishing Tasks
+
+ permissions:
+ contents: write
+ id-token: write
+ attestations: write
+
strategy:
+ fail-fast: false
matrix:
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- i686-unknown-linux-musl
- aarch64-unknown-linux-gnu
- - armv7-unknown-linux-gnueabihf
+ - armv7-unknown-linux-musleabi
+ - armv7-unknown-linux-musleabihf
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
+ - aarch64-pc-windows-msvc
+ - x86_64-win7-windows-msvc
+ - i686-win7-windows-msvc
include:
- target: x86_64-unknown-linux-gnu
- host_os: ubuntu-latest
+ host_os: ubuntu-22.04
- target: x86_64-unknown-linux-musl
host_os: ubuntu-latest
- target: i686-unknown-linux-musl
host_os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
host_os: ubuntu-latest
- - target: armv7-unknown-linux-gnueabihf
+ - target: armv7-unknown-linux-musleabi
+ host_os: ubuntu-latest
+ - target: armv7-unknown-linux-musleabihf
host_os: ubuntu-latest
- target: x86_64-apple-darwin
host_os: macos-latest
@@ -40,43 +54,87 @@ jobs:
host_os: windows-latest
- target: i686-pc-windows-msvc
host_os: windows-latest
+ - target: aarch64-pc-windows-msvc
+ host_os: windows-latest
+ - target: x86_64-win7-windows-msvc
+ host_os: windows-latest
+ - target: i686-win7-windows-msvc
+ host_os: windows-latest
runs-on: ${{ matrix.host_os }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
- name: Prepare
shell: bash
run: |
mkdir mypubdir4
- rustup target add ${{ matrix.target }}
- if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
+ if [[ "${{ matrix.target }}" != "x86_64-win7-windows-msvc" && "${{ matrix.target }}" != "i686-win7-windows-msvc" ]]; then
+ rustup target add ${{ matrix.target }}
+ fi
+ cargo install cbindgen
+ if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-22.04" ]]; then
sudo .github/workflows/install-cross.sh
fi
- name: Build
+ if: ${{ !cancelled() }}
shell: bash
run: |
- if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
+ if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-22.04" ]]; then
cross build --all-features --release --target ${{ matrix.target }}
else
- cargo build --all-features --release --target ${{ matrix.target }}
+ if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then
+ rustup toolchain install nightly
+ rustup component add rust-src --toolchain nightly
+ cargo +nightly build --release -Z build-std --target ${{ matrix.target }}
+ else
+ cargo build --all-features --release --target ${{ matrix.target }}
+ fi
fi
- cbindgen --config cbindgen.toml -l C -o target/tun2proxy-ffi.h
+ cbindgen --config cbindgen.toml -o target/tun2proxy.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.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
+ 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
elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then
- zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib
- elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
- zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so
+ 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
+ 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
+ if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then
+ ./build-android.sh
+ cp ./tun2proxy-android-libs.zip ./mypubdir4/
+ fi
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 d66b58a..82fa836 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -1,7 +1,13 @@
name: Push or PR
on:
- [push, pull_request]
+ workflow_dispatch:
+ push:
+ branches:
+ - '**'
+ pull_request:
+ branches:
+ - '**'
env:
CARGO_TERM_COLOR: always
@@ -9,18 +15,99 @@ env:
jobs:
build_n_test:
strategy:
+ fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+
- name: rustfmt
+ if: ${{ !cancelled() }}
run: cargo fmt --all -- --check
+
- name: check
+ if: ${{ !cancelled() }}
run: cargo check --verbose
+
- name: clippy
+ if: ${{ !cancelled() }}
run: cargo clippy --all-targets --all-features -- -D warnings
+
- name: Build
- run: cargo build --verbose --tests --all-features
+ if: ${{ !cancelled() }}
+ run: |
+ cargo build --verbose --tests --all-features
+ cargo clean
+ cargo build --verbose
+
+ - name: Abort on error
+ 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:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Check semver
+ if: ${{ !cancelled() }}
+ uses: obi1kenobi/cargo-semver-checks-action@v2
+ - name: Abort on error
+ if: ${{ failure() }}
+ run: echo "Semver check failed" && false
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..32d02f5
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,45 @@
+on:
+ pull_request_review:
+ types: [submitted]
+ push:
+ workflow_dispatch:
+ pull_request_target:
+ types: [labeled]
+
+name: Integration Tests
+
+jobs:
+ proxy_tests:
+ name: Proxy Tests
+ runs-on: ubuntu-latest
+ if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test')) && github.actor != 'dependabot[bot]' && github.actor != 'github-actions[bot]'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Populate .env
+ env:
+ DOTENV: ${{ secrets.DOTENV }}
+ run: |
+ echo "$DOTENV" > tests/.env
+ ln -s tests/.env
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.x'
+
+ - name: Create virtual environment
+ run: python -m venv venv
+
+ - name: Activate virtual environment and install dependencies
+ run: |
+ source venv/bin/activate
+ pip install -r tests/requirements.txt
+
+ - name: Build project
+ run: cargo build --release
+
+ - name: Run tests
+ run: |
+ source venv/bin/activate
+ python tests/tests.py
diff --git a/.gitignore b/.gitignore
index 2ac3387..e163239 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,10 @@
+tun2proxy-android-libs.zip
+tun2proxy-android-libs/
+tun2proxy.xcframework/
.env
project.xcworkspace/
xcuserdata/
+.vs/
.vscode/
.VSCodeCounter/
build/
diff --git a/Cargo.toml b/Cargo.toml
index 950c1a1..f3b06b8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,49 +1,82 @@
[package]
name = "tun2proxy"
-version = "0.2.11"
-edition = "2021"
+version = "0.7.10"
+edition = "2024"
license = "MIT"
-repository = "https://github.com/blechschmidt/tun2proxy"
-homepage = "https://github.com/blechschmidt/tun2proxy"
+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"
[lib]
crate-type = ["staticlib", "cdylib", "lib"]
+[[bin]]
+name = "tun2proxy-bin"
+path = "src/bin/main.rs"
+
+[[bin]]
+name = "udpgw-server"
+path = "src/bin/udpgw_server.rs"
+required-features = ["udpgw"]
+
+[features]
+default = ["udpgw"]
+udpgw = []
+
[dependencies]
-async-recursion = "1.0"
async-trait = "0.1"
-base64 = { version = "0.21" }
+base64easy = "0.1"
chrono = "0.4"
-clap = { version = "4.5", features = ["derive", "wrap_help", "color"] }
-ctrlc2 = { version = "3.5", features = ["tokio", "termination"] }
+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.9"
-httparse = "1.8"
-ipstack = { version = "0.0", features = ["log"] }
+hashlink = "0.10"
+hickory-proto = "0.25"
+httparse = "1"
+ipstack = { version = "0.4" }
log = { version = "0.4", features = ["std"] }
-socks5-impl = { version = "0.5" }
-thiserror = "1.0"
-tokio = { version = "1.36", features = ["full"] }
+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 = "2.0", features = ["log"] }
-trust-dns-proto = "0.23"
-tun2 = { version = "1.1", features = ["async"] }
-udp-stream = { version = "0.0", default-features = false }
-unicase = "2.7"
-url = "2.5"
+tproxy-config = { version = "6", 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.13"
+android_logger = "0.15"
jni = { version = "0.21", default-features = false }
-[build-dependencies]
-serde_json = "1.0"
+[target.'cfg(target_os="linux")'.dependencies]
+bincode = "2"
+serde = { version = "1", features = ["derive"] }
-[[bin]]
-name = "tun2proxy"
-path = "src/bin/main.rs"
+[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"
diff --git a/Dockerfile.alpine b/Dockerfile.alpine
new file mode 100644
index 0000000..570b819
--- /dev/null
+++ b/Dockerfile.alpine
@@ -0,0 +1,18 @@
+####################################################################################################
+## Builder
+####################################################################################################
+FROM rust:latest AS builder
+WORKDIR /worker
+COPY ./ .
+RUN rustup target add x86_64-unknown-linux-musl
+RUN cargo build --release --target x86_64-unknown-linux-musl
+
+####################################################################################################
+## Final image
+####################################################################################################
+FROM alpine:latest
+RUN apk add --no-cache iproute2
+
+COPY --from=builder /worker/target/x86_64-unknown-linux-musl/release/tun2proxy-bin /usr/bin/tun2proxy-bin
+
+ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"]
diff --git a/Dockerfile b/Dockerfile.ubuntu
similarity index 73%
rename from Dockerfile
rename to Dockerfile.ubuntu
index 708d60f..e6ad592 100644
--- a/Dockerfile
+++ b/Dockerfile.ubuntu
@@ -5,7 +5,7 @@ FROM rust:latest AS builder
WORKDIR /worker
COPY ./ .
-RUN cargo build --release --target x86_64-unknown-linux-gnu
+RUN cargo build --release
####################################################################################################
@@ -15,6 +15,6 @@ FROM ubuntu:latest
RUN apt update && apt install -y iproute2 && apt clean all
-COPY --from=builder /worker/target/x86_64-unknown-linux-gnu/release/tun2proxy /usr/bin/tun2proxy
+COPY --from=builder /worker/target/release/tun2proxy-bin /usr/bin/tun2proxy-bin
-ENTRYPOINT ["/usr/bin/tun2proxy", "--setup"]
+ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"]
diff --git a/README.md b/README.md
index 15ab769..18be7ba 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,15 @@
+[](https://github.com/tun2proxy/tun2proxy)
+
# tun2proxy
A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows.
[](https://crates.io/crates/tun2proxy)
-
+[](https://docs.rs/tun2proxy)
[](https://docs.rs/tun2proxy)
[](https://crates.io/crates/tun2proxy)
-[](https://github.com/blechschmidt/tun2proxy/blob/master/LICENSE)
+[](https://github.com/tun2proxy/tun2proxy/blob/master/LICENSE)
-> Additional information can be found in the [wiki](https://github.com/blechschmidt/tun2proxy/wiki)
+> Additional information can be found in the [wiki](https://github.com/tun2proxy/tun2proxy/wiki)
## Features
- HTTP proxy support (unauthenticated, basic and digest auth)
@@ -15,9 +17,10 @@ A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and
- SOCKS4a and SOCKS5h support (through the virtual DNS feature)
- Minimal configuration setup for routing all traffic
- IPv4 and IPv6 support
-- GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/blechschmidt/tun2proxy/issues/35))
+- GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/tun2proxy/tun2proxy/issues/35))
- SOCKS5 UDP support
- Native support for proxying DNS over TCP
+- UdpGW (UDP gateway) support for UDP over TCP, see the [wiki](https://github.com/tun2proxy/tun2proxy/wiki/UDP-gateway-feature) for more information
## Build
Clone the repository and `cd` into the project folder. Then run the following:
@@ -25,11 +28,29 @@ Clone the repository and `cd` into the project folder. Then run the following:
cargo build --release
```
+### Building Framework for Apple Devices
+To build an XCFramework for macOS and iOS, run the following:
+```
+./build-apple.sh
+```
+
## Installation
### Install from binary
-Download the binary from [releases](https://github.com/blechschmidt/tun2proxy/releases) and put it in your `PATH`.
+Download the binary from [releases](https://github.com/tun2proxy/tun2proxy/releases) and put it in your `PATH`.
+
+
+ Authenticity Verification
+
+Since v0.2.23 [build provenance attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli)
+are supported. These allow you to ensure that the builds have been generated from the code on GitHub through the GitHub
+CI/CD pipeline. To verify the authenticity of the build files, you can use the [GitHub CLI](https://cli.github.com/):
+```shell
+gh attestation verify <*.zip file> --owner tun2proxy
+```
+
+
### Install from source
@@ -48,14 +69,14 @@ describing the manual setup, except that a bind mount is used to overlay the `/e
You would then run the tool as follows:
```bash
-sudo ./target/release/tun2proxy --setup --proxy "socks5://1.2.3.4:1080"
+sudo ./target/release/tun2proxy-bin --setup --proxy "socks5://1.2.3.4:1080"
```
Apart from SOCKS5, SOCKS4 and HTTP are supported.
Note that if your proxy is a non-global IP address (e.g. because the proxy is provided by some tunneling tool running
locally), you will additionally need to provide the public IP address of the server through which the traffic is
-actually tunneled. In such a case, the tool will tell you to specify the address through `--bypass ` if you
+actually tunneled. In such a case, the tool will tell you to specify the address through `--bypass ` if you
wish to make use of the automated setup feature.
## Manual Setup
@@ -87,7 +108,7 @@ sudo ip route add 8000::/1 dev tun0
# Make sure that DNS queries are routed through the tunnel.
sudo sh -c "echo nameserver 198.18.0.1 > /etc/resolv.conf"
-./target/release/tun2proxy --tun tun0 --proxy "$PROXY_TYPE://$PROXY_IP:$PROXY_PORT"
+./target/release/tun2proxy-bin --tun tun0 --proxy "$PROXY_TYPE://$PROXY_IP:$PROXY_PORT"
```
This tool implements a virtual DNS feature that is used by switch `--dns virtual`. When a DNS packet to port 53 is detected, an IP
@@ -108,28 +129,51 @@ sudo ip link del tun0
```
Tunnel interface to proxy.
-Usage: tun2proxy [OPTIONS] --proxy
+Usage: tun2proxy-bin [OPTIONS] --proxy [ADMIN_COMMAND]...
+
+Arguments:
+ [ADMIN_COMMAND]... Specify a command to run with root-like capabilities in the new namespace when using `--unshare`. This could be
+ useful to start additional daemons, e.g. `openvpn` instance
Options:
- -p, --proxy Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of socks4,
- socks5, http. For example: socks5://myname:password@127.0.0.1:1080
- -t, --tun Name of the tun interface [default: tun0]
- --tun-fd File descriptor of the tun interface
- -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 privileges
- -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct]
- --dns-addr DNS resolver address [default: 8.8.8.8]
- -b, --bypass IPs used in routing setup which should bypass the tunnel
- -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace]
- -h, --help Print help
- -V, --version Print version
+ -p, --proxy Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of
+ socks4, socks5, http. Username and password are encoded in percent encoding. For example:
+ socks5://myname:pass%40word@127.0.0.1:1080
+ -t, --tun Name of the tun interface, such as tun0, utun4, etc. If this option is not provided, the
+ OS will generate a random one
+ --tun-fd File descriptor of the tun interface
+ --close-fd-on-drop Set whether to close the received raw file descriptor on drop or not. This setting is
+ dependent on [tun_fd] [possible values: true, false]
+ --unshare Create a tun interface in a newly created unprivileged namespace while maintaining proxy
+ connectivity via the global network namespace
+ --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)`
+ -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]
+ -b, --bypass IPs used in routing setup which should bypass the tunnel, in the form of IP or IP/CIDR.
+ Multiple IPs can be specified, e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8
+ --tcp-timeout TCP timeout in seconds [default: 600]
+ --udp-timeout UDP timeout in seconds [default: 10]
+ -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace]
+ --daemonize Daemonize for unix family or run as Windows service
+ --exit-on-fatal-error Exit immediately when fatal error occurs, useful for running as a service
+ --max-sessions Maximum number of sessions to be handled concurrently [default: 200]
+ --udpgw-server UDP gateway server address, forwards UDP packets via specified TCP server
+ --udpgw-connections Max connections for the UDP gateway, default value is 5
+ --udpgw-keepalive Keepalive interval in seconds for the UDP gateway, default value is 30
+ -h, --help Print help
+ -V, --version Print version
```
Currently, tun2proxy supports HTTP, SOCKS4/SOCKS4a and SOCKS5. A proxy is supplied to the `--proxy` argument in the
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.
-## Docker Support
+## Container Support
+### Docker
Tun2proxy can serve as a proxy for other Docker containers. To make use of that feature, first build the image:
```bash
@@ -144,7 +188,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):
@@ -154,6 +198,36 @@ docker run -it \
--network "container:tun2proxy" \
ubuntu:latest
```
+### Docker Compose
+
+Write 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:latest-ubuntu
+ 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
@@ -175,3 +249,10 @@ 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/apple/readme.md b/apple/readme.md
index 0185db4..afff997 100644
--- a/apple/readme.md
+++ b/apple/readme.md
@@ -1,4 +1,4 @@
-Build iOS framework
+Build iOS xcframework
----------------
# Install Rust build tools
@@ -8,14 +8,15 @@ Build iOS framework
- Install iOS target support: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios`
- Install cbindgen tool: `cargo install cbindgen`
-# Building iOS framework
+# Building iOS xcframework
-Due to an unknown reason at present, compiling Rust code inside Xcode fails, so you have to manually compile it. Please run the following command in zsh (or bash):
+Run the following command in zsh (or bash):
```bash
cd tun2proxy
-
-cargo build --release --target aarch64-apple-ios
-cargo build --release --target x86_64-apple-ios
-lipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a
-cbindgen --config cbindgen.toml -l C -o target/tun2proxy-ffi.h
+./build-apple.sh
```
+
+The script `build-apple.sh` will build the iOS/macOS xcframework and output it to `./tun2proxy.xcframework`
+
+To save the build time, you can use the `build-aarch64-apple-ios-debug.sh` or `build-aarch64-apple-ios.sh` script
+to build the `aarch64-apple-ios` target only.
diff --git a/apple/tun2proxy.xcodeproj/project.pbxproj b/apple/tun2proxy.xcodeproj/project.pbxproj
deleted file mode 100644
index 1d4c250..0000000
--- a/apple/tun2proxy.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,398 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 55;
- objects = {
-
-/* Begin PBXBuildFile section */
- B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = B648A35829F43D110045B334 /* Tun2proxyWrapper.m */; };
- B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
- B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B692ACC829F7EA4C006BF04D /* libtun2proxy.a */; };
- B6DE654429F4255A00468184 /* tun2proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = B6DE654329F4255A00468184 /* tun2proxy.h */; settings = {ATTRIBUTES = (Public, ); }; };
-/* End PBXBuildFile section */
-
-/* Begin PBXFileReference section */
- B648A35829F43D110045B334 /* Tun2proxyWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tun2proxyWrapper.m; sourceTree = ""; };
- B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tun2proxyWrapper.h; sourceTree = ""; };
- B692ACC829F7EA4C006BF04D /* libtun2proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtun2proxy.a; path = ../target/libtun2proxy.a; sourceTree = ""; };
- B6DE654029F4255A00468184 /* tun2proxy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = tun2proxy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- B6DE654329F4255A00468184 /* tun2proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tun2proxy.h; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- B6DE653D29F4255A00468184 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- B692ACC729F7EA4C006BF04D /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- B692ACC829F7EA4C006BF04D /* libtun2proxy.a */,
- );
- name = Frameworks;
- sourceTree = "";
- };
- B6DE653629F4255A00468184 = {
- isa = PBXGroup;
- children = (
- B6DE654229F4255A00468184 /* tun2proxy */,
- B6DE654129F4255A00468184 /* Products */,
- B692ACC729F7EA4C006BF04D /* Frameworks */,
- );
- sourceTree = "";
- };
- B6DE654129F4255A00468184 /* Products */ = {
- isa = PBXGroup;
- children = (
- B6DE654029F4255A00468184 /* tun2proxy.framework */,
- );
- name = Products;
- sourceTree = "";
- };
- B6DE654229F4255A00468184 /* tun2proxy */ = {
- isa = PBXGroup;
- children = (
- B6DE654329F4255A00468184 /* tun2proxy.h */,
- B648A35829F43D110045B334 /* Tun2proxyWrapper.m */,
- B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */,
- );
- path = tun2proxy;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXHeadersBuildPhase section */
- B6DE653B29F4255A00468184 /* Headers */ = {
- isa = PBXHeadersBuildPhase;
- buildActionMask = 2147483647;
- files = (
- B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */,
- B6DE654429F4255A00468184 /* tun2proxy.h in Headers */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXHeadersBuildPhase section */
-
-/* Begin PBXNativeTarget section */
- B6DE653F29F4255A00468184 /* tun2proxy */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */;
- buildPhases = (
- B692ACB329F7E203006BF04D /* Run Script */,
- B6DE653B29F4255A00468184 /* Headers */,
- B6DE653C29F4255A00468184 /* Sources */,
- B6DE653D29F4255A00468184 /* Frameworks */,
- B6DE653E29F4255A00468184 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = tun2proxy;
- productName = tun2proxy;
- productReference = B6DE654029F4255A00468184 /* tun2proxy.framework */;
- productType = "com.apple.product-type.framework";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- B6DE653729F4255A00468184 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = 1;
- LastUpgradeCheck = 1430;
- TargetAttributes = {
- B6DE653F29F4255A00468184 = {
- CreatedOnToolsVersion = 13.2.1;
- };
- };
- };
- buildConfigurationList = B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */;
- compatibilityVersion = "Xcode 13.0";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = B6DE653629F4255A00468184;
- productRefGroup = B6DE654129F4255A00468184 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- B6DE653F29F4255A00468184 /* tun2proxy */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- B6DE653E29F4255A00468184 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- B692ACB329F7E203006BF04D /* Run Script */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- );
- name = "Run Script";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/bash;
- shellScript = "set -e\nPATH=\"$PATH:${HOME}/.cargo/bin\"\nRUST_PROJ=${PROJECT_DIR}/..\ncd \"${RUST_PROJ}\"\ncargo build --release --target aarch64-apple-ios\ncargo build --release --target x86_64-apple-ios\nlipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a\ncbindgen --config cbindgen.toml -l C -o target/tun2proxy-ffi.h\n";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- B6DE653C29F4255A00468184 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin XCBuildConfiguration section */
- B6DE654529F4255A00468184 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
- MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
- MTL_FAST_MATH = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- VERSIONING_SYSTEM = "apple-generic";
- VERSION_INFO_PREFIX = "";
- };
- name = Debug;
- };
- B6DE654629F4255A00468184 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- CURRENT_PROJECT_VERSION = 1;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- MTL_FAST_MATH = YES;
- SDKROOT = iphoneos;
- VALIDATE_PRODUCT = YES;
- VERSIONING_SYSTEM = "apple-generic";
- VERSION_INFO_PREFIX = "";
- };
- name = Release;
- };
- B6DE654829F4255A00468184 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CODE_SIGN_IDENTITY = "";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEFINES_MODULE = YES;
- DEVELOPMENT_TEAM = "";
- DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 1;
- DYLIB_INSTALL_NAME_BASE = "@rpath";
- ENABLE_BITCODE = NO;
- ENABLE_MODULE_VERIFIER = YES;
- GENERATE_INFOPLIST_FILE = YES;
- HEADER_SEARCH_PATHS = "";
- INFOPLIST_KEY_NSHumanReadableCopyright = "";
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- LIBRARY_SEARCH_PATHS = (
- ../target,
- "$(PROJECT_DIR)/../target",
- );
- MARKETING_VERSION = 1.0;
- MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
- MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
- PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy;
- PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
- SKIP_INSTALL = YES;
- SWIFT_EMIT_LOC_STRINGS = YES;
- TARGETED_DEVICE_FAMILY = "1,2";
- USER_HEADER_SEARCH_PATHS = ../target;
- };
- name = Debug;
- };
- B6DE654929F4255A00468184 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CODE_SIGN_IDENTITY = "";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEFINES_MODULE = YES;
- DEVELOPMENT_TEAM = "";
- DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 1;
- DYLIB_INSTALL_NAME_BASE = "@rpath";
- ENABLE_BITCODE = NO;
- ENABLE_MODULE_VERIFIER = YES;
- GENERATE_INFOPLIST_FILE = YES;
- HEADER_SEARCH_PATHS = "";
- INFOPLIST_KEY_NSHumanReadableCopyright = "";
- INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- LIBRARY_SEARCH_PATHS = (
- ../target,
- "$(PROJECT_DIR)/../target",
- );
- MARKETING_VERSION = 1.0;
- MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
- MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
- PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy;
- PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
- SKIP_INSTALL = YES;
- SWIFT_EMIT_LOC_STRINGS = YES;
- TARGETED_DEVICE_FAMILY = "1,2";
- USER_HEADER_SEARCH_PATHS = ../target;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- B6DE654529F4255A00468184 /* Debug */,
- B6DE654629F4255A00468184 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- B6DE654829F4255A00468184 /* Debug */,
- B6DE654929F4255A00468184 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = B6DE653729F4255A00468184 /* Project object */;
-}
diff --git a/apple/tun2proxy/Tun2proxyWrapper.h b/apple/tun2proxy/Tun2proxyWrapper.h
deleted file mode 100644
index 517192e..0000000
--- a/apple/tun2proxy/Tun2proxyWrapper.h
+++ /dev/null
@@ -1,22 +0,0 @@
-//
-// Tun2proxyWrapper.h
-// tun2proxy
-//
-// Created by ssrlive on 2023/4/23.
-//
-
-#ifndef Tun2proxyWrapper_h
-#define Tun2proxyWrapper_h
-
-@interface Tun2proxyWrapper : NSObject
-
-+ (void)startWithConfig:(NSString *)proxy_url
- tun_fd:(int)tun_fd
- tun_mtu:(uint16_t)tun_mtu
- dns_over_tcp:(bool)dns_over_tcp
- verbose:(bool)verbose;
-+ (void) shutdown;
-
-@end
-
-#endif /* Tun2proxyWrapper_h */
diff --git a/apple/tun2proxy/Tun2proxyWrapper.m b/apple/tun2proxy/Tun2proxyWrapper.m
deleted file mode 100644
index dd8c75d..0000000
--- a/apple/tun2proxy/Tun2proxyWrapper.m
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Tun2proxyWrapper.m
-// tun2proxy
-//
-// Created by ssrlive on 2023/4/23.
-//
-
-#import
-
-#import "Tun2proxyWrapper.h"
-#include "tun2proxy-ffi.h"
-
-@implementation Tun2proxyWrapper
-
-+ (void)startWithConfig:(NSString *)proxy_url
- tun_fd:(int)tun_fd
- tun_mtu:(uint16_t)tun_mtu
- dns_over_tcp:(bool)dns_over_tcp
- verbose:(bool)verbose {
- ArgDns dns_strategy = dns_over_tcp ? OverTcp : Direct;
- ArgVerbosity v = verbose ? Trace : Info;
- tun2proxy_run_with_fd(proxy_url.UTF8String, tun_fd, tun_mtu, dns_strategy, v);
-}
-
-+ (void)shutdown {
- tun2proxy_stop();
-}
-
-@end
diff --git a/apple/tun2proxy/tun2proxy.h b/apple/tun2proxy/tun2proxy.h
deleted file mode 100644
index d62e035..0000000
--- a/apple/tun2proxy/tun2proxy.h
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// tun2proxy.h
-// tun2proxy
-//
-// Created by tun2proxy on 2023/4/22.
-//
-
-#import
-
-//! Project version number for tun2proxy.
-FOUNDATION_EXPORT double tun2proxyVersionNumber;
-
-//! Project version string for tun2proxy.
-FOUNDATION_EXPORT const unsigned char tun2proxyVersionString[];
-
-// In this header, you should import all the public headers of your framework using statements like #import
-
-#import
diff --git a/build-aarch64-apple-ios-debug.sh b/build-aarch64-apple-ios-debug.sh
new file mode 100755
index 0000000..6c97921
--- /dev/null
+++ b/build-aarch64-apple-ios-debug.sh
@@ -0,0 +1,26 @@
+#! /bin/sh
+
+echo "Setting up the rust environment..."
+rustup target add aarch64-apple-ios
+cargo install cbindgen
+
+echo "Building target aarch64-apple-ios..."
+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
+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"))?;
use std::io::Write;
f.write_all(format!("CARGO_TARGET_DIR: '{}'\r\n", cargo_target_dir.display()).as_bytes())?;
- // The wintun crate's root directory
- let crate_dir = get_crate_dir("wintun")?;
+ // The wintun-bindings crate's root directory
+ let crate_dir = get_crate_dir("wintun-bindings")?;
// The path to the DLL file, relative to the crate root, depending on the target architecture
let dll_path = get_wintun_bin_relative_path()?;
@@ -49,17 +58,16 @@ fn get_cargo_target_dir() -> Result Result> {
- let dll_path = if cfg!(target_arch = "x86") {
- "wintun/bin/x86/wintun.dll"
- } else if cfg!(target_arch = "x86_64") {
- "wintun/bin/amd64/wintun.dll"
- } else if cfg!(target_arch = "arm") {
- "wintun/bin/arm/wintun.dll"
- } else if cfg!(target_arch = "aarch64") {
- "wintun/bin/arm64/wintun.dll"
- } else {
- return Err("Unsupported architecture".into());
+ let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH")?;
+
+ let dll_path = match target_arch.as_str() {
+ "x86" => "wintun/bin/x86/wintun.dll",
+ "x86_64" => "wintun/bin/amd64/wintun.dll",
+ "arm" => "wintun/bin/arm/wintun.dll",
+ "aarch64" => "wintun/bin/arm64/wintun.dll",
+ _ => return Err("Unsupported architecture".into()),
};
+
Ok(dll_path.into())
}
@@ -86,3 +94,10 @@ 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 c305b1e..50ea65f 100644
--- a/cbindgen.toml
+++ b/cbindgen.toml
@@ -1,11 +1,25 @@
+language = "C"
+cpp_compat = true
+
[export]
include = [
- "tun2proxy_run_with_fd",
- "tun2proxy_run_with_name",
+ "tun2proxy_run_with_cli",
+ "tun2proxy_with_fd_run",
+ "tun2proxy_with_name_run",
"tun2proxy_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]
+"ArgVerbosity" = "Tun2proxyVerbosity"
+"ArgDns" = "Tun2proxyDns"
+"TrafficStatus" = "Tun2proxyTrafficStatus"
+
+[enum]
+prefix_with_name = true
diff --git a/scripts/iperf3.sh b/scripts/iperf3.sh
index f491072..e6a0134 100755
--- a/scripts/iperf3.sh
+++ b/scripts/iperf3.sh
@@ -8,7 +8,7 @@ echo $SCRIPT_DIR
netns="test"
dante="danted"
-tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy"
+tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy-bin"
ip netns add "$netns"
@@ -39,7 +39,7 @@ sleep 1
ip tuntap add name tun0 mode tun
ip link set tun0 up
ip route add 10.0.0.4 dev tun0
-"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off &
+"$tun2proxy" --tun tun0 --proxy socks5://10.0.0.3:10800 -v off &
sleep 3
@@ -51,4 +51,4 @@ sleep 3
iperf3 -c 10.0.0.4 -P 10
# Clean up
-# sudo sh -c "pkill tun2proxy; pkill iperf3; pkill danted; ip link del tun0; ip netns del test"
+# sudo sh -c "pkill tun2proxy-bin; pkill iperf3; pkill danted; ip link del tun0; ip netns del test"
diff --git a/scripts/linux.sh b/scripts/linux.sh
index dc1d805..4cdefaf 100755
--- a/scripts/linux.sh
+++ b/scripts/linux.sh
@@ -30,7 +30,7 @@ function core_function() {
else
trap 'echo "" && echo "tun2proxy exited with code: $?" && restore' EXIT
local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- local APP_BIN_PATH="${SCRIPT_DIR}/../target/release/tun2proxy"
+ local APP_BIN_PATH="${SCRIPT_DIR}/../target/release/tun2proxy-bin"
"${APP_BIN_PATH}" --tun tun0 --proxy "${PROXY_TYPE}://${PROXY_IP}:${PROXY_PORT}" -v trace
fi
}
diff --git a/scripts/rperf.sh b/scripts/rperf.sh
index 1f06986..db34d25 100755
--- a/scripts/rperf.sh
+++ b/scripts/rperf.sh
@@ -29,7 +29,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
netns="test"
dante="danted"
-tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy"
+tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy-bin"
ip netns add "$netns"
@@ -60,7 +60,7 @@ sleep 1
ip tuntap add name tun0 mode tun
ip link set tun0 up
ip route add 10.0.0.4 dev tun0
-"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off &
+"$tun2proxy" --tun tun0 --proxy socks5://10.0.0.3:10800 -v off &
sleep 3
@@ -80,4 +80,4 @@ sleep 3
rperf -c 10.0.0.4 -v trace -P 1 -u -r
# Clean up
-# sudo sh -c "pkill tun2proxy; pkill rperf; pkill danted; ip link del tun0; ip netns del test"
+# sudo sh -c "pkill tun2proxy-bin; pkill rperf; pkill danted; ip link del tun0; ip netns del test"
diff --git a/src/android.rs b/src/android.rs
index f89bb00..030e921 100644
--- a/src/android.rs
+++ b/src/android.rs
@@ -1,25 +1,33 @@
#![cfg(target_os = "android")]
use crate::{
+ Args,
args::ArgProxy,
error::{Error, Result},
- Args,
};
use jni::{
- objects::{JClass, JString},
- sys::{jchar, jint},
JNIEnv,
+ objects::{JClass, JString},
+ sys::{jboolean, jchar, jint},
};
/// # Safety
///
-/// Running tun2proxy
-#[no_mangle]
+/// Running tun2proxy 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
+/// - tun_mtu: the tun mtu
+/// - dns_strategy: the dns strategy, see ArgDns enum
+/// - verbosity: the verbosity level, see ArgVerbosity enum
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
mut env: JNIEnv,
_clazz: JClass,
proxy_url: JString,
tun_fd: jint,
+ close_fd_on_drop: jboolean,
tun_mtu: jchar,
verbosity: jint,
dns_strategy: jint,
@@ -35,23 +43,26 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
.with_filter(filter),
);
let proxy_url = get_java_string(&mut env, &proxy_url).unwrap();
- let proxy = ArgProxy::from_url(proxy_url).unwrap();
+ let proxy = ArgProxy::try_from(proxy_url.as_str()).unwrap();
+ let close_fd_on_drop = close_fd_on_drop != 0;
let mut args = Args::default();
- args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns).verbosity(verbosity);
- crate::mobile_api::mobile_run(args, tun_mtu)
+ args.proxy(proxy)
+ .tun_fd(Some(tun_fd))
+ .close_fd_on_drop(close_fd_on_drop)
+ .dns(dns)
+ .verbosity(verbosity);
+ crate::general_api::general_run_for_api(args, tun_mtu, false)
}
/// # Safety
///
/// Shutdown tun2proxy
-#[no_mangle]
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint {
- crate::mobile_api::mobile_stop()
+ crate::general_api::tun2proxy_stop_internal()
}
-unsafe fn get_java_string<'a>(env: &'a mut JNIEnv, string: &'a JString) -> Result<&'a str, Error> {
- let str_ptr = env.get_string(string)?.as_ptr();
- let s: &str = std::ffi::CStr::from_ptr(str_ptr).to_str()?;
- Ok(s)
+fn get_java_string(env: &mut JNIEnv, string: &JString) -> Result {
+ Ok(env.get_string(string)?.into())
}
diff --git a/src/args.rs b/src/args.rs
index 165bd58..2b74748 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -1,32 +1,84 @@
use crate::{Error, Result};
use socks5_impl::protocol::UserKey;
+use tproxy_config::IpCidr;
+
+#[cfg(target_os = "linux")]
+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, about = "Tunnel interface to proxy.", long_about = None)]
+#[command(author, version = version_info!(), about = about_info(), long_about = None)]
pub struct Args {
/// Proxy URL in the form proto://[username[:password]@]host:port,
- /// where proto is one of socks4, socks5, http. For example:
- /// socks5://myname:password@127.0.0.1:1080
- #[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")]
+ /// where proto is one of socks4, socks5, http.
+ /// Username and password are encoded in percent encoding. For example:
+ /// socks5://myname:pass%40word@127.0.0.1:1080
+ #[arg(short, long, value_parser = |s: &str| ArgProxy::try_from(s), value_name = "URL")]
pub proxy: ArgProxy,
/// Name of the tun interface, such as tun0, utun4, etc.
/// If this option is not provided, the OS will generate a random one.
- #[arg(short, long, value_name = "name", conflicts_with = "tun_fd")]
+ #[arg(short, long, value_name = "name", value_parser = validate_tun)]
+ #[cfg_attr(unix, arg(conflicts_with = "tun_fd"))]
pub tun: Option,
/// File descriptor of the tun interface
+ #[cfg(unix)]
#[arg(long, value_name = "fd", conflicts_with = "tun")]
pub tun_fd: Option,
+ /// Set whether to close the received raw file descriptor on drop or not.
+ /// This setting is dependent on [tun_fd].
+ #[cfg(unix)]
+ #[arg(long, value_name = "true or false", conflicts_with = "tun", requires = "tun_fd")]
+ pub close_fd_on_drop: Option,
+
+ /// Create a tun interface in a newly created unprivileged namespace
+ /// while maintaining proxy connectivity via the global network namespace.
+ #[cfg(target_os = "linux")]
+ #[arg(long)]
+ pub unshare: bool,
+
+ /// Create a pidfile of `unshare` process when using `--unshare`.
+ #[cfg(target_os = "linux")]
+ #[arg(long)]
+ pub unshare_pidfile: Option,
+
+ /// File descriptor for UNIX datagram socket meant to transfer
+ /// network sockets from global namespace to the new one.
+ /// See `unshare(1)`, `namespaces(7)`, `sendmsg(2)`, `unix(7)`.
+ #[cfg(target_os = "linux")]
+ #[arg(long, value_name = "fd", hide(true))]
+ pub socket_transfer_fd: Option,
+
+ /// Specify a command to run with root-like capabilities in the new namespace
+ /// when using `--unshare`.
+ /// This could be useful to start additional daemons, e.g. `openvpn` instance.
+ #[cfg(target_os = "linux")]
+ #[arg(requires = "unshare")]
+ pub admin_command: Vec,
+
/// IPv6 enabled
#[arg(short = '6', long)]
pub ipv6_enabled: bool,
- #[arg(short, long)]
/// Routing and system setup, which decides whether to setup the routing and system configuration.
- /// This option is only available on Linux and requires root privileges.
+ /// This option requires root-like privileges on every platform.
+ /// It is very important on Linux, see `capabilities(7)`.
+ #[arg(short, long)]
pub setup: bool,
/// DNS handling strategy
@@ -37,35 +89,117 @@ pub struct Args {
#[arg(long, value_name = "IP", default_value = "8.8.8.8")]
pub dns_addr: IpAddr,
- /// IPs used in routing setup which should bypass the tunnel
- #[arg(short, long, value_name = "IP")]
- pub bypass: Vec,
+ /// IP address pool to be used by virtual DNS in CIDR notation.
+ #[arg(long, value_name = "CIDR", default_value = "198.18.0.0/15")]
+ pub virtual_dns_pool: IpCidr,
+
+ /// IPs used in routing setup which should bypass the tunnel,
+ /// in the form of IP or IP/CIDR. Multiple IPs can be specified,
+ /// e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8
+ #[arg(short, long, value_name = "IP/CIDR")]
+ pub bypass: Vec,
+
+ /// TCP timeout in seconds
+ #[arg(long, value_name = "seconds", default_value = "600")]
+ pub tcp_timeout: u64,
+
+ /// UDP timeout in seconds
+ #[arg(long, value_name = "seconds", default_value = "10")]
+ pub udp_timeout: u64,
/// Verbosity level
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
pub verbosity: ArgVerbosity,
+
+ /// Daemonize for unix family or run as Windows service
+ #[arg(long)]
+ pub daemonize: bool,
+
+ /// Exit immediately when fatal error occurs, useful for running as a service
+ #[arg(long)]
+ pub exit_on_fatal_error: bool,
+
+ /// Maximum number of sessions to be handled concurrently
+ #[arg(long, value_name = "number", default_value = "200")]
+ pub max_sessions: usize,
+
+ /// UDP gateway server address, forwards UDP packets via specified TCP server
+ #[cfg(feature = "udpgw")]
+ #[arg(long, value_name = "IP:PORT")]
+ pub udpgw_server: Option,
+
+ /// Max connections for the UDP gateway, default value is 5
+ #[cfg(feature = "udpgw")]
+ #[arg(long, value_name = "number", requires = "udpgw_server")]
+ pub udpgw_connections: Option,
+
+ /// Keepalive interval in seconds for the UDP gateway, default value is 30
+ #[cfg(feature = "udpgw")]
+ #[arg(long, value_name = "seconds", requires = "udpgw_server")]
+ pub udpgw_keepalive: Option,
+}
+
+fn validate_tun(p: &str) -> Result {
+ #[cfg(target_os = "macos")]
+ if p.len() <= 4 || &p[..4] != "utun" {
+ return Err(Error::from("Invalid tun interface name, please use utunX"));
+ }
+ Ok(p.to_string())
}
impl Default for Args {
fn default() -> Self {
+ #[cfg(target_os = "linux")]
+ let setup = false;
+ #[cfg(not(target_os = "linux"))]
+ let setup = true;
Args {
proxy: ArgProxy::default(),
tun: None,
+ #[cfg(unix)]
tun_fd: None,
+ #[cfg(unix)]
+ close_fd_on_drop: None,
+ #[cfg(target_os = "linux")]
+ unshare: false,
+ #[cfg(target_os = "linux")]
+ unshare_pidfile: None,
+ #[cfg(target_os = "linux")]
+ socket_transfer_fd: None,
+ #[cfg(target_os = "linux")]
+ admin_command: Vec::new(),
ipv6_enabled: false,
- setup: false,
+ setup,
dns: ArgDns::default(),
dns_addr: "8.8.8.8".parse().unwrap(),
bypass: vec![],
+ tcp_timeout: 600,
+ udp_timeout: 10,
verbosity: ArgVerbosity::Info,
+ virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(),
+ daemonize: false,
+ exit_on_fatal_error: false,
+ max_sessions: 200,
+ #[cfg(feature = "udpgw")]
+ udpgw_server: None,
+ #[cfg(feature = "udpgw")]
+ udpgw_connections: None,
+ #[cfg(feature = "udpgw")]
+ udpgw_keepalive: None,
}
}
}
impl Args {
+ #[allow(clippy::let_and_return)]
pub fn parse_args() -> Self {
- use clap::Parser;
- Self::parse()
+ let args = ::parse();
+ #[cfg(target_os = "linux")]
+ if !args.setup && args.tun.is_none() {
+ eprintln!("Missing required argument, '--tun' must present when '--setup' is not used.");
+ std::process::exit(-1);
+ }
+ args
}
pub fn proxy(&mut self, proxy: ArgProxy) -> &mut Self {
@@ -78,11 +212,30 @@ impl Args {
self
}
+ #[cfg(feature = "udpgw")]
+ pub fn udpgw_server(&mut self, udpgw: SocketAddr) -> &mut Self {
+ self.udpgw_server = Some(udpgw);
+ self
+ }
+
+ #[cfg(feature = "udpgw")]
+ pub fn udpgw_connections(&mut self, udpgw_connections: usize) -> &mut Self {
+ self.udpgw_connections = Some(udpgw_connections);
+ self
+ }
+
+ #[cfg(unix)]
pub fn tun_fd(&mut self, tun_fd: Option) -> &mut Self {
self.tun_fd = tun_fd;
self
}
+ #[cfg(unix)]
+ pub fn close_fd_on_drop(&mut self, close_fd_on_drop: bool) -> &mut Self {
+ self.close_fd_on_drop = Some(close_fd_on_drop);
+ self
+ }
+
pub fn verbosity(&mut self, verbosity: ArgVerbosity) -> &mut Self {
self.verbosity = verbosity;
self
@@ -98,7 +251,7 @@ impl Args {
self
}
- pub fn bypass(&mut self, bypass: IpAddr) -> &mut Self {
+ pub fn bypass(&mut self, bypass: IpCidr) -> &mut Self {
self.bypass.push(bypass);
self
}
@@ -237,42 +390,38 @@ impl std::fmt::Display for ArgProxy {
}
}
-impl ArgProxy {
- pub fn from_url(s: &str) -> Result {
+impl TryFrom<&str> for ArgProxy {
+ type Error = Error;
+ fn try_from(s: &str) -> Result {
+ if s == "none" {
+ return Ok(ArgProxy {
+ proxy_type: ProxyType::None,
+ addr: "0.0.0.0:0".parse().unwrap(),
+ credentials: None,
+ });
+ }
+
let e = format!("`{s}` is not a valid proxy URL");
let url = url::Url::parse(s).map_err(|_| Error::from(&e))?;
let e = format!("`{s}` does not contain a host");
let host = url.host_str().ok_or(Error::from(e))?;
- let mut url_host = String::from(host);
let e = format!("`{s}` does not contain a port");
- let port = url.port().ok_or(Error::from(&e))?;
- url_host.push(':');
- url_host.push_str(port.to_string().as_str());
+ let port = url.port_or_known_default().ok_or(Error::from(&e))?;
- 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 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 credentials = if url.username() == "" && url.password().is_none() {
None
} else {
- let username = String::from(url.username());
- let password = String::from(url.password().unwrap_or(""));
+ use percent_encoding::percent_decode;
+ let username = percent_decode(url.username().as_bytes()).decode_utf8()?;
+ let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8()?;
Some(UserKey::new(username, password))
};
- let scheme = url.scheme();
-
- let proxy_type = match url.scheme().to_ascii_lowercase().as_str() {
- "socks4" => Some(ProxyType::Socks4),
- "socks5" => Some(ProxyType::Socks5),
- "http" => Some(ProxyType::Http),
- _ => None,
- }
- .ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?;
+ let proxy_type = url.scheme().to_ascii_lowercase().as_str().try_into()?;
Ok(ArgProxy {
proxy_type,
@@ -289,6 +438,20 @@ pub enum ProxyType {
Socks4,
#[default]
Socks5,
+ None,
+}
+
+impl TryFrom<&str> for ProxyType {
+ type Error = Error;
+ fn try_from(value: &str) -> Result {
+ match value {
+ "http" => Ok(ProxyType::Http),
+ "socks4" => Ok(ProxyType::Socks4),
+ "socks5" => Ok(ProxyType::Socks5),
+ "none" => Ok(ProxyType::None),
+ scheme => Err(Error::from(&format!("`{scheme}` is an invalid proxy type"))),
+ }
+ }
}
impl std::fmt::Display for ProxyType {
@@ -297,6 +460,7 @@ impl std::fmt::Display for ProxyType {
ProxyType::Socks4 => write!(f, "socks4"),
ProxyType::Socks5 => write!(f, "socks5"),
ProxyType::Http => write!(f, "http"),
+ ProxyType::None => write!(f, "none"),
}
}
}
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 0293235..7bf00ef 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -1,33 +1,141 @@
-use tun2proxy::{Args, BoxError};
+use tun2proxy::{ArgVerbosity, Args, BoxError};
-#[tokio::main]
-async fn main() -> Result<(), BoxError> {
+fn main() -> Result<(), BoxError> {
dotenvy::dotenv().ok();
let args = Args::parse_args();
- // let default = format!("{}={:?}", module_path!(), args.verbosity);
- let default = format!("{:?}", args.verbosity);
+ #[cfg(unix)]
+ if args.daemonize {
+ let stdout = std::fs::File::create("/tmp/tun2proxy.out")?;
+ let stderr = std::fs::File::create("/tmp/tun2proxy.err")?;
+ let daemonize = daemonize::Daemonize::new()
+ .working_directory("/tmp")
+ .umask(0o777)
+ .stdout(stdout)
+ .stderr(stderr)
+ .privileged_action(|| "Executed before drop privileges");
+ let _ = daemonize.start()?;
+ }
+
+ #[cfg(target_os = "windows")]
+ if args.daemonize {
+ tun2proxy::win_svc::start_service()?;
+ return Ok(());
+ }
+
+ let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
+ rt.block_on(main_async(args))
+}
+
+async fn main_async(args: Args) -> Result<(), BoxError> {
+ let ipstack = match args.verbosity {
+ ArgVerbosity::Trace => ArgVerbosity::Debug,
+ _ => args.verbosity,
+ };
+ let default = format!("{:?},hickory_proto=warn,ipstack={:?}", args.verbosity, ipstack);
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init();
let shutdown_token = tokio_util::sync::CancellationToken::new();
- let join_handle = tokio::spawn({
+ let main_loop_handle = tokio::spawn({
+ let args = args.clone();
let shutdown_token = shutdown_token.clone();
async move {
- if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await {
- log::error!("main loop error: {}", err);
+ #[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);
+ }
+ return Ok(0);
}
+
+ unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) {
+ let status = unsafe { &*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}");
+ }
+ ret
}
});
- ctrlc2::set_async_handler(async move {
+ 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 || {
log::info!("Ctrl-C received, exiting...");
+ ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst);
shutdown_token.cancel();
- })
- .await;
+ true
+ })?;
- if let Err(err) = join_handle.await {
- log::error!("main_entry error {}", err);
+ let tasks = 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);
}
Ok(())
}
+
+#[cfg(target_os = "linux")]
+async fn namespace_proxy_main(
+ _args: Args,
+ _shutdown_token: tokio_util::sync::CancellationToken,
+) -> Result {
+ use nix::fcntl::{OFlag, open};
+ use nix::sys::stat::Mode;
+ use std::os::fd::AsRawFd;
+
+ let (socket, remote_fd) = tun2proxy::socket_transfer::create_transfer_socket_pair().await?;
+
+ let fd = open("/proc/self/exe", OFlag::O_PATH, Mode::empty())?;
+
+ 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("--socket-transfer-fd")
+ .arg(remote_fd.as_raw_fd().to_string())
+ .args(std::env::args().skip(1))
+ .kill_on_drop(true)
+ .spawn();
+
+ let mut child = match child {
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
+ log::error!("`unshare(1)` executable wasn't located in PATH.");
+ log::error!("Consider installing linux utils package: `apt install util-linux`");
+ log::error!("Or similar for your distribution.");
+ return Err(err.into());
+ }
+ child => child?,
+ };
+
+ let unshare_pid = child.id().unwrap_or(0);
+ log::info!("The tun proxy is running in unprivileged mode. See `namespaces(7)`.");
+ log::info!("");
+ log::info!("If you need to run a process that relies on root-like capabilities (e.g. `openvpn`)");
+ 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 {} /bin/sh`",
+ unshare_pid
+ );
+ log::info!("");
+ if let Some(pidfile) = _args.unshare_pidfile.as_ref() {
+ 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 });
+
+ Ok(child.wait().await?)
+}
diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs
new file mode 100644
index 0000000..2a8f91d
--- /dev/null
+++ b/src/bin/udpgw_server.rs
@@ -0,0 +1,273 @@
+use socks5_impl::protocol::AsyncStreamOperation;
+use std::net::SocketAddr;
+use tokio::{
+ io::AsyncWriteExt,
+ net::{
+ UdpSocket,
+ tcp::{ReadHalf, WriteHalf},
+ },
+ sync::mpsc::{Receiver, Sender},
+};
+use tun2proxy::{
+ ArgVerbosity, BoxError, Error, Result,
+ udpgw::{Packet, UdpFlag},
+};
+
+pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60);
+
+#[derive(Debug, Clone)]
+pub struct Client {
+ addr: SocketAddr,
+ last_activity: std::time::Instant,
+}
+
+impl Client {
+ pub fn new(addr: SocketAddr) -> Self {
+ let last_activity = std::time::Instant::now();
+ Self { addr, last_activity }
+ }
+}
+
+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")]
+ pub listen_addr: SocketAddr,
+
+ /// UDP mtu
+ #[arg(short = 'm', long, value_name = "udp mtu", default_value = "10240")]
+ pub udp_mtu: u16,
+
+ /// UDP timeout in seconds
+ #[arg(short = 't', long, value_name = "seconds", default_value = "3")]
+ pub udp_timeout: u64,
+
+ /// Daemonize for unix family or run as Windows service
+ #[cfg(unix)]
+ #[arg(short, long)]
+ pub daemonize: bool,
+
+ /// Verbosity level
+ #[arg(short, long, value_name = "level", value_enum, default_value = "info")]
+ pub verbosity: ArgVerbosity,
+}
+
+impl UdpGwArgs {
+ pub fn parse_args() -> 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);
+ }
+}
+
+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);
+ }
+}
+
+/// 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<()> {
+ let Some(dst_addr) = &packet.address else {
+ return Err(std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "udp request address is None").into());
+ };
+ 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")?,
+ };
+ 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)?;
+ // 1. send udp data to destination server
+ socket.send_to(&packet.data, &dst_addr).await?;
+ // 2. receive response from destination server
+ let mut buf = vec![0u8; udp_mtu as usize];
+ let (len, _addr) = tokio::time::timeout(tokio::time::Duration::from_secs(udp_timeout), socket.recv_from(&mut buf))
+ .await
+ .map_err(std::io::Error::from)??;
+ packet.data = buf[..len].to_vec();
+ // 3. send response back to client
+ use std::io::{Error, ErrorKind::BrokenPipe};
+ tx.send(packet).await.map_err(|e| Error::new(BrokenPipe, e))?;
+ 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 {} retrieve_from_async_stream \"{}\"", masked_addr, e);
+ break;
+ }
+ Err(e) => {
+ if client.last_activity.elapsed() >= CLIENT_DISCONNECT_TIMEOUT {
+ log::debug!("client {} last_activity elapsed \"{e}\"", masked_addr);
+ break;
+ }
+ continue;
+ }
+ };
+ client.last_activity = std::time::Instant::now();
+
+ let flags = packet.header.flags;
+ let conn_id = packet.header.conn_id;
+ if flags & UdpFlag::KEEPALIVE == UdpFlag::KEEPALIVE {
+ log::trace!("client {} send keepalive", masked_addr);
+ // 2. if keepalive packet, do nothing, send keepalive response to client
+ send_keepalive_response(tx.clone(), conn_id).await;
+ continue;
+ }
+ log::trace!("client {} received udp data {}", masked_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 {
+ send_error_response(tx, conn_id).await;
+ log::debug!("client {} process udp function \"{e}\"", masked_addr);
+ }
+ });
+ }
+ Ok(())
+}
+
+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 {} with {}", masked_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!("UDP Gateway Server running at {}", args.listen_addr);
+
+ let shutdown_token = tokio_util::sync::CancellationToken::new();
+ let main_loop_handle = tokio::spawn(run(args, shutdown_token.clone()));
+
+ 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 || {
+ log::info!("Ctrl-C received, exiting...");
+ ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst);
+ shutdown_token.cancel();
+ true
+ })?;
+
+ 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?;
+ }
+
+ Ok(())
+}
+
+pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::CancellationToken) -> crate::Result<()> {
+ let tcp_listener = tokio::net::TcpListener::bind(args.listen_addr).await?;
+ loop {
+ let (mut tcp_stream, addr) = tokio::select! {
+ v = tcp_listener.accept() => v?,
+ _ = shutdown_token.cancelled() => break,
+ };
+ let client = Client::new(addr);
+ let masked_addr = mask_socket_addr(addr);
+ log::info!("client {} connected", masked_addr);
+ let params = args.clone();
+ tokio::spawn(async move {
+ let (tx, rx) = tokio::sync::mpsc::channel::(100);
+ let (tcp_read_stream, tcp_write_stream) = tcp_stream.split();
+ let res = tokio::select! {
+ 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 {} disconnected with {:?}", masked_addr, res);
+ });
+ }
+ Ok::<(), Error>(())
+}
+
+fn main() -> Result<(), BoxError> {
+ dotenvy::dotenv().ok();
+ let args = UdpGwArgs::parse_args();
+
+ let default = format!("{:?}", args.verbosity);
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init();
+
+ #[cfg(unix)]
+ if args.daemonize {
+ let stdout = std::fs::File::create("/tmp/udpgw.out")?;
+ let stderr = std::fs::File::create("/tmp/udpgw.err")?;
+ let daemonize = daemonize::Daemonize::new()
+ .working_directory("/tmp")
+ .umask(0o777)
+ .stdout(stdout)
+ .stderr(stderr)
+ .privileged_action(|| "Executed before drop privileges");
+ let _ = daemonize
+ .start()
+ .map_err(|e| format!("Failed to daemonize process, error:{:?}", e))?;
+ }
+
+ let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
+ rt.block_on(main_async(args))
+}
diff --git a/src/desktop_api.rs b/src/desktop_api.rs
deleted file mode 100644
index 61dab33..0000000
--- a/src/desktop_api.rs
+++ /dev/null
@@ -1,153 +0,0 @@
-#![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 tun2::{AbstractDevice, DEFAULT_MTU as MTU};
-
-static TUN_QUIT: std::sync::Mutex