Compare commits

...

63 commits

Author SHA1 Message Date
Koi to Coco
a8ebe0b9be
fix: use vec! to allocate buffer #213 (#214)
Some checks are pending
Push or PR / build_n_test (macos-latest) (push) Waiting to run
Push or PR / build_n_test (ubuntu-latest) (push) Waiting to run
Push or PR / build_n_test (windows-latest) (push) Waiting to run
Push or PR / build_n_test_android (push) Waiting to run
Push or PR / build_n_test_ios (push) Waiting to run
Push or PR / Check semver (push) Waiting to run
Integration Tests / Proxy Tests (push) Waiting to run
2025-06-29 16:18:25 +08:00
ssrlive
31b0972801 make rustc happy 2025-06-29 16:11:14 +08:00
B. Blechschmidt
9a6c96cf8b chore: publish v0.7.11 2025-06-19 19:16:24 +02:00
B. Blechschmidt
b5fbaa2d19 doc: fix Docker URL in README 2025-06-19 16:08:01 +02:00
B. Blechschmidt
3baa41a1fb fix: support multi-arch musl builds in Dockerfile 2025-06-19 16:05:54 +02:00
ssrlive
0cf4427ef6 setup_logging function 2025-06-19 20:15:47 +08:00
B. Blechschmidt
bc00dcc5ae fix: docker publish workflow 2025-06-19 13:47:48 +02:00
B. Blechschmidt
d87562b8d3 style: add pre-commit-config 2025-06-19 13:43:14 +02:00
B. Blechschmidt
fa09daabac fix: variable ref in publish-docker action 2025-06-19 13:42:57 +02:00
B. Blechschmidt
b36473ced9 feat(Docker): multi-stage Dockerfile with OS-less container 2025-06-19 13:32:01 +02:00
B. Blechschmidt
584bdc17ed feat(Linux): phase out reliance on iproute2 2025-06-17 01:10:25 +02:00
ssrlive
1880396822 use ctrlc2 async feature 2025-06-14 13:05:54 +08:00
Paper-Dragon
8b4ecabd8f
build image based on alpine/musl (#212) 2025-06-11 14:12:02 +08:00
B. Blechschmidt
fbc47a3001 fix(ci): account for change in load_dotenv 2025-06-11 00:21:03 +02:00
ssrlive
88d31ce168 Bump version 0.7.10 2025-06-03 14:05:28 +08:00
dependabot[bot]
ddebf5ee50
Update tun requirement from 0.7 to 0.8 (#209) 2025-06-03 12:02:31 +08:00
ssrlive
8cdb4f535d
Significant change in --setup parameter (#207) 2025-06-02 10:31:44 +08:00
ssrlive
6a5692cea0 refine code 2025-05-21 15:45:48 +08:00
ssrlive
3dc8f222cb Bump version 0.7.9 2025-05-08 10:26:00 +08:00
ssrlive
7c32b62727 Exclude dependabot[bot] in Integration Tests 2025-05-02 16:49:34 +08:00
dependabot[bot]
cf4a565f93
Update socks5-impl requirement from 0.6 to 0.7 (#201) 2025-05-01 08:30:43 +08:00
ssrlive
54f7dbc81b update nix deps 2025-04-30 10:59:48 +08:00
ssrlive
b71f479bf3 close-stale-issues.yml 2025-04-23 13:58:13 +08:00
ssrlive
2ead13a3f4 version_info & about_info 2025-04-22 14:58:52 +08:00
ssrlive
88423039c6 make TASK_COUNT as local task_count variable 2025-04-20 19:56:36 +08:00
ssrlive
7121a80300 Bump version 0.7.8 2025-04-19 17:50:56 +08:00
ssrlive
9e75475a23 force exit process while fatal error 2025-04-18 16:09:35 +08:00
ssrlive
7657f1603f Bump version 0.7.7 2025-03-28 20:23:47 +08:00
ssrlive
a380817951 update hickory-proto (DNS parser) 2025-03-19 08:36:29 +08:00
ssrlive
a2399c8b28 log ipstack info adjusted 2025-03-12 11:18:47 +08:00
ssrlive
61bbafcf82 version_info method 2025-03-11 12:41:57 +08:00
ssrlive
ca7cd25c4e Bump version 0.7.6 2025-03-07 14:15:14 +08:00
ssrlive
68716bdc9f update deps 2025-03-07 14:07:55 +08:00
ssrlive
e556f7657b Bump version 0.7.5 2025-02-27 14:56:14 +08:00
ssrlive
fd7dca9988 unsafe_in_unsafe issues 2025-02-27 14:40:09 +08:00
ssrlive
9a018f2393 update ipstack 2025-02-19 21:45:23 +08:00
ssrlive
c5d907551b ubuntu-20.04 used in publish script 2025-02-12 20:54:53 +08:00
ssrlive
6b038c2a80 Bump version 0.7.4 2025-02-12 18:09:37 +08:00
ssrlive
5287bef3c0 PI issues for macOS 2025-01-10 18:48:32 +08:00
ssrlive
04db15f553 Bump version 0.7.3 2025-01-07 21:15:44 +08:00
Ahmed Elsayed
f8c902b61c
use shlex instead of split whitespaces. (#179) 2025-01-07 21:03:25 +08:00
ssrlive
8ba2c1a2b7 Bump version 0.7.2 2025-01-03 15:30:41 +08:00
ssrlive
e939f5f3dc remove mod mobile 2025-01-03 15:22:14 +08:00
ssrlive
ecd1ab80bf base64 removed 2025-01-03 12:32:28 +08:00
Mostafa Kazemi
51de01854b
Fix typo in comment (#178) 2025-01-03 11:00:19 +08:00
ssrlive
bac54ec56c Bump version 0.7.1 2025-01-03 02:26:51 +08:00
ssrlive
6034870264 rename desktop_run_async to general_run_async 2025-01-03 02:05:50 +08:00
ssrlive
e933e5d4c0 iOS & Android testing suits 2025-01-03 00:41:29 +08:00
ssrlive
7136e2a20c refactor desktop_run_async 2025-01-02 23:44:57 +08:00
ssrlive
2a8e31225c refine clap::Parser 2025-01-02 17:08:19 +08:00
ssrlive
ea5ee834db Bump version 0.6.7 2024-12-27 17:15:48 +08:00
ssrlive
4d4a0ce85c minor changes 2024-12-26 20:38:13 +08:00
ssrlive
258637a52e upgrade dependencies 2024-12-17 11:35:58 +08:00
ssrlive
a01de17b36 minor changes 2024-11-26 13:32:39 +08:00
Paper-Dragon
724557b30e
docker-compose.yaml support (#166) 2024-11-26 13:12:04 +08:00
ssrlive
7a7293effd Refine code 2024-11-26 12:58:26 +08:00
ssrlive
46bf4434ef Bump version 0.6.6 2024-11-26 12:28:03 +08:00
ssrlive
d37cb44b62 Fix #165 2024-11-26 12:17:16 +08:00
ssrlive
987635d3dc Contributors in README 2024-11-24 15:49:10 +08:00
ssrlive
ebd3128778 Bump version 0.6.5 2024-11-21 14:15:37 +08:00
ssrlive
ee4df8f97b cbindgen issues 2024-11-17 17:54:30 +08:00
ssrlive
7314906841 mask_socket_addr function 2024-11-11 15:11:06 +08:00
ssrlive
23d4e59367 minor changes 2024-11-11 11:51:28 +08:00
37 changed files with 872 additions and 637 deletions

1
.dockerignore Symbolic link
View file

@ -0,0 +1 @@
.gitignore

View file

@ -15,6 +15,6 @@ jobs:
- name: Auto approve pull request, then squash and merge
uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: minor
# target: minor
# here `PAT_REPO_ADMIN` is a user's passkey provided by github.
github-token: ${{ secrets.PAT_REPO_ADMIN }}

View file

@ -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'

View file

@ -1,7 +1,5 @@
#
name: Create and publish a Docker image
name: Publish Docker Images
# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
push:
tags: [ 'v*.*.*' ]
@ -9,12 +7,20 @@ 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
IMAGE_NAME: ${{ github.repository }}
# This also contains the owner, i.e. tun2proxy/tun2proxy.
IMAGE_PATH: ${{ github.repository }}
IMAGE_NAME: ${{ github.event.repository.name }}
DEFAULT_OS: scratch
# 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
@ -31,30 +37,36 @@ 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@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
uses: docker/login-action@v3
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
- name: Extract metadata (tags, labels) for Docker Image
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# 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) || '' }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
uses: docker/build-push-action@v6
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 }}

View file

@ -1,4 +1,5 @@
on:
workflow_dispatch:
push:
tags:
- "v*.*.*"
@ -15,6 +16,7 @@ jobs:
attestations: write
strategy:
fail-fast: false
matrix:
target:
- x86_64-unknown-linux-gnu
@ -33,7 +35,7 @@ jobs:
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
@ -72,14 +74,15 @@ jobs:
rustup target add ${{ matrix.target }}
fi
cargo install cbindgen
if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
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
if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then
@ -90,21 +93,21 @@ jobs:
cargo build --all-features --release --target ${{ matrix.target }}
fi
fi
cbindgen --config cbindgen.toml -l C --cpp-compat -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-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
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-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.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.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 [[ "${{ 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
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/
@ -112,20 +115,26 @@ 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

View file

@ -1,12 +1,15 @@
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
@ -47,6 +50,53 @@ 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:

View file

@ -12,18 +12,16 @@ 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')
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@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Populate .env
env:
DOTENV: ${{ secrets.DOTENV }}
run: echo "$DOTENV" > .env
run: |
echo "$DOTENV" > tests/.env
ln -s tests/.env
- name: Set up Python
uses: actions/setup-python@v2
@ -40,7 +38,7 @@ jobs:
- name: Build project
run: cargo build --release
- name: Run tests
run: |
source venv/bin/activate

11
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,11 @@
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

View file

@ -1,70 +1,18 @@
[package]
name = "tun2proxy"
version = "0.6.4"
edition = "2021"
version = "0.7.11"
edition = "2024"
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.80"
rust-version = "1.85"
[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 = "2"
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"
@ -74,5 +22,61 @@ name = "udpgw-server"
path = "src/bin/udpgw_server.rs"
required-features = ["udpgw"]
[profile.release]
strip = "symbols"
[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"

View file

@ -1,20 +1,61 @@
####################################################################################################
## Builder
# This is a multi-stage Dockerfile.
# Build with `docker buildx build -t <image-tag> --target <stage> .`
# For example, to build the Alpine-based image while naming it tun2proxy, run:
# `docker buildx build -t tun2proxy --target tun2proxy-alpine .`
####################################################################################################
FROM rust:latest AS builder
WORKDIR /worker
COPY ./ .
RUN cargo build --release
####################################################################################################
## Final image
## glibc builder
####################################################################################################
FROM ubuntu:latest
FROM rust:latest AS glibc-builder
RUN apt update && apt install -y iproute2 && apt clean all
WORKDIR /worker
COPY ./ .
RUN cargo build --release
COPY --from=builder /worker/target/release/tun2proxy-bin /usr/bin/tun2proxy-bin
####################################################################################################
## musl builder
####################################################################################################
FROM rust:latest AS musl-builder
ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"]
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"]

View file

@ -1,8 +1,10 @@
[![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)
[![tun2proxy](https://docs.rs/tun2proxy/badge.svg)](https://docs.rs/tun2proxy)
[![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)
@ -27,7 +29,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
```
@ -147,8 +149,8 @@ Options:
--unshare-pidfile <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 is only available on Linux and requires root-like privileges.
See `capabilities(7)`
configuration. This option requires root-like privileges on every platform.
It is very important on Linux, see `capabilities(7)`
-d, --dns <strategy> DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct]
--dns-addr <IP> DNS resolver address [default: 8.8.8.8]
--virtual-dns-pool <CIDR> IP address pool to be used by virtual DNS in CIDR notation [default: 198.18.0.0/15]
@ -170,11 +172,21 @@ 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.
## 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
docker build -t tun2proxy .
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 .
```
Next, start a container from the tun2proxy image:
@ -185,7 +197,7 @@ docker run -d \
--sysctl net.ipv6.conf.default.disable_ipv6=0 \
--cap-add NET_ADMIN \
--name tun2proxy \
tun2proxy-bin --proxy proto://[username[:password]@]host:port
tun2proxy --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):
@ -195,6 +207,36 @@ 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
@ -216,3 +258,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:
<a href="https://github.com/tun2proxy/tun2proxy/graphs/contributors">
<img src="https://contrib.rocks/image?repo=tun2proxy/tun2proxy" />
</a>

View file

@ -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 -l C --cpp-compat -o target/include/tun2proxy.h
cbindgen --config cbindgen.toml -o target/include/tun2proxy.h
cat > target/include/tun2proxy.modulemap <<EOF
framework module tun2proxy {
umbrella header "tun2proxy.h"

View file

@ -10,7 +10,7 @@ cargo build --release --target aarch64-apple-ios --features mimalloc
echo "Generating includes..."
mkdir -p target/include/
rm -rf target/include/*
cbindgen --config cbindgen.toml -l C --cpp-compat -o target/include/tun2proxy.h
cbindgen --config cbindgen.toml -o target/include/tun2proxy.h
cat > target/include/tun2proxy.modulemap <<EOF
framework module tun2proxy {
umbrella header "tun2proxy.h"

View file

@ -108,7 +108,7 @@ function build_android() {
cp $BASE/target/$target/${mode2}/lib${name}.a $android_libs/${target_dir}/lib${name}.a
done
cbindgen -c $BASE/cbindgen.toml -l C --cpp-compat -o $android_libs/$name.h
cbindgen -c $BASE/cbindgen.toml -o $android_libs/$name.h
}
function main() {

View file

@ -24,7 +24,7 @@ cargo build --release --target aarch64-apple-ios-sim
echo "Generating includes..."
mkdir -p target/include/
rm -rf target/include/*
cbindgen --config cbindgen.toml -l C --cpp-compat -o target/include/tun2proxy.h
cbindgen --config cbindgen.toml -o target/include/tun2proxy.h
cat > target/include/tun2proxy.modulemap <<EOF
framework module tun2proxy {
umbrella header "tun2proxy.h"

View file

@ -1,4 +1,13 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
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"))?;
@ -19,7 +28,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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': {}\r\n", e).as_bytes())?;
f.write_all(format!("Failed to copy 'wintun.dll': {e}\r\n").as_bytes())?;
} else {
f.write_all(format!("Copied 'wintun.dll' to '{}'\r\n", dst_path.display()).as_bytes())?;
@ -85,3 +94,10 @@ fn get_crate_dir(crate_name: &str) -> Result<std::path::PathBuf, Box<dyn std::er
}
Ok(crate_dir.ok_or("crate_dir")?)
}
fn get_git_hash() -> std::io::Result<String> {
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)
}

View file

@ -1,15 +1,19 @@
language = "C"
cpp_compat = true
[export]
include = [
"tun2proxy_run_with_cli",
"tun2proxy_with_fd_run",
"tun2proxy_with_name_run",
"tun2proxy_with_name_stop",
"tun2proxy_with_fd_stop",
"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]

View file

@ -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
#[no_mangle]
#[unsafe(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::mobile_api::mobile_run(args, tun_mtu, false)
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()
}
fn get_java_string(env: &mut JNIEnv, string: &JString) -> Result<String, Error> {

View file

@ -1,54 +0,0 @@
#![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::<crate::dump_logger::DumpLogger>::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()
}

View file

@ -8,8 +8,19 @@ 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.
@ -65,8 +76,9 @@ pub struct Args {
pub ipv6_enabled: bool,
/// Routing and system setup, which decides whether to setup the routing and system configuration.
/// 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" })]
/// 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
@ -181,8 +193,7 @@ impl Default for Args {
impl Args {
#[allow(clippy::let_and_return)]
pub fn parse_args() -> Self {
use clap::Parser;
let args = Self::parse();
let args = <Self as ::clap::Parser>::parse();
#[cfg(target_os = "linux")]
if !args.setup && args.tun.is_none() {
eprintln!("Missing required argument, '--tun' must present when '--setup' is not used.");
@ -368,7 +379,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() {
@ -395,17 +406,11 @@ 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().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

View file

@ -1,4 +1,4 @@
use tun2proxy::{Args, BoxError};
use tun2proxy::{ArgVerbosity, Args, BoxError};
fn main() -> Result<(), BoxError> {
dotenvy::dotenv().ok();
@ -27,48 +27,68 @@ fn main() -> Result<(), BoxError> {
rt.block_on(main_async(args))
}
async fn main_async(args: Args) -> Result<(), BoxError> {
let default = format!("{:?},hickory_proto=warn", args.verbosity);
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 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;
return Ok(0);
}
unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) {
let status = &*status;
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()) };
if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await {
log::error!("main loop error: {}", err);
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
}
});
let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let ctrlc_fired_clone = ctrlc_fired.clone();
let ctrlc_handel = ctrlc2::set_async_handler(async move {
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
})?;
main_loop_handle.await?;
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.map_err(|err| err.to_string())?;
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(())
@ -79,7 +99,7 @@ async fn namespace_proxy_main(
_args: Args,
_shutdown_token: tokio_util::sync::CancellationToken,
) -> Result<std::process::ExitStatus, tun2proxy::Error> {
use nix::fcntl::{open, OFlag};
use nix::fcntl::{OFlag, open};
use nix::sys::stat::Mode;
use std::os::fd::AsRawFd;
@ -89,7 +109,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))
.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))
@ -113,13 +133,10 @@ 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 {} /bin/sh`",
unshare_pid
);
log::info!("Use `nsenter --preserve-credentials --user --net --mount --target {unshare_pid} /bin/sh`");
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 });

View file

@ -3,14 +3,14 @@ use std::net::SocketAddr;
use tokio::{
io::AsyncWriteExt,
net::{
tcp::{ReadHalf, WriteHalf},
UdpSocket,
tcp::{ReadHalf, WriteHalf},
},
sync::mpsc::{Receiver, Sender},
};
use tun2proxy::{
udpgw::{Packet, UdpFlag},
ArgVerbosity, BoxError, Error, Result,
udpgw::{Packet, UdpFlag},
};
pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60);
@ -28,7 +28,12 @@ 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")]
@ -53,30 +58,28 @@ pub struct UdpGwArgs {
}
impl UdpGwArgs {
#[allow(clippy::let_and_return)]
pub fn parse_args() -> Self {
use clap::Parser;
Self::parse()
<Self as ::clap::Parser>::parse()
}
}
async fn send_error_response(tx: Sender<Packet>, 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<Packet>, 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(_client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sender<Packet>, mut packet: Packet) -> Result<()> {
async fn process_udp(udp_mtu: u16, udp_timeout: u64, tx: Sender<Packet>, 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());
};
@ -106,22 +109,53 @@ async fn process_udp(_client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Se
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<Packet>, 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 \"{}\"", client.addr, e);
log::debug!("client {masked_addr} retrieve_from_async_stream \"{e}\"");
break;
}
Err(e) => {
if client.last_activity.elapsed() >= CLIENT_DISCONNECT_TIMEOUT {
log::debug!("client {} last_activity elapsed \"{e}\"", client.addr);
log::debug!("client {masked_addr} last_activity elapsed \"{e}\"");
break;
}
continue;
@ -132,19 +166,19 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender<Packet>, mut client
let flags = packet.header.flags;
let conn_id = packet.header.conn_id;
if flags & UdpFlag::KEEPALIVE == UdpFlag::KEEPALIVE {
log::trace!("client {} send keepalive", client.addr);
log::trace!("client {masked_addr} send keepalive");
// 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 {}", client.addr, packet);
log::trace!("client {masked_addr} received udp data {packet}");
// 3. process client udpgw packet in a new task
let tx = tx.clone();
tokio::spawn(async move {
if let Err(e) = process_udp(client.addr, udp_mtu, udp_timeout, tx.clone(), packet).await {
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}\"", client.addr);
log::debug!("client {masked_addr} process udp function \"{e}\"");
}
});
}
@ -152,17 +186,18 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender<Packet>, mut client
}
async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Receiver<Packet>) -> 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 {}", addr, packet);
log::trace!("send response to client {masked_addr} with {packet}");
let data: Vec<u8> = packet.into();
let _r = writer.write(&data).await?;
}
}
async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> {
log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION"));
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();
@ -170,18 +205,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::set_async_handler(async move {
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
})?;
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.map_err(|err| err.to_string())?;
ctrlc_handel.await?;
}
Ok(())
@ -195,7 +230,8 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation
_ = shutdown_token.cancelled() => break,
};
let client = Client::new(addr);
log::info!("client {} connected", addr);
let masked_addr = mask_socket_addr(addr);
log::info!("client {masked_addr} connected");
let params = args.clone();
tokio::spawn(async move {
let (tx, rx) = tokio::sync::mpsc::channel::<Packet>(100);
@ -204,7 +240,7 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation
v = process_client_udp_req(&params, tx, client, tcp_read_stream) => v,
v = write_to_client(addr, tcp_write_stream, rx) => v,
};
log::info!("client {} disconnected with {:?}", addr, res);
log::info!("client {masked_addr} disconnected with {res:?}");
});
}
Ok::<(), Error>(())
@ -227,9 +263,7 @@ 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()?;

View file

@ -1,187 +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 tun::{AbstractDevice, DEFAULT_MTU as MTU};
static TUN_QUIT: std::sync::Mutex<Option<tokio_util::sync::CancellationToken>> = 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::<crate::dump_logger::DumpLogger>::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<tproxy_config::TproxyState> = 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
}

View file

@ -1,21 +1,16 @@
use hickory_proto::{
op::{Message, MessageType, ResponseCode},
rr::{record_type::RecordType, Name, RData, Record},
rr::{
Name, RData, Record,
rdata::{A, AAAA},
},
};
use std::{net::IpAddr, str::FromStr};
pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result<Message, String> {
let record = match 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
}
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))),
};
// We must indicate that this message is a response. Otherwise, implementations may not
@ -27,9 +22,7 @@ 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(), Some(RData::AAAA(_))));
message.answers_mut().retain(|answer| !matches!(answer.data(), RData::AAAA(_)));
}
pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result<IpAddr, String> {
@ -38,7 +31,7 @@ pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result<IpAddr, Stri
}
let mut cname = None;
for answer in message.answers() {
match answer.data().ok_or("DNS response not contains answer data")? {
match answer.data() {
RData::A(addr) => {
return Ok(IpAddr::V4((*addr).into()));
}

View file

@ -9,7 +9,7 @@ pub(crate) static DUMP_CALLBACK: Mutex<Option<DumpCallback>> = Mutex::new(None);
/// # Safety
///
/// set dump log info callback.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tun2proxy_set_log_callback(
callback: Option<unsafe extern "C" fn(ArgVerbosity, *const c_char, *mut c_void)>,
ctx: *mut c_void,
@ -23,7 +23,7 @@ pub struct DumpCallback(Option<unsafe extern "C" fn(ArgVerbosity, *const c_char,
impl DumpCallback {
unsafe fn call(self, dump_level: ArgVerbosity, info: *const c_char) {
if let Some(cb) = self.0 {
cb(dump_level, info, self.1);
unsafe { cb(dump_level, info, self.1) };
}
}
}

View file

@ -23,10 +23,10 @@ pub enum Error {
TryFromSlice(#[from] std::array::TryFromSliceError),
#[error("IpStackError {0:?}")]
IpStack(#[from] ipstack::IpStackError),
IpStack(#[from] Box<ipstack::IpStackError>),
#[error("DnsProtoError {0:?}")]
DnsProto(#[from] hickory_proto::error::ProtoError),
DnsProto(#[from] hickory_proto::ProtoError),
#[error("httparse::Error {0:?}")]
Httparse(#[from] httparse::Error),
@ -43,10 +43,12 @@ pub enum Error {
#[error("std::num::ParseIntError {0:?}")]
IntParseError(#[from] std::num::ParseIntError),
}
#[cfg(target_os = "linux")]
#[error("bincode::Error {0:?}")]
BincodeError(#[from] bincode::Error),
impl From<ipstack::IpStackError> for Error {
fn from(err: ipstack::IpStackError) -> Self {
Self::IpStack(Box::new(err))
}
}
impl From<&str> for Error {
@ -71,7 +73,7 @@ impl From<Error> for std::io::Error {
fn from(err: Error) -> Self {
match err {
Error::Io(err) => err,
_ => std::io::Error::new(std::io::ErrorKind::Other, err),
_ => std::io::Error::other(err),
}
}
}

269
src/general_api.rs Normal file
View file

@ -0,0 +1,269 @@
use crate::{
ArgVerbosity, Args,
args::{ArgDns, ArgProxy},
};
use std::os::raw::{c_char, c_int, c_ushort};
static TUN_QUIT: std::sync::Mutex<Option<tokio_util::sync::CancellationToken>> = 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 = <Args as ::clap::Parser>::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::<crate::dump_logger::DumpLogger>::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<usize> {
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<tproxy_config::TproxyState> = 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
}

View file

@ -4,11 +4,10 @@ use crate::{
proxy_handler::{ProxyHandler, ProxyHandlerManager},
session_info::{IpProtocol, SessionInfo},
};
use base64::Engine;
use httparse::Response;
use socks5_impl::protocol::UserKey;
use std::{
collections::{hash_map::RandomState, HashMap, VecDeque},
collections::{HashMap, VecDeque, hash_map::RandomState},
iter::FromIterator,
net::SocketAddr,
str,
@ -141,10 +140,9 @@ impl HttpConnection {
.extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes());
}
AuthenticationScheme::Basic => {
let cred = format!("{}:{}", credentials.username, credentials.password);
let auth_b64 = base64::engine::general_purpose::STANDARD.encode(cred);
let auth_b64 = base64easy::encode(credentials.to_string(), base64easy::EngineKind::Standard);
self.server_outbuf
.extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes());
.extend(format!("{PROXY_AUTHORIZATION}: Basic {auth_b64}\r\n").as_bytes());
}
AuthenticationScheme::None => {}
}
@ -252,7 +250,7 @@ impl HttpConnection {
}
// The HTTP/1.1 expected to be keep alive waiting for the next frame so, we must
// compute the lenght of the response in order to detect the next frame (response)
// compute the length 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

View file

@ -7,7 +7,7 @@ use crate::{
session_info::{IpProtocol, SessionInfo},
virtual_dns::VirtualDns,
};
use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream};
use ipstack::{IpStackStream, IpStackTcpStream, IpStackUdpStream};
use proxy_handler::{ProxyHandler, ProxyHandlerManager};
use socks::SocksProxyManager;
pub use socks5_impl::protocol::UserKey;
@ -22,43 +22,34 @@ use std::{
use tokio::{
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
net::{TcpSocket, TcpStream, UdpSocket},
sync::{mpsc::Receiver, Mutex},
sync::{Mutex, mpsc::Receiver},
};
pub use tokio_util::sync::CancellationToken;
use tproxy_config::is_private_ip;
use udp_stream::UdpStream;
#[cfg(feature = "udpgw")]
use udpgw::{UdpGwClientStream, UdpGwResponse, UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS};
use udpgw::{UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS, UdpGwClientStream, UdpGwResponse};
pub use {
args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType},
error::{BoxError, Error, Result},
traffic_status::{tun2proxy_set_traffic_status_callback, TrafficStatus},
traffic_status::{TrafficStatus, tun2proxy_set_traffic_status_callback},
};
#[cfg(feature = "mimalloc")]
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[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};
pub use general_api::general_run_async;
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;
@ -73,12 +64,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(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
target_os = "linux",
derive(bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize)
)]
pub enum SocketProtocol {
Tcp,
Udp,
@ -86,7 +77,10 @@ pub enum SocketProtocol {
#[allow(unused)]
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
target_os = "linux",
derive(bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize)
)]
pub enum SocketDomain {
IpV4,
IpV6,
@ -157,11 +151,13 @@ async fn create_udp_stream(socket_queue: &Option<Arc<SocketQueue>>, peer: Socket
/// * `mtu` - The MTU of the network device
/// * `args` - The arguments to use
/// * `shutdown_token` - The token to exit the server
pub async fn run<D>(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result<()>
/// # Returns
/// * The number of sessions while exiting
pub async fn run<D>(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result<usize>
where
D: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), version_info!());
log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr);
let server_addr = args.proxy.addr;
@ -225,11 +221,11 @@ where
let socket_queue = None;
use socks5_impl::protocol::Version::{V4, V5};
let mgr = match args.proxy.proxy_type {
ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc<dyn ProxyHandlerManager>,
ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc<dyn ProxyHandlerManager>,
ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc<dyn ProxyHandlerManager>,
ProxyType::None => Arc::new(NoProxyManager::new()) as Arc<dyn ProxyHandlerManager>,
let mgr: Arc<dyn ProxyHandlerManager> = 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 mut ipstack_config = ipstack::IpStackConfig::default();
@ -241,7 +237,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,
@ -257,7 +253,11 @@ 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 as u64;
let max_sessions = args.max_sessions;
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) + 1);
log::trace!("Session count {}", task_count.fetch_add(1, Relaxed).saturating_add(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!("{} error \"{}\"", info, err);
log::error!("{info} error \"{err}\"");
}
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1);
log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(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) + 1);
log::trace!("Session count {}", task_count.fetch_add(1, Relaxed).saturating_add(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);
info.dst.set_ip(dns_addr); // !!! Here we change the destination address to remote DNS server!!!
}
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!("{} error \"{}\"", info, err);
log::error!("{info} error \"{err}\"");
}
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1);
log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(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!("{} error \"{}\"", info, err);
log::error!("{info} error \"{err}\"");
}
}
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1);
log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(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 {} with \"{}\"", info, e);
log::info!("Ending {info} with \"{e}\"");
}
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1);
log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(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 {} with \"{}\"", info, err);
log::info!("Ending {info} with \"{err}\"");
}
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1);
log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(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 0x{:02X}, length {}", u.ip_protocol(), len);
log::info!("#0 unhandled transport - Ip Protocol {:?}, length {}", u.ip_protocol(), len);
continue;
}
IpStackStream::UnknownNetwork(pkt) => {
@ -393,7 +393,7 @@ where
}
}
}
Ok(())
Ok(task_count.load(Relaxed))
}
async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc<Mutex<VirtualDns>>) -> crate::Result<()> {
@ -402,7 +402,7 @@ async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc<Mutex<Vi
let len = match udp.read(&mut buf).await {
Err(e) => {
// 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<Mutex<Vi
}
let (msg, qname, ip) = dns.lock().await.generate_query(&buf[..len])?;
udp.write_all(&msg).await?;
log::debug!("Virtual DNS query: {} -> {}", qname, 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!("{} s_tx shutdown error {}", session_info, err);
log::trace!("{session_info} s_tx shutdown error {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!("{} t_tx shutdown error {}", session_info, err);
log::trace!("{session_info} t_tx shutdown error {err}");
}
r
},
);
log::info!("Ending {} with {:?}", session_info, res);
log::info!("Ending {session_info} with {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);
}
@ -625,7 +625,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 +702,7 @@ async fn handle_udp_associate_session(
}
}
log::info!("Ending {}", session_info);
log::info!("Ending {session_info}");
Ok(())
}
@ -721,7 +721,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 +774,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 +794,7 @@ async fn handle_dns_over_tcp_session(
}
}
log::info!("Ending {}", session_info);
log::info!("Ending {session_info}");
Ok(())
}

View file

@ -1,83 +0,0 @@
#![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<Option<tokio_util::sync::CancellationToken>> = 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
}

View file

@ -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{:02X})", v),
IpProtocol::Other(v) => write!(f, "Other(0x{v:02X})"),
}
}
}

View file

@ -1,10 +1,10 @@
#![cfg(target_os = "linux")]
use crate::{error, SocketDomain, SocketProtocol};
use crate::{SocketDomain, SocketProtocol, error};
use nix::{
errno::Errno,
fcntl::{self, FdFlag},
sys::socket::{cmsg_space, getsockopt, recvmsg, sendmsg, sockopt, ControlMessage, ControlMessageOwned, MsgFlags, SockType},
sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, SockType, cmsg_space, getsockopt, recvmsg, sendmsg, sockopt},
};
use serde::{Deserialize, Serialize};
use std::{
@ -16,31 +16,31 @@ use tokio::net::{TcpSocket, UdpSocket, UnixDatagram};
const REQUEST_BUFFER_SIZE: usize = 64;
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
#[derive(bincode::Encode, bincode::Decode, Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
struct Request {
protocol: SocketProtocol,
domain: SocketDomain,
number: u32,
}
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
#[derive(bincode::Encode, bincode::Decode, PartialEq, Debug, Hash, Copy, Clone, Eq, Serialize, Deserialize)]
enum Response {
Ok,
}
/// Reconstruct socket from raw `fd`
pub fn reconstruct_socket(fd: RawFd) -> Result<OwnedFd> {
// 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(fd, fcntl::F_SETFD(fd_flags))?;
fcntl::fcntl(socket.as_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_raw_fd(), fcntl::F_GETFD)?;
let fd_flags = fcntl::fcntl(remote_fd.as_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_raw_fd(), fcntl::F_SETFD(fd_flags))?;
fcntl::fcntl(remote_fd.as_fd(), fcntl::F_SETFD(fd_flags))?;
Ok((local, remote_fd))
}
@ -135,14 +135,21 @@ where
// Borrow socket as mut to prevent multiple simultaneous requests
let socket = socket.deref_mut();
// Send request
let request = bincode::serialize(&Request {
protocol: T::domain(),
domain,
number,
})?;
let mut request = [0u8; 1000];
socket.send(&request[..]).await?;
// 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))?;
socket.send(&request[..size]).await?;
// Receive response
loop {
@ -150,8 +157,7 @@ where
let mut buf = [0_u8; REQUEST_BUFFER_SIZE];
let mut iov = [IoSliceMut::new(&mut buf[..])];
let mut cmsg = Vec::with_capacity(cmsg_space::<RawFd>() * number as usize);
let mut cmsg = vec![0; cmsg_space::<RawFd>() * number as usize];
let msg = recvmsg::<()>(socket.as_fd().as_raw_fd(), &mut iov, Some(&mut cmsg), MsgFlags::empty());
let msg = match msg {
@ -161,7 +167,9 @@ where
// Parse response
let response = &msg.iovs().next().unwrap()[..msg.bytes];
let response: Response = bincode::deserialize(response)?;
let response: Response = bincode::decode_from_slice(response, bincode::config::standard())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?
.0;
if !matches!(response, Response::Ok) {
return Err("Request for new sockets failed".into());
}
@ -194,10 +202,14 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()>
let len = socket.recv(&mut buf[..]).await?;
let request: Request = bincode::deserialize(&buf[..len])?;
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 response = Response::Ok;
let buf = bincode::serialize(&response)?;
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 mut owned_fd_buf: Vec<OwnedFd> = Vec::with_capacity(request.number as usize);
for _ in 0..request.number {
@ -223,7 +235,7 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()>
let raw_fd_buf: Vec<RawFd> = owned_fd_buf.iter().map(|fd| fd.as_raw_fd()).collect();
let cmsg = ControlMessage::ScmRights(&raw_fd_buf[..]);
let iov = [IoSlice::new(&buf[..])];
let iov = [IoSlice::new(&buf[..size])];
sendmsg::<()>(socket.as_raw_fd(), &iov, &[cmsg], MsgFlags::empty(), None)?;
}

View file

@ -4,7 +4,7 @@ use crate::{
proxy_handler::{ProxyHandler, ProxyHandlerManager},
session_info::SessionInfo,
};
use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version};
use socks5_impl::protocol::{self, Address, AuthMethod, StreamOperation, UserKey, Version, handshake, password_method};
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);

View file

@ -5,7 +5,7 @@ use std::sync::{LazyLock, Mutex};
/// # Safety
///
/// set traffic status callback.
#[no_mangle]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tun2proxy_set_traffic_status_callback(
send_interval_secs: u32,
callback: Option<unsafe extern "C" fn(*const TrafficStatus, *mut c_void)>,
@ -34,7 +34,7 @@ struct TrafficStatusCallback(Option<unsafe extern "C" fn(*const TrafficStatus, *
impl TrafficStatusCallback {
unsafe fn call(self, info: &TrafficStatus) {
if let Some(cb) = self.0 {
cb(info, self.1);
unsafe { cb(info, self.1) };
}
}
}
@ -51,7 +51,7 @@ static TIME_STAMP: LazyLock<Mutex<std::time::Instant>> = 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 {

View file

@ -4,11 +4,11 @@ use std::{collections::VecDeque, hash::Hash, net::SocketAddr, sync::atomic::Orde
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpStream,
tcp::{OwnedReadHalf, OwnedWriteHalf},
},
sync::Mutex,
time::{sleep, Duration},
time::{Duration, sleep},
};
pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::<u16>();
@ -32,9 +32,9 @@ impl std::fmt::Display for UdpFlag {
0x01 => "KEEPALIVE",
0x20 => "ERR",
0x02 => "DATA",
n => return write!(f, "Unknown UdpFlag(0x{:02X})", n),
n => return write!(f, "Unknown UdpFlag(0x{n:02X})"),
};
write!(f, "{}", flag)
write!(f, "{flag}")
}
}
@ -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})"),
}
}
}
@ -487,21 +487,21 @@ impl UdpGwClient {
let keepalive_packet: Vec<u8> = Packet::build_keepalive_packet(sn).into();
tx += keepalive_packet.len();
if let Err(e) = stream_writer.write_all(&keepalive_packet).await {
log::warn!("stream {} {:?} send keepalive failed: {}", sn, local_addr, e);
log::warn!("stream {sn} {local_addr:?} send keepalive failed: {e}");
continue;
}
match UdpGwClient::recv_udpgw_packet(self.udp_mtu, self.udp_timeout, &mut stream_reader).await {
Ok((len, UdpGwResponse::KeepAlive)) => {
stream.update_activity();
self.store_server_connection_full(stream, stream_reader, stream_writer).await;
log::trace!("stream {sn} {:?} send keepalive and recieve it successfully", local_addr);
log::trace!("stream {sn} {local_addr:?} send keepalive and recieve it successfully");
rx += len;
}
Ok((len, v)) => {
log::debug!("stream {sn} {:?} keepalive unexpected response: {v}", local_addr);
log::debug!("stream {sn} {local_addr:?} keepalive unexpected response: {v}");
rx += len;
}
Err(e) => log::debug!("stream {sn} {:?} keepalive no response, error \"{e}\"", local_addr),
Err(e) => log::debug!("stream {sn} {local_addr:?} keepalive no response, error \"{e}\""),
}
}
crate::traffic_status::traffic_status_update(tx, rx)?;

View file

@ -1,5 +1,5 @@
use crate::error::Result;
use hashlink::{linked_hash_map::RawEntryMut, LruCache};
use hashlink::{LruCache, linked_hash_map::RawEntryMut};
use std::{
collections::HashMap,
convert::TryInto,

View file

@ -16,7 +16,7 @@ fn my_service_main(arguments: Vec<std::ffi::OsString>) {
// `service_dispatcher::start` from `main`.
if let Err(_e) = run_service(arguments) {
log::error!("Error: {:?}", _e);
log::error!("Error: {_e:?}");
}
}
@ -73,13 +73,21 @@ fn run_service(_arguments: Vec<std::ffi::OsString>) -> 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 = &*status;
let status = unsafe { &*status };
log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx);
}
unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) };
if let Err(err) = crate::desktop_run_async(args, shutdown_token).await {
log::error!("main loop error: {}", err);
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}"),
}
Ok::<(), crate::Error>(())
})?;