mirror of
https://github.com/tun2proxy/tun2proxy.git
synced 2025-04-21 22:39:08 +00:00
Compare commits
35 commits
Author | SHA1 | Date | |
---|---|---|---|
|
88423039c6 | ||
|
7121a80300 | ||
|
9e75475a23 | ||
|
7657f1603f | ||
|
a380817951 | ||
|
a2399c8b28 | ||
|
61bbafcf82 | ||
|
ca7cd25c4e | ||
|
68716bdc9f | ||
|
e556f7657b | ||
|
fd7dca9988 | ||
|
9a018f2393 | ||
|
c5d907551b | ||
|
6b038c2a80 | ||
|
5287bef3c0 | ||
|
04db15f553 | ||
|
f8c902b61c | ||
|
8ba2c1a2b7 | ||
|
e939f5f3dc | ||
|
ecd1ab80bf | ||
|
51de01854b | ||
|
bac54ec56c | ||
|
6034870264 | ||
|
e933e5d4c0 | ||
|
7136e2a20c | ||
|
2a8e31225c | ||
|
ea5ee834db | ||
|
4d4a0ce85c | ||
|
258637a52e | ||
|
a01de17b36 | ||
|
724557b30e | ||
|
7a7293effd | ||
|
46bf4434ef | ||
|
d37cb44b62 | ||
|
987635d3dc |
26 changed files with 554 additions and 478 deletions
2
.github/workflows/auto-merge.yaml
vendored
2
.github/workflows/auto-merge.yaml
vendored
|
@ -15,6 +15,6 @@ jobs:
|
||||||
- name: Auto approve pull request, then squash and merge
|
- name: Auto approve pull request, then squash and merge
|
||||||
uses: ahmadnassri/action-dependabot-auto-merge@v2
|
uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||||
with:
|
with:
|
||||||
target: minor
|
# target: minor
|
||||||
# here `PAT_REPO_ADMIN` is a user's passkey provided by github.
|
# here `PAT_REPO_ADMIN` is a user's passkey provided by github.
|
||||||
github-token: ${{ secrets.PAT_REPO_ADMIN }}
|
github-token: ${{ secrets.PAT_REPO_ADMIN }}
|
||||||
|
|
16
.github/workflows/publish-exe.yml
vendored
16
.github/workflows/publish-exe.yml
vendored
|
@ -15,6 +15,7 @@ jobs:
|
||||||
attestations: write
|
attestations: write
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
target:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
|
@ -33,7 +34,7 @@ jobs:
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
host_os: ubuntu-latest
|
host_os: ubuntu-22.04
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
host_os: ubuntu-latest
|
host_os: ubuntu-latest
|
||||||
- target: i686-unknown-linux-musl
|
- target: i686-unknown-linux-musl
|
||||||
|
@ -72,14 +73,15 @@ jobs:
|
||||||
rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }}
|
||||||
fi
|
fi
|
||||||
cargo install cbindgen
|
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
|
sudo .github/workflows/install-cross.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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 }}
|
cross build --all-features --release --target ${{ matrix.target }}
|
||||||
else
|
else
|
||||||
if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then
|
if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then
|
||||||
|
@ -103,7 +105,7 @@ jobs:
|
||||||
./build-apple.sh
|
./build-apple.sh
|
||||||
zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/
|
zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/
|
||||||
fi
|
fi
|
||||||
elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then
|
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
|
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
|
if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then
|
||||||
./build-android.sh
|
./build-android.sh
|
||||||
|
@ -112,20 +114,26 @@ jobs:
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: bin-${{ matrix.target }}
|
name: bin-${{ matrix.target }}
|
||||||
path: mypubdir4/*
|
path: mypubdir4/*
|
||||||
|
|
||||||
- name: Generate artifact attestation
|
- name: Generate artifact attestation
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
uses: actions/attest-build-provenance@v1
|
uses: actions/attest-build-provenance@v1
|
||||||
with:
|
with:
|
||||||
subject-path: mypubdir4/*
|
subject-path: mypubdir4/*
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
files: mypubdir4/*
|
files: mypubdir4/*
|
||||||
|
|
||||||
|
- name: Abort on error
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: echo "Some of jobs failed" && false
|
||||||
|
|
47
.github/workflows/rust.yml
vendored
47
.github/workflows/rust.yml
vendored
|
@ -47,6 +47,53 @@ jobs:
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
run: echo "Some of jobs failed" && false
|
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:
|
semver:
|
||||||
name: Check semver
|
name: Check semver
|
||||||
strategy:
|
strategy:
|
||||||
|
|
54
Cargo.toml
54
Cargo.toml
|
@ -1,39 +1,51 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tun2proxy"
|
name = "tun2proxy"
|
||||||
version = "0.6.5"
|
version = "0.7.8"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/tun2proxy/tun2proxy"
|
repository = "https://github.com/tun2proxy/tun2proxy"
|
||||||
homepage = "https://github.com/tun2proxy/tun2proxy"
|
homepage = "https://github.com/tun2proxy/tun2proxy"
|
||||||
authors = ["B. Blechschmidt", "ssrlive"]
|
authors = ["B. Blechschmidt", "ssrlive"]
|
||||||
description = "Tunnel interface to proxy"
|
description = "Tunnel interface to proxy"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
rust-version = "1.80"
|
rust-version = "1.85"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib", "cdylib", "lib"]
|
crate-type = ["staticlib", "cdylib", "lib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tun2proxy-bin"
|
||||||
|
path = "src/bin/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "udpgw-server"
|
||||||
|
path = "src/bin/udpgw_server.rs"
|
||||||
|
required-features = ["udpgw"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["udpgw"]
|
default = ["udpgw"]
|
||||||
udpgw = []
|
udpgw = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
base64 = { version = "0.22" }
|
base64easy = "0.1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "4", features = ["derive", "wrap_help", "color"] }
|
clap = { version = "4", features = ["derive", "wrap_help", "color"] }
|
||||||
ctrlc2 = { version = "3", features = ["tokio", "termination"] }
|
ctrlc2 = { version = "3", features = ["tokio", "termination"] }
|
||||||
digest_auth = "0.3"
|
digest_auth = "0.3"
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
hashlink = "0.9"
|
hashlink = "0.10"
|
||||||
hickory-proto = "0.24"
|
hickory-proto = "0.25"
|
||||||
httparse = "1"
|
httparse = "1"
|
||||||
ipstack = { version = "0.1" }
|
ipstack = { version = "0.3", git = "https://github.com/ssrlive/ipstack.git", rev = "53c648e" }
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
mimalloc = { version = "0.1", default-features = false, optional = true }
|
mimalloc = { version = "0.1", default-features = false, optional = true }
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
socks5-impl = { version = "0.5" }
|
shlex = "1.3.0"
|
||||||
|
socks5-impl = { version = "0.6", default-features = false, features = [
|
||||||
|
"tokio",
|
||||||
|
] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
|
@ -43,12 +55,16 @@ udp-stream = { version = "0.0.12", default-features = false }
|
||||||
unicase = "2"
|
unicase = "2"
|
||||||
url = "2"
|
url = "2"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
chrono = "0.4"
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
[target.'cfg(target_os="linux")'.dependencies]
|
[target.'cfg(target_os="linux")'.dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
bincode = "1"
|
bincode = "2"
|
||||||
|
|
||||||
[target.'cfg(target_os="android")'.dependencies]
|
[target.'cfg(target_os="android")'.dependencies]
|
||||||
android_logger = "0.14"
|
android_logger = "0.15"
|
||||||
jni = { version = "0.21", default-features = false }
|
jni = { version = "0.21", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
@ -60,19 +76,7 @@ nix = { version = "0.29", default-features = false, features = [
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows-service = "0.7"
|
windows-service = "0.8"
|
||||||
|
|
||||||
[build-dependencies]
|
# [profile.release]
|
||||||
serde_json = "1"
|
# strip = "symbols"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tun2proxy-bin"
|
|
||||||
path = "src/bin/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "udpgw-server"
|
|
||||||
path = "src/bin/udpgw_server.rs"
|
|
||||||
required-features = ["udpgw"]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
strip = "symbols"
|
|
||||||
|
|
44
README.md
44
README.md
|
@ -1,8 +1,10 @@
|
||||||
|
[](https://github.com/tun2proxy/tun2proxy)
|
||||||
|
|
||||||
# tun2proxy
|
# tun2proxy
|
||||||
A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows.
|
A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows.
|
||||||
|
|
||||||
[](https://crates.io/crates/tun2proxy)
|
[](https://crates.io/crates/tun2proxy)
|
||||||

|
[](https://docs.rs/tun2proxy)
|
||||||
[](https://docs.rs/tun2proxy)
|
[](https://docs.rs/tun2proxy)
|
||||||
[](https://crates.io/crates/tun2proxy)
|
[](https://crates.io/crates/tun2proxy)
|
||||||
[](https://github.com/tun2proxy/tun2proxy/blob/master/LICENSE)
|
[](https://github.com/tun2proxy/tun2proxy/blob/master/LICENSE)
|
||||||
|
@ -170,7 +172,8 @@ 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
|
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.
|
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:
|
Tun2proxy can serve as a proxy for other Docker containers. To make use of that feature, first build the image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -195,6 +198,36 @@ docker run -it \
|
||||||
--network "container:tun2proxy" \
|
--network "container:tun2proxy" \
|
||||||
ubuntu:latest
|
ubuntu:latest
|
||||||
```
|
```
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
Write a `docker-compose.yaml` file with the following content:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
tun2proxy:
|
||||||
|
volumes:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
sysctls:
|
||||||
|
- net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
container_name: tun2proxy
|
||||||
|
image: ghcr.io/tun2proxy/tun2proxy:latest
|
||||||
|
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
|
## Configuration Tips
|
||||||
### DNS
|
### DNS
|
||||||
|
@ -216,3 +249,10 @@ asked to open connections to IPv6 destinations. In such a case, you can disable
|
||||||
either through `sysctl -w net.ipv6.conf.all.disable_ipv6=1` and `sysctl -w net.ipv6.conf.default.disable_ipv6=1`
|
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
|
or through `ip -6 route del default`, which causes the `libc` resolver (and other software) to not issue DNS AAAA
|
||||||
requests for IPv6 addresses.
|
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>
|
||||||
|
|
16
build.rs
16
build.rs
|
@ -1,4 +1,13 @@
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
if let Ok(cargo_target_dir) = get_cargo_target_dir() {
|
if let Ok(cargo_target_dir) = get_cargo_target_dir() {
|
||||||
let mut f = std::fs::File::create(cargo_target_dir.join("build.log"))?;
|
let mut f = std::fs::File::create(cargo_target_dir.join("build.log"))?;
|
||||||
|
@ -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")?)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ cpp_compat = true
|
||||||
|
|
||||||
[export]
|
[export]
|
||||||
include = [
|
include = [
|
||||||
|
"tun2proxy_run_with_cli",
|
||||||
"tun2proxy_with_fd_run",
|
"tun2proxy_with_fd_run",
|
||||||
"tun2proxy_with_name_run",
|
"tun2proxy_with_name_run",
|
||||||
"tun2proxy_with_name_stop",
|
"tun2proxy_stop",
|
||||||
"tun2proxy_with_fd_stop",
|
|
||||||
"tun2proxy_set_log_callback",
|
"tun2proxy_set_log_callback",
|
||||||
"tun2proxy_set_traffic_status_callback",
|
"tun2proxy_set_traffic_status_callback",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#![cfg(target_os = "android")]
|
#![cfg(target_os = "android")]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Args,
|
||||||
args::ArgProxy,
|
args::ArgProxy,
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
Args,
|
|
||||||
};
|
};
|
||||||
use jni::{
|
use jni::{
|
||||||
|
JNIEnv,
|
||||||
objects::{JClass, JString},
|
objects::{JClass, JString},
|
||||||
sys::{jboolean, jchar, jint},
|
sys::{jboolean, jchar, jint},
|
||||||
JNIEnv,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -21,7 +21,7 @@ use jni::{
|
||||||
/// - tun_mtu: the tun mtu
|
/// - tun_mtu: the tun mtu
|
||||||
/// - dns_strategy: the dns strategy, see ArgDns enum
|
/// - dns_strategy: the dns strategy, see ArgDns enum
|
||||||
/// - verbosity: the verbosity level, see ArgVerbosity 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(
|
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
|
||||||
mut env: JNIEnv,
|
mut env: JNIEnv,
|
||||||
_clazz: JClass,
|
_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)
|
.close_fd_on_drop(close_fd_on_drop)
|
||||||
.dns(dns)
|
.dns(dns)
|
||||||
.verbosity(verbosity);
|
.verbosity(verbosity);
|
||||||
crate::mobile_api::mobile_run(args, tun_mtu, false)
|
crate::general_api::general_run_for_api(args, tun_mtu, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Shutdown tun2proxy
|
/// Shutdown tun2proxy
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint {
|
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> {
|
fn get_java_string(env: &mut JNIEnv, string: &JString) -> Result<String, Error> {
|
||||||
|
|
54
src/apple.rs
54
src/apple.rs
|
@ -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()
|
|
||||||
}
|
|
21
src/args.rs
21
src/args.rs
|
@ -9,7 +9,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Parser)]
|
#[derive(Debug, Clone, clap::Parser)]
|
||||||
#[command(author, version, about = "Tunnel interface to proxy.", long_about = None)]
|
#[command(author, version = version_info(), about = "Tunnel interface to proxy.", long_about = None)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Proxy URL in the form proto://[username[:password]@]host:port,
|
/// Proxy URL in the form proto://[username[:password]@]host:port,
|
||||||
/// where proto is one of socks4, socks5, http.
|
/// where proto is one of socks4, socks5, http.
|
||||||
|
@ -127,6 +127,10 @@ pub struct Args {
|
||||||
pub udpgw_keepalive: Option<u64>,
|
pub udpgw_keepalive: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn version_info() -> &'static str {
|
||||||
|
concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")")
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_tun(p: &str) -> Result<String> {
|
fn validate_tun(p: &str) -> Result<String> {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if p.len() <= 4 || &p[..4] != "utun" {
|
if p.len() <= 4 || &p[..4] != "utun" {
|
||||||
|
@ -181,8 +185,7 @@ impl Default for Args {
|
||||||
impl Args {
|
impl Args {
|
||||||
#[allow(clippy::let_and_return)]
|
#[allow(clippy::let_and_return)]
|
||||||
pub fn parse_args() -> Self {
|
pub fn parse_args() -> Self {
|
||||||
use clap::Parser;
|
let args = <Self as ::clap::Parser>::parse();
|
||||||
let args = Self::parse();
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if !args.setup && args.tun.is_none() {
|
if !args.setup && args.tun.is_none() {
|
||||||
eprintln!("Missing required argument, '--tun' must present when '--setup' is not used.");
|
eprintln!("Missing required argument, '--tun' must present when '--setup' is not used.");
|
||||||
|
@ -395,17 +398,11 @@ impl TryFrom<&str> for ArgProxy {
|
||||||
let e = format!("`{s}` does not contain a host");
|
let e = format!("`{s}` does not contain a host");
|
||||||
let host = url.host_str().ok_or(Error::from(e))?;
|
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 e = format!("`{s}` does not contain a port");
|
||||||
let port = url.port().ok_or(Error::from(&e))?;
|
let port = url.port_or_known_default().ok_or(Error::from(&e))?;
|
||||||
url_host.push(':');
|
|
||||||
url_host.push_str(port.to_string().as_str());
|
|
||||||
|
|
||||||
let e = format!("`{host}` could not be resolved");
|
let e2 = format!("`{host}` does not resolve to a usable IP address");
|
||||||
let mut addr_iter = url_host.to_socket_addrs().map_err(|_| Error::from(&e))?;
|
let addr = (host, port).to_socket_addrs()?.next().ok_or(Error::from(&e2))?;
|
||||||
|
|
||||||
let e = format!("`{host}` does not resolve to a usable IP address");
|
|
||||||
let addr = addr_iter.next().ok_or(Error::from(&e))?;
|
|
||||||
|
|
||||||
let credentials = if url.username() == "" && url.password().is_none() {
|
let credentials = if url.username() == "" && url.password().is_none() {
|
||||||
None
|
None
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use tun2proxy::{Args, BoxError};
|
use tun2proxy::{ArgVerbosity, Args, BoxError};
|
||||||
|
|
||||||
fn main() -> Result<(), BoxError> {
|
fn main() -> Result<(), BoxError> {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
|
@ -28,11 +28,16 @@ fn main() -> Result<(), BoxError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn main_async(args: Args) -> Result<(), BoxError> {
|
async fn main_async(args: Args) -> Result<(), BoxError> {
|
||||||
let default = format!("{:?},hickory_proto=warn", args.verbosity);
|
let ipstack = match args.verbosity {
|
||||||
|
ArgVerbosity::Trace => ArgVerbosity::Debug,
|
||||||
|
_ => args.verbosity,
|
||||||
|
};
|
||||||
|
let default = format!("{:?},hickory_proto=warn,ipstack={:?}", args.verbosity, ipstack);
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init();
|
||||||
|
|
||||||
let shutdown_token = tokio_util::sync::CancellationToken::new();
|
let shutdown_token = tokio_util::sync::CancellationToken::new();
|
||||||
let main_loop_handle = tokio::spawn({
|
let main_loop_handle = tokio::spawn({
|
||||||
|
let args = args.clone();
|
||||||
let shutdown_token = shutdown_token.clone();
|
let shutdown_token = shutdown_token.clone();
|
||||||
async move {
|
async move {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -40,18 +45,20 @@ async fn main_async(args: Args) -> Result<(), BoxError> {
|
||||||
if let Err(err) = namespace_proxy_main(args, shutdown_token).await {
|
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) {
|
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);
|
log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx);
|
||||||
}
|
}
|
||||||
unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) };
|
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 {
|
let ret = tun2proxy::general_run_async(args, tun::DEFAULT_MTU, cfg!(target_os = "macos"), shutdown_token).await;
|
||||||
log::error!("main loop error: {}", err);
|
if let Err(err) = &ret {
|
||||||
|
log::error!("main loop error: {err}");
|
||||||
}
|
}
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,13 +71,19 @@ async fn main_async(args: Args) -> Result<(), BoxError> {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
main_loop_handle.await?;
|
let tasks = main_loop_handle.await??;
|
||||||
|
|
||||||
if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) {
|
if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
log::info!("Ctrl-C fired, waiting the handler to finish...");
|
log::info!("Ctrl-C fired, waiting the handler to finish...");
|
||||||
ctrlc_handel.await.map_err(|err| err.to_string())?;
|
ctrlc_handel.await.map_err(|err| err.to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +92,7 @@ async fn namespace_proxy_main(
|
||||||
_args: Args,
|
_args: Args,
|
||||||
_shutdown_token: tokio_util::sync::CancellationToken,
|
_shutdown_token: tokio_util::sync::CancellationToken,
|
||||||
) -> Result<std::process::ExitStatus, tun2proxy::Error> {
|
) -> Result<std::process::ExitStatus, tun2proxy::Error> {
|
||||||
use nix::fcntl::{open, OFlag};
|
use nix::fcntl::{OFlag, open};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use std::os::fd::AsRawFd;
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@ use std::net::SocketAddr;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
net::{
|
net::{
|
||||||
tcp::{ReadHalf, WriteHalf},
|
|
||||||
UdpSocket,
|
UdpSocket,
|
||||||
|
tcp::{ReadHalf, WriteHalf},
|
||||||
},
|
},
|
||||||
sync::mpsc::{Receiver, Sender},
|
sync::mpsc::{Receiver, Sender},
|
||||||
};
|
};
|
||||||
use tun2proxy::{
|
use tun2proxy::{
|
||||||
udpgw::{Packet, UdpFlag},
|
|
||||||
ArgVerbosity, BoxError, Error, Result,
|
ArgVerbosity, BoxError, Error, Result,
|
||||||
|
udpgw::{Packet, UdpFlag},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60);
|
pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60);
|
||||||
|
@ -54,10 +54,8 @@ pub struct UdpGwArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpGwArgs {
|
impl UdpGwArgs {
|
||||||
#[allow(clippy::let_and_return)]
|
|
||||||
pub fn parse_args() -> Self {
|
pub fn parse_args() -> Self {
|
||||||
use clap::Parser;
|
<Self as ::clap::Parser>::parse()
|
||||||
Self::parse()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
23
src/dns.rs
23
src/dns.rs
|
@ -1,21 +1,16 @@
|
||||||
use hickory_proto::{
|
use hickory_proto::{
|
||||||
op::{Message, MessageType, ResponseCode},
|
op::{Message, MessageType, ResponseCode},
|
||||||
rr::{record_type::RecordType, Name, RData, Record},
|
rr::{
|
||||||
|
Name, RData, Record,
|
||||||
|
rdata::{A, AAAA},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::{net::IpAddr, str::FromStr};
|
use std::{net::IpAddr, str::FromStr};
|
||||||
|
|
||||||
pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result<Message, String> {
|
pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result<Message, String> {
|
||||||
let record = match ip {
|
let record = match ip {
|
||||||
IpAddr::V4(ip) => {
|
IpAddr::V4(ip) => Record::from_rdata(Name::from_str(domain)?, ttl, RData::A(A(ip))),
|
||||||
let mut record = Record::with(Name::from_str(domain)?, RecordType::A, ttl);
|
IpAddr::V6(ip) => Record::from_rdata(Name::from_str(domain)?, ttl, RData::AAAA(AAAA(ip))),
|
||||||
record.set_data(Some(RData::A(ip.into())));
|
|
||||||
record
|
|
||||||
}
|
|
||||||
IpAddr::V6(ip) => {
|
|
||||||
let mut record = Record::with(Name::from_str(domain)?, RecordType::AAAA, ttl);
|
|
||||||
record.set_data(Some(RData::AAAA(ip.into())));
|
|
||||||
record
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// We must indicate that this message is a response. Otherwise, implementations may not
|
// 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) {
|
pub fn remove_ipv6_entries(message: &mut Message) {
|
||||||
message
|
message.answers_mut().retain(|answer| !matches!(answer.data(), RData::AAAA(_)));
|
||||||
.answers_mut()
|
|
||||||
.retain(|answer| !matches!(answer.data(), Some(RData::AAAA(_))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result<IpAddr, String> {
|
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;
|
let mut cname = None;
|
||||||
for answer in message.answers() {
|
for answer in message.answers() {
|
||||||
match answer.data().ok_or("DNS response not contains answer data")? {
|
match answer.data() {
|
||||||
RData::A(addr) => {
|
RData::A(addr) => {
|
||||||
return Ok(IpAddr::V4((*addr).into()));
|
return Ok(IpAddr::V4((*addr).into()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub(crate) static DUMP_CALLBACK: Mutex<Option<DumpCallback>> = Mutex::new(None);
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// set dump log info callback.
|
/// set dump log info callback.
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn tun2proxy_set_log_callback(
|
pub unsafe extern "C" fn tun2proxy_set_log_callback(
|
||||||
callback: Option<unsafe extern "C" fn(ArgVerbosity, *const c_char, *mut c_void)>,
|
callback: Option<unsafe extern "C" fn(ArgVerbosity, *const c_char, *mut c_void)>,
|
||||||
ctx: *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 {
|
impl DumpCallback {
|
||||||
unsafe fn call(self, dump_level: ArgVerbosity, info: *const c_char) {
|
unsafe fn call(self, dump_level: ArgVerbosity, info: *const c_char) {
|
||||||
if let Some(cb) = self.0 {
|
if let Some(cb) = self.0 {
|
||||||
cb(dump_level, info, self.1);
|
unsafe { cb(dump_level, info, self.1) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub enum Error {
|
||||||
IpStack(#[from] ipstack::IpStackError),
|
IpStack(#[from] ipstack::IpStackError),
|
||||||
|
|
||||||
#[error("DnsProtoError {0:?}")]
|
#[error("DnsProtoError {0:?}")]
|
||||||
DnsProto(#[from] hickory_proto::error::ProtoError),
|
DnsProto(#[from] hickory_proto::ProtoError),
|
||||||
|
|
||||||
#[error("httparse::Error {0:?}")]
|
#[error("httparse::Error {0:?}")]
|
||||||
Httparse(#[from] httparse::Error),
|
Httparse(#[from] httparse::Error),
|
||||||
|
@ -43,10 +43,6 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("std::num::ParseIntError {0:?}")]
|
#[error("std::num::ParseIntError {0:?}")]
|
||||||
IntParseError(#[from] std::num::ParseIntError),
|
IntParseError(#[from] std::num::ParseIntError),
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
#[error("bincode::Error {0:?}")]
|
|
||||||
BincodeError(#[from] bincode::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Error {
|
impl From<&str> for Error {
|
||||||
|
|
269
src/general_api.rs
Normal file
269
src/general_api.rs
Normal 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);
|
||||||
|
|
||||||
|
#[allow(unused_mut, unused_assignments, unused_variables)]
|
||||||
|
let mut setup = true;
|
||||||
|
|
||||||
|
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(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, tun_mtu, args, shutdown_token));
|
||||||
|
Ok(join_handle.await.map_err(std::io::Error::from)??)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # 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
|
||||||
|
}
|
|
@ -4,11 +4,10 @@ use crate::{
|
||||||
proxy_handler::{ProxyHandler, ProxyHandlerManager},
|
proxy_handler::{ProxyHandler, ProxyHandlerManager},
|
||||||
session_info::{IpProtocol, SessionInfo},
|
session_info::{IpProtocol, SessionInfo},
|
||||||
};
|
};
|
||||||
use base64::Engine;
|
|
||||||
use httparse::Response;
|
use httparse::Response;
|
||||||
use socks5_impl::protocol::UserKey;
|
use socks5_impl::protocol::UserKey;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::RandomState, HashMap, VecDeque},
|
collections::{HashMap, VecDeque, hash_map::RandomState},
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
str,
|
str,
|
||||||
|
@ -141,8 +140,7 @@ impl HttpConnection {
|
||||||
.extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes());
|
.extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes());
|
||||||
}
|
}
|
||||||
AuthenticationScheme::Basic => {
|
AuthenticationScheme::Basic => {
|
||||||
let cred = format!("{}:{}", credentials.username, credentials.password);
|
let auth_b64 = base64easy::encode(credentials.to_string(), base64easy::EngineKind::Standard);
|
||||||
let auth_b64 = base64::engine::general_purpose::STANDARD.encode(cred);
|
|
||||||
self.server_outbuf
|
self.server_outbuf
|
||||||
.extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes());
|
.extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes());
|
||||||
}
|
}
|
||||||
|
@ -252,7 +250,7 @@ impl HttpConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The HTTP/1.1 expected to be keep alive waiting for the next frame so, we must
|
// 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)
|
// [RFC-9112](https://datatracker.ietf.org/doc/html/rfc9112#body.content-length)
|
||||||
|
|
||||||
// Transfer-Encoding isn't supported yet
|
// Transfer-Encoding isn't supported yet
|
||||||
|
|
76
src/lib.rs
76
src/lib.rs
|
@ -22,43 +22,34 @@ use std::{
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||||
net::{TcpSocket, TcpStream, UdpSocket},
|
net::{TcpSocket, TcpStream, UdpSocket},
|
||||||
sync::{mpsc::Receiver, Mutex},
|
sync::{Mutex, mpsc::Receiver},
|
||||||
};
|
};
|
||||||
pub use tokio_util::sync::CancellationToken;
|
pub use tokio_util::sync::CancellationToken;
|
||||||
use tproxy_config::is_private_ip;
|
use tproxy_config::is_private_ip;
|
||||||
use udp_stream::UdpStream;
|
use udp_stream::UdpStream;
|
||||||
#[cfg(feature = "udpgw")]
|
#[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 {
|
pub use {
|
||||||
args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType},
|
args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType},
|
||||||
error::{BoxError, Error, Result},
|
error::{BoxError, Error, Result},
|
||||||
traffic_status::{tun2proxy_set_traffic_status_callback, TrafficStatus},
|
traffic_status::{TrafficStatus, tun2proxy_set_traffic_status_callback},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "mimalloc")]
|
#[cfg(feature = "mimalloc")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
|
||||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
pub use general_api::general_run_async;
|
||||||
pub use desktop_api::desktop_run_async;
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
|
||||||
pub use mobile_api::{desktop_run_async, mobile_run, mobile_stop};
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub use mobile_api::{mobile_run, mobile_stop};
|
|
||||||
|
|
||||||
mod android;
|
mod android;
|
||||||
mod apple;
|
|
||||||
mod args;
|
mod args;
|
||||||
mod desktop_api;
|
|
||||||
mod directions;
|
mod directions;
|
||||||
mod dns;
|
mod dns;
|
||||||
mod dump_logger;
|
mod dump_logger;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod general_api;
|
||||||
mod http;
|
mod http;
|
||||||
mod mobile_api;
|
|
||||||
mod no_proxy;
|
mod no_proxy;
|
||||||
mod proxy_handler;
|
mod proxy_handler;
|
||||||
mod session_info;
|
mod session_info;
|
||||||
|
@ -73,12 +64,12 @@ pub mod win_svc;
|
||||||
|
|
||||||
const DNS_PORT: u16 = 53;
|
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)]
|
#[allow(unused)]
|
||||||
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)]
|
#[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 {
|
pub enum SocketProtocol {
|
||||||
Tcp,
|
Tcp,
|
||||||
Udp,
|
Udp,
|
||||||
|
@ -86,7 +77,10 @@ pub enum SocketProtocol {
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)]
|
#[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 {
|
pub enum SocketDomain {
|
||||||
IpV4,
|
IpV4,
|
||||||
IpV6,
|
IpV6,
|
||||||
|
@ -157,7 +151,9 @@ async fn create_udp_stream(socket_queue: &Option<Arc<SocketQueue>>, peer: Socket
|
||||||
/// * `mtu` - The MTU of the network device
|
/// * `mtu` - The MTU of the network device
|
||||||
/// * `args` - The arguments to use
|
/// * `args` - The arguments to use
|
||||||
/// * `shutdown_token` - The token to exit the server
|
/// * `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
|
where
|
||||||
D: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
D: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
|
@ -225,11 +221,11 @@ where
|
||||||
let socket_queue = None;
|
let socket_queue = None;
|
||||||
|
|
||||||
use socks5_impl::protocol::Version::{V4, V5};
|
use socks5_impl::protocol::Version::{V4, V5};
|
||||||
let mgr = match args.proxy.proxy_type {
|
let mgr: Arc<dyn ProxyHandlerManager> = match args.proxy.proxy_type {
|
||||||
ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc<dyn ProxyHandlerManager>,
|
ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)),
|
||||||
ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc<dyn ProxyHandlerManager>,
|
ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)),
|
||||||
ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc<dyn ProxyHandlerManager>,
|
ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)),
|
||||||
ProxyType::None => Arc::new(NoProxyManager::new()) as Arc<dyn ProxyHandlerManager>,
|
ProxyType::None => Arc::new(NoProxyManager::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ipstack_config = ipstack::IpStackConfig::default();
|
let mut ipstack_config = ipstack::IpStackConfig::default();
|
||||||
|
@ -257,7 +253,11 @@ where
|
||||||
client
|
client
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let task_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||||
|
use std::sync::atomic::Ordering::Relaxed;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let task_count = task_count.clone();
|
||||||
let virtual_dns = virtual_dns.clone();
|
let virtual_dns = virtual_dns.clone();
|
||||||
let ip_stack_stream = tokio::select! {
|
let ip_stack_stream = tokio::select! {
|
||||||
_ = shutdown_token.cancelled() => {
|
_ = shutdown_token.cancelled() => {
|
||||||
|
@ -268,10 +268,10 @@ where
|
||||||
ip_stack_stream?
|
ip_stack_stream?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let max_sessions = args.max_sessions as u64;
|
let max_sessions = args.max_sessions;
|
||||||
match ip_stack_stream {
|
match ip_stack_stream {
|
||||||
IpStackStream::Tcp(tcp) => {
|
IpStackStream::Tcp(tcp) => {
|
||||||
if TASK_COUNT.load(Relaxed) > max_sessions {
|
if task_count.load(Relaxed) >= max_sessions {
|
||||||
if args.exit_on_fatal_error {
|
if args.exit_on_fatal_error {
|
||||||
log::info!("Too many sessions that over {max_sessions}, exiting...");
|
log::info!("Too many sessions that over {max_sessions}, exiting...");
|
||||||
break;
|
break;
|
||||||
|
@ -279,7 +279,7 @@ where
|
||||||
log::warn!("Too many sessions that over {max_sessions}, dropping new session");
|
log::warn!("Too many sessions that over {max_sessions}, dropping new session");
|
||||||
continue;
|
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 info = SessionInfo::new(tcp.local_addr(), tcp.peer_addr(), IpProtocol::Tcp);
|
||||||
let domain_name = if let Some(virtual_dns) = &virtual_dns {
|
let domain_name = if let Some(virtual_dns) = &virtual_dns {
|
||||||
let mut virtual_dns = virtual_dns.lock().await;
|
let mut virtual_dns = virtual_dns.lock().await;
|
||||||
|
@ -294,11 +294,11 @@ where
|
||||||
if let Err(err) = handle_tcp_session(tcp, proxy_handler, socket_queue).await {
|
if let Err(err) = handle_tcp_session(tcp, proxy_handler, socket_queue).await {
|
||||||
log::error!("{} error \"{}\"", info, err);
|
log::error!("{} error \"{}\"", info, 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) => {
|
IpStackStream::Udp(udp) => {
|
||||||
if TASK_COUNT.load(Relaxed) > max_sessions {
|
if task_count.load(Relaxed) >= max_sessions {
|
||||||
if args.exit_on_fatal_error {
|
if args.exit_on_fatal_error {
|
||||||
log::info!("Too many sessions that over {max_sessions}, exiting...");
|
log::info!("Too many sessions that over {max_sessions}, exiting...");
|
||||||
break;
|
break;
|
||||||
|
@ -306,11 +306,11 @@ where
|
||||||
log::warn!("Too many sessions that over {max_sessions}, dropping new session");
|
log::warn!("Too many sessions that over {max_sessions}, dropping new session");
|
||||||
continue;
|
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);
|
let mut info = SessionInfo::new(udp.local_addr(), udp.peer_addr(), IpProtocol::Udp);
|
||||||
if info.dst.port() == DNS_PORT {
|
if info.dst.port() == DNS_PORT {
|
||||||
if is_private_ip(info.dst.ip()) {
|
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 {
|
if args.dns == ArgDns::OverTcp {
|
||||||
info.protocol = IpProtocol::Tcp;
|
info.protocol = IpProtocol::Tcp;
|
||||||
|
@ -320,7 +320,7 @@ where
|
||||||
if let Err(err) = handle_dns_over_tcp_session(udp, proxy_handler, socket_queue, ipv6_enabled).await {
|
if let Err(err) = handle_dns_over_tcp_session(udp, proxy_handler, socket_queue, ipv6_enabled).await {
|
||||||
log::error!("{} error \"{}\"", info, err);
|
log::error!("{} error \"{}\"", info, 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -331,7 +331,7 @@ where
|
||||||
log::error!("{} error \"{}\"", info, err);
|
log::error!("{} error \"{}\"", info, 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ where
|
||||||
if let Err(e) = handle_udp_gateway_session(udp, udpgw, &dst_addr, proxy_handler, queue, ipv6_enabled).await {
|
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 {} with \"{}\"", info, 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -374,7 +374,7 @@ where
|
||||||
if let Err(err) = handle_udp_associate_session(udp, ty, proxy_handler, socket_queue, ipv6_enabled).await {
|
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 {} with \"{}\"", info, 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) => {
|
Err(e) => {
|
||||||
|
@ -384,7 +384,7 @@ where
|
||||||
}
|
}
|
||||||
IpStackStream::UnknownTransport(u) => {
|
IpStackStream::UnknownTransport(u) => {
|
||||||
let len = u.payload().len();
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
IpStackStream::UnknownNetwork(pkt) => {
|
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<()> {
|
async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc<Mutex<VirtualDns>>) -> crate::Result<()> {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
#![cfg(target_os = "linux")]
|
#![cfg(target_os = "linux")]
|
||||||
|
|
||||||
use crate::{error, SocketDomain, SocketProtocol};
|
use crate::{SocketDomain, SocketProtocol, error};
|
||||||
use nix::{
|
use nix::{
|
||||||
errno::Errno,
|
errno::Errno,
|
||||||
fcntl::{self, FdFlag},
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -16,14 +16,14 @@ use tokio::net::{TcpSocket, UdpSocket, UnixDatagram};
|
||||||
|
|
||||||
const REQUEST_BUFFER_SIZE: usize = 64;
|
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 {
|
struct Request {
|
||||||
protocol: SocketProtocol,
|
protocol: SocketProtocol,
|
||||||
domain: SocketDomain,
|
domain: SocketDomain,
|
||||||
number: u32,
|
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 {
|
enum Response {
|
||||||
Ok,
|
Ok,
|
||||||
}
|
}
|
||||||
|
@ -135,14 +135,21 @@ where
|
||||||
// Borrow socket as mut to prevent multiple simultaneous requests
|
// Borrow socket as mut to prevent multiple simultaneous requests
|
||||||
let socket = socket.deref_mut();
|
let socket = socket.deref_mut();
|
||||||
|
|
||||||
|
let mut request = [0u8; 1000];
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
let request = bincode::serialize(&Request {
|
let size = bincode::encode_into_slice(
|
||||||
|
Request {
|
||||||
protocol: T::domain(),
|
protocol: T::domain(),
|
||||||
domain,
|
domain,
|
||||||
number,
|
number,
|
||||||
})?;
|
},
|
||||||
|
&mut request,
|
||||||
|
bincode::config::standard(),
|
||||||
|
)
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
|
||||||
|
|
||||||
socket.send(&request[..]).await?;
|
socket.send(&request[..size]).await?;
|
||||||
|
|
||||||
// Receive response
|
// Receive response
|
||||||
loop {
|
loop {
|
||||||
|
@ -161,7 +168,9 @@ where
|
||||||
|
|
||||||
// Parse response
|
// Parse response
|
||||||
let response = &msg.iovs().next().unwrap()[..msg.bytes];
|
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) {
|
if !matches!(response, Response::Ok) {
|
||||||
return Err("Request for new sockets failed".into());
|
return Err("Request for new sockets failed".into());
|
||||||
}
|
}
|
||||||
|
@ -194,10 +203,14 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()>
|
||||||
|
|
||||||
let len = socket.recv(&mut buf[..]).await?;
|
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 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);
|
let mut owned_fd_buf: Vec<OwnedFd> = Vec::with_capacity(request.number as usize);
|
||||||
for _ in 0..request.number {
|
for _ in 0..request.number {
|
||||||
|
@ -223,7 +236,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 raw_fd_buf: Vec<RawFd> = owned_fd_buf.iter().map(|fd| fd.as_raw_fd()).collect();
|
||||||
let cmsg = ControlMessage::ScmRights(&raw_fd_buf[..]);
|
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)?;
|
sendmsg::<()>(socket.as_raw_fd(), &iov, &[cmsg], MsgFlags::empty(), None)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
proxy_handler::{ProxyHandler, ProxyHandlerManager},
|
proxy_handler::{ProxyHandler, ProxyHandlerManager},
|
||||||
session_info::SessionInfo,
|
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 std::{collections::VecDeque, net::SocketAddr, sync::Arc};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::sync::{LazyLock, Mutex};
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// set traffic status callback.
|
/// set traffic status callback.
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn tun2proxy_set_traffic_status_callback(
|
pub unsafe extern "C" fn tun2proxy_set_traffic_status_callback(
|
||||||
send_interval_secs: u32,
|
send_interval_secs: u32,
|
||||||
callback: Option<unsafe extern "C" fn(*const TrafficStatus, *mut c_void)>,
|
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 {
|
impl TrafficStatusCallback {
|
||||||
unsafe fn call(self, info: &TrafficStatus) {
|
unsafe fn call(self, info: &TrafficStatus) {
|
||||||
if let Some(cb) = self.0 {
|
if let Some(cb) = self.0 {
|
||||||
cb(info, self.1);
|
unsafe { cb(info, self.1) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ use std::{collections::VecDeque, hash::Hash, net::SocketAddr, sync::atomic::Orde
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::{
|
net::{
|
||||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
|
||||||
TcpStream,
|
TcpStream,
|
||||||
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
},
|
},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::{sleep, Duration},
|
time::{Duration, sleep},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::<u16>();
|
pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::<u16>();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use hashlink::{linked_hash_map::RawEntryMut, LruCache};
|
use hashlink::{LruCache, linked_hash_map::RawEntryMut};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
|
|
|
@ -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()?;
|
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) {
|
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);
|
log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx);
|
||||||
}
|
}
|
||||||
unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) };
|
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 {
|
let ret = crate::general_run_async(args.clone(), tun::DEFAULT_MTU, false, shutdown_token).await;
|
||||||
log::error!("main loop error: {}", err);
|
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>(())
|
Ok::<(), crate::Error>(())
|
||||||
})?;
|
})?;
|
||||||
|
|
Loading…
Add table
Reference in a new issue