committed by
GitHub
41 changed files with 2009 additions and 3273 deletions
@ -1,77 +0,0 @@ |
|||||
on: [push, pull_request] |
|
||||
|
|
||||
name: Build and Formatting Tests |
|
||||
|
|
||||
jobs: |
|
||||
check: |
|
||||
name: Check |
|
||||
runs-on: ubuntu-latest |
|
||||
steps: |
|
||||
- uses: actions/checkout@v2 |
|
||||
- uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: stable |
|
||||
override: true |
|
||||
- uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: check |
|
||||
|
|
||||
fmt: |
|
||||
name: Rustfmt |
|
||||
runs-on: ubuntu-latest |
|
||||
steps: |
|
||||
- uses: actions/checkout@v2 |
|
||||
- uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: stable |
|
||||
override: true |
|
||||
- run: rustup component add rustfmt |
|
||||
- uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: fmt |
|
||||
args: --all -- --check |
|
||||
|
|
||||
clippy: |
|
||||
name: Clippy |
|
||||
strategy: |
|
||||
matrix: |
|
||||
os: [ubuntu-latest, macos-latest, windows-latest] |
|
||||
runs-on: ${{ matrix.os }} |
|
||||
steps: |
|
||||
- uses: actions/checkout@v2 |
|
||||
- uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: stable |
|
||||
override: true |
|
||||
- name: rustfmt |
|
||||
run: | |
|
||||
rustc --version |
|
||||
cargo fmt --all -- --check |
|
||||
- run: rustup component add clippy |
|
||||
- uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: clippy |
|
||||
args: -- -D warnings |
|
||||
- name: Build |
|
||||
run: cargo build --verbose |
|
||||
|
|
||||
iperf: |
|
||||
name: Iperf |
|
||||
runs-on: ubuntu-latest |
|
||||
steps: |
|
||||
- uses: actions/checkout@v2 |
|
||||
- uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: stable |
|
||||
override: true |
|
||||
- uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: build |
|
||||
args: --release |
|
||||
- run: sudo apt-get install -y iperf3 dante-server |
|
||||
- run: sudo systemctl stop danted |
|
||||
- run: sudo tests/iperf/test.sh |
|
||||
@ -1,48 +0,0 @@ |
|||||
# |
|
||||
name: Create and publish a Docker image |
|
||||
|
|
||||
# Configures this workflow to run every time a change is pushed to the branch called `release`. |
|
||||
on: |
|
||||
push: |
|
||||
tags: [ 'v*.*.*' ] |
|
||||
|
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. |
|
||||
env: |
|
||||
REGISTRY: ghcr.io |
|
||||
IMAGE_NAME: ${{ github.repository }} |
|
||||
|
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. |
|
||||
jobs: |
|
||||
build-and-push-image: |
|
||||
runs-on: ubuntu-latest |
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. |
|
||||
permissions: |
|
||||
contents: read |
|
||||
packages: write |
|
||||
# |
|
||||
steps: |
|
||||
- name: Checkout repository |
|
||||
uses: actions/checkout@v4 |
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. |
|
||||
- name: Log in to the Container registry |
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 |
|
||||
with: |
|
||||
registry: ${{ env.REGISTRY }} |
|
||||
username: ${{ github.actor }} |
|
||||
password: ${{ secrets.GITHUB_TOKEN }} |
|
||||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. |
|
||||
- name: Extract metadata (tags, labels) for Docker |
|
||||
id: meta |
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 |
|
||||
with: |
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} |
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. |
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. |
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. |
|
||||
- name: Build and push Docker image |
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 |
|
||||
with: |
|
||||
context: . |
|
||||
push: true |
|
||||
tags: ${{ steps.meta.outputs.tags }} |
|
||||
labels: ${{ steps.meta.outputs.labels }} |
|
||||
@ -0,0 +1,26 @@ |
|||||
|
name: Push or PR |
||||
|
|
||||
|
on: |
||||
|
[push, pull_request] |
||||
|
|
||||
|
env: |
||||
|
CARGO_TERM_COLOR: always |
||||
|
|
||||
|
jobs: |
||||
|
build_n_test: |
||||
|
strategy: |
||||
|
matrix: |
||||
|
os: [ubuntu-latest, macos-latest, windows-latest] |
||||
|
|
||||
|
runs-on: ${{ matrix.os }} |
||||
|
|
||||
|
steps: |
||||
|
- uses: actions/checkout@v3 |
||||
|
- name: rustfmt |
||||
|
run: cargo fmt --all -- --check |
||||
|
- name: check |
||||
|
run: cargo check --verbose |
||||
|
- name: clippy |
||||
|
run: cargo clippy --all-targets --all-features -- -D warnings |
||||
|
- name: Build |
||||
|
run: cargo build --verbose --tests --all-features |
||||
@ -1,45 +0,0 @@ |
|||||
on: |
|
||||
pull_request_review: |
|
||||
types: [submitted] |
|
||||
push: |
|
||||
workflow_dispatch: |
|
||||
pull_request_target: |
|
||||
types: [labeled] |
|
||||
|
|
||||
name: Integration Tests |
|
||||
|
|
||||
jobs: |
|
||||
proxy_tests: |
|
||||
name: Proxy Tests |
|
||||
runs-on: ubuntu-latest |
|
||||
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test') |
|
||||
steps: |
|
||||
- uses: actions/checkout@v2 |
|
||||
- uses: actions-rs/toolchain@v1 |
|
||||
with: |
|
||||
profile: minimal |
|
||||
toolchain: stable |
|
||||
override: true |
|
||||
- uses: actions-rs/cargo@v1 |
|
||||
with: |
|
||||
command: test |
|
||||
args: --no-run |
|
||||
- name: Populate .env |
|
||||
env: |
|
||||
DOTENV: ${{ secrets.DOTENV }} |
|
||||
run: echo "$DOTENV" > .env |
|
||||
- name: Set up runner SSH key |
|
||||
run: >- |
|
||||
set -o allexport && |
|
||||
source .env && |
|
||||
set +o allexport && |
|
||||
mkdir ~/.ssh && |
|
||||
echo "$TEST_SERVER_PRIVATE_SSH_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa |
|
||||
- name: Run tests |
|
||||
run: >- |
|
||||
set -o allexport && |
|
||||
source .env && |
|
||||
set +o allexport && |
|
||||
ssh -N -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -D 1080 "$TEST_SERVER_SSH_DST" & |
|
||||
while ! nc -z 127.0.0.1 1080; do sleep 1; done && |
|
||||
sudo -E /home/runner/.cargo/bin/cargo test |
|
||||
@ -1,11 +1,9 @@ |
|||||
examples/ |
.env |
||||
|
project.xcworkspace/ |
||||
|
xcuserdata/ |
||||
|
.vscode/ |
||||
|
.VSCodeCounter/ |
||||
build/ |
build/ |
||||
tmp/ |
tmp/ |
||||
.* |
|
||||
*.secret |
|
||||
*.iml |
|
||||
!/.github |
|
||||
!/.cargo |
|
||||
/target |
|
||||
Cargo.lock |
Cargo.lock |
||||
manual-test.sh |
target/ |
||||
|
|||||
@ -1,12 +0,0 @@ |
|||||
# Changelog for Tun2Proxy |
|
||||
|
|
||||
## 0.1.1 |
|
||||
|
|
||||
- Updated dependencies: |
|
||||
- `chrono`: v0.4, ready for next planned release ; |
|
||||
- `clap`: last version ; |
|
||||
- `mio`: v0.8 + rename renamed feature (os-util became os-ext) + some fixes due to removal of `TcpSocket` type ; |
|
||||
- `smoltcp`: set v0.8 but from crates.io, plus old reference could not work. |
|
||||
- Fixes: |
|
||||
- Removed typo from Cargo.toml ; |
|
||||
- Clippy. |
|
||||
@ -1,66 +1,43 @@ |
|||||
[package] |
[package] |
||||
authors = ["B. Blechschmidt", "ssrlive"] |
|
||||
edition = "2021" |
|
||||
name = "tun2proxy" |
name = "tun2proxy" |
||||
version = "0.1.12" |
version = "0.2.0" |
||||
|
edition = "2021" |
||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
|
||||
[lib] |
[lib] |
||||
crate-type = ["cdylib", "lib"] |
crate-type = ["staticlib", "cdylib", "lib"] |
||||
|
|
||||
[dependencies] |
[dependencies] |
||||
|
async-recursion = "1.0" |
||||
|
async-trait = "0.1" |
||||
base64 = { version = "0.21" } |
base64 = { version = "0.21" } |
||||
clap = { version = "4.4", features = ["derive"] } |
chrono = "0.4" |
||||
ctrlc2 = { version = "3.5", features = ["termination"] } |
clap = { version = "4.4", features = ["derive", "wrap_help", "color"] } |
||||
|
ctrlc2 = { version = "3.5", features = ["tokio", "termination"] } |
||||
digest_auth = "0.3" |
digest_auth = "0.3" |
||||
dotenvy = "0.15" |
dotenvy = "0.15" |
||||
env_logger = "0.10" |
env_logger = "0.11" |
||||
hashlink = "0.9" |
hashlink = "0.9" |
||||
httparse = "1.8" |
httparse = "1.8" |
||||
libc = "0.2" |
ipstack = { version = "0.0", features = ["log"] } |
||||
log = "0.4" |
log = { version = "0.4", features = ["std"] } |
||||
mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } |
socks5-impl = { version = "0.5" } |
||||
nix = { version = "0.27", features = [ |
|
||||
"process", |
|
||||
"signal", |
|
||||
"fs", |
|
||||
"mount", |
|
||||
"user", |
|
||||
] } |
|
||||
prctl = "1.0" |
|
||||
smoltcp = { version = "0.11", features = ["std", "phy-tuntap_interface"] } |
|
||||
socks5-impl = { version = "0.5", default-features = false } |
|
||||
thiserror = "1.0" |
thiserror = "1.0" |
||||
|
tokio = { version = "1.35", features = ["full"] } |
||||
|
tproxy-config = { version = "0.1", features = ["log"] } |
||||
trust-dns-proto = "0.23" |
trust-dns-proto = "0.23" |
||||
|
tun2 = { version = "1.0", features = ["async"] } |
||||
|
udp-stream = { version = "0.0", default-features = false } |
||||
unicase = "2.7" |
unicase = "2.7" |
||||
url = "2.5" |
url = "2.5" |
||||
|
|
||||
[target.'cfg(target_family="unix")'.dependencies] |
|
||||
fork = "0.1" |
|
||||
|
|
||||
[target.'cfg(target_os="android")'.dependencies] |
[target.'cfg(target_os="android")'.dependencies] |
||||
android_logger = "0.13" |
android_logger = "0.13" |
||||
jni = { version = "0.21", default-features = false } |
jni = { version = "0.21", default-features = false } |
||||
|
|
||||
[dev-dependencies] |
|
||||
ctor = "0.2" |
|
||||
reqwest = { version = "0.11", default-features = false, features = [ |
|
||||
"blocking", |
|
||||
"json", |
|
||||
"rustls-tls", |
|
||||
] } |
|
||||
serial_test = "3.0" |
|
||||
test-log = "0.2" |
|
||||
|
|
||||
[target.'cfg(target_os="windows")'.dependencies] |
|
||||
rand = "0.8" |
|
||||
windows = { version = "0.52", features = [ |
|
||||
"Win32_Storage_FileSystem", |
|
||||
"Win32_NetworkManagement_IpHelper", |
|
||||
"Win32_NetworkManagement_Ndis", |
|
||||
"Win32_Networking_WinSock", |
|
||||
"Win32_Foundation", |
|
||||
] } |
|
||||
wintun = { version = "0.4", features = ["panic_on_unsent_packets"] } |
|
||||
|
|
||||
[build-dependencies] |
[build-dependencies] |
||||
serde_json = "1.0" |
serde_json = "1.0" |
||||
|
|
||||
|
[[bin]] |
||||
|
name = "tun2proxy" |
||||
|
path = "src/bin/main.rs" |
||||
|
|||||
@ -1,20 +0,0 @@ |
|||||
#################################################################################################### |
|
||||
## Builder |
|
||||
#################################################################################################### |
|
||||
FROM rust:latest AS builder |
|
||||
|
|
||||
WORKDIR /worker |
|
||||
COPY ./ . |
|
||||
RUN cargo build --release --target x86_64-unknown-linux-gnu |
|
||||
|
|
||||
|
|
||||
#################################################################################################### |
|
||||
## Final image |
|
||||
#################################################################################################### |
|
||||
FROM ubuntu:latest |
|
||||
|
|
||||
RUN apt update && apt install -y iproute2 && apt clean all |
|
||||
|
|
||||
COPY --from=builder /worker/target/x86_64-unknown-linux-gnu/release/tun2proxy /usr/bin/tun2proxy |
|
||||
|
|
||||
ENTRYPOINT ["/usr/bin/tun2proxy", "--setup", "auto"] |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
Build iOS framework |
||||
|
---------------- |
||||
|
|
||||
|
# Install Rust build tools |
||||
|
|
||||
|
- Install Xcode Command Line Tools: `xcode-select --install` |
||||
|
- Install Rust programming language: `curl https://sh.rustup.rs -sSf | sh` |
||||
|
- Install iOS target support: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios` |
||||
|
- Install cbindgen tool: `cargo install cbindgen` |
||||
|
|
||||
|
# Building iOS framework |
||||
|
|
||||
|
Due to an unknown reason at present, compiling Rust code inside Xcode fails, so you have to manually compile it. Please run the following command in zsh (or bash): |
||||
|
```bash |
||||
|
cd tun2proxy |
||||
|
|
||||
|
cargo build --release --target aarch64-apple-ios |
||||
|
cargo build --release --target x86_64-apple-ios |
||||
|
lipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a |
||||
|
cbindgen --config cbindgen.toml -l C -o target/tun2proxy-sys.h |
||||
|
``` |
||||
@ -0,0 +1,398 @@ |
|||||
|
// !$*UTF8*$! |
||||
|
{ |
||||
|
archiveVersion = 1; |
||||
|
classes = { |
||||
|
}; |
||||
|
objectVersion = 55; |
||||
|
objects = { |
||||
|
|
||||
|
/* Begin PBXBuildFile section */ |
||||
|
B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = B648A35829F43D110045B334 /* Tun2proxyWrapper.m */; }; |
||||
|
B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; |
||||
|
B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B692ACC829F7EA4C006BF04D /* libtun2proxy.a */; }; |
||||
|
B6DE654429F4255A00468184 /* tun2proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = B6DE654329F4255A00468184 /* tun2proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; |
||||
|
/* End PBXBuildFile section */ |
||||
|
|
||||
|
/* Begin PBXFileReference section */ |
||||
|
B648A35829F43D110045B334 /* Tun2proxyWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tun2proxyWrapper.m; sourceTree = "<group>"; }; |
||||
|
B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tun2proxyWrapper.h; sourceTree = "<group>"; }; |
||||
|
B692ACC829F7EA4C006BF04D /* libtun2proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtun2proxy.a; path = ../target/libtun2proxy.a; sourceTree = "<group>"; }; |
||||
|
B6DE654029F4255A00468184 /* tun2proxy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = tun2proxy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; |
||||
|
B6DE654329F4255A00468184 /* tun2proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tun2proxy.h; sourceTree = "<group>"; }; |
||||
|
/* End PBXFileReference section */ |
||||
|
|
||||
|
/* Begin PBXFrameworksBuildPhase section */ |
||||
|
B6DE653D29F4255A00468184 /* Frameworks */ = { |
||||
|
isa = PBXFrameworksBuildPhase; |
||||
|
buildActionMask = 2147483647; |
||||
|
files = ( |
||||
|
B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */, |
||||
|
); |
||||
|
runOnlyForDeploymentPostprocessing = 0; |
||||
|
}; |
||||
|
/* End PBXFrameworksBuildPhase section */ |
||||
|
|
||||
|
/* Begin PBXGroup section */ |
||||
|
B692ACC729F7EA4C006BF04D /* Frameworks */ = { |
||||
|
isa = PBXGroup; |
||||
|
children = ( |
||||
|
B692ACC829F7EA4C006BF04D /* libtun2proxy.a */, |
||||
|
); |
||||
|
name = Frameworks; |
||||
|
sourceTree = "<group>"; |
||||
|
}; |
||||
|
B6DE653629F4255A00468184 = { |
||||
|
isa = PBXGroup; |
||||
|
children = ( |
||||
|
B6DE654229F4255A00468184 /* tun2proxy */, |
||||
|
B6DE654129F4255A00468184 /* Products */, |
||||
|
B692ACC729F7EA4C006BF04D /* Frameworks */, |
||||
|
); |
||||
|
sourceTree = "<group>"; |
||||
|
}; |
||||
|
B6DE654129F4255A00468184 /* Products */ = { |
||||
|
isa = PBXGroup; |
||||
|
children = ( |
||||
|
B6DE654029F4255A00468184 /* tun2proxy.framework */, |
||||
|
); |
||||
|
name = Products; |
||||
|
sourceTree = "<group>"; |
||||
|
}; |
||||
|
B6DE654229F4255A00468184 /* tun2proxy */ = { |
||||
|
isa = PBXGroup; |
||||
|
children = ( |
||||
|
B6DE654329F4255A00468184 /* tun2proxy.h */, |
||||
|
B648A35829F43D110045B334 /* Tun2proxyWrapper.m */, |
||||
|
B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */, |
||||
|
); |
||||
|
path = tun2proxy; |
||||
|
sourceTree = "<group>"; |
||||
|
}; |
||||
|
/* End PBXGroup section */ |
||||
|
|
||||
|
/* Begin PBXHeadersBuildPhase section */ |
||||
|
B6DE653B29F4255A00468184 /* Headers */ = { |
||||
|
isa = PBXHeadersBuildPhase; |
||||
|
buildActionMask = 2147483647; |
||||
|
files = ( |
||||
|
B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */, |
||||
|
B6DE654429F4255A00468184 /* tun2proxy.h in Headers */, |
||||
|
); |
||||
|
runOnlyForDeploymentPostprocessing = 0; |
||||
|
}; |
||||
|
/* End PBXHeadersBuildPhase section */ |
||||
|
|
||||
|
/* Begin PBXNativeTarget section */ |
||||
|
B6DE653F29F4255A00468184 /* tun2proxy */ = { |
||||
|
isa = PBXNativeTarget; |
||||
|
buildConfigurationList = B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */; |
||||
|
buildPhases = ( |
||||
|
B692ACB329F7E203006BF04D /* Run Script */, |
||||
|
B6DE653B29F4255A00468184 /* Headers */, |
||||
|
B6DE653C29F4255A00468184 /* Sources */, |
||||
|
B6DE653D29F4255A00468184 /* Frameworks */, |
||||
|
B6DE653E29F4255A00468184 /* Resources */, |
||||
|
); |
||||
|
buildRules = ( |
||||
|
); |
||||
|
dependencies = ( |
||||
|
); |
||||
|
name = tun2proxy; |
||||
|
productName = tun2proxy; |
||||
|
productReference = B6DE654029F4255A00468184 /* tun2proxy.framework */; |
||||
|
productType = "com.apple.product-type.framework"; |
||||
|
}; |
||||
|
/* End PBXNativeTarget section */ |
||||
|
|
||||
|
/* Begin PBXProject section */ |
||||
|
B6DE653729F4255A00468184 /* Project object */ = { |
||||
|
isa = PBXProject; |
||||
|
attributes = { |
||||
|
BuildIndependentTargetsInParallel = 1; |
||||
|
LastUpgradeCheck = 1430; |
||||
|
TargetAttributes = { |
||||
|
B6DE653F29F4255A00468184 = { |
||||
|
CreatedOnToolsVersion = 13.2.1; |
||||
|
}; |
||||
|
}; |
||||
|
}; |
||||
|
buildConfigurationList = B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */; |
||||
|
compatibilityVersion = "Xcode 13.0"; |
||||
|
developmentRegion = en; |
||||
|
hasScannedForEncodings = 0; |
||||
|
knownRegions = ( |
||||
|
en, |
||||
|
Base, |
||||
|
); |
||||
|
mainGroup = B6DE653629F4255A00468184; |
||||
|
productRefGroup = B6DE654129F4255A00468184 /* Products */; |
||||
|
projectDirPath = ""; |
||||
|
projectRoot = ""; |
||||
|
targets = ( |
||||
|
B6DE653F29F4255A00468184 /* tun2proxy */, |
||||
|
); |
||||
|
}; |
||||
|
/* End PBXProject section */ |
||||
|
|
||||
|
/* Begin PBXResourcesBuildPhase section */ |
||||
|
B6DE653E29F4255A00468184 /* Resources */ = { |
||||
|
isa = PBXResourcesBuildPhase; |
||||
|
buildActionMask = 2147483647; |
||||
|
files = ( |
||||
|
); |
||||
|
runOnlyForDeploymentPostprocessing = 0; |
||||
|
}; |
||||
|
/* End PBXResourcesBuildPhase section */ |
||||
|
|
||||
|
/* Begin PBXShellScriptBuildPhase section */ |
||||
|
B692ACB329F7E203006BF04D /* Run Script */ = { |
||||
|
isa = PBXShellScriptBuildPhase; |
||||
|
buildActionMask = 2147483647; |
||||
|
files = ( |
||||
|
); |
||||
|
inputFileListPaths = ( |
||||
|
); |
||||
|
inputPaths = ( |
||||
|
); |
||||
|
name = "Run Script"; |
||||
|
outputFileListPaths = ( |
||||
|
); |
||||
|
outputPaths = ( |
||||
|
); |
||||
|
runOnlyForDeploymentPostprocessing = 0; |
||||
|
shellPath = /bin/bash; |
||||
|
shellScript = "set -e\nPATH=\"$PATH:${HOME}/.cargo/bin\"\nRUST_PROJ=${PROJECT_DIR}/..\ncd \"${RUST_PROJ}\"\ncargo build --release --target aarch64-apple-ios\ncargo build --release --target x86_64-apple-ios\nlipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a\ncbindgen --config cbindgen.toml -l C -o target/tun2proxy-sys.h\n"; |
||||
|
}; |
||||
|
/* End PBXShellScriptBuildPhase section */ |
||||
|
|
||||
|
/* Begin PBXSourcesBuildPhase section */ |
||||
|
B6DE653C29F4255A00468184 /* Sources */ = { |
||||
|
isa = PBXSourcesBuildPhase; |
||||
|
buildActionMask = 2147483647; |
||||
|
files = ( |
||||
|
B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */, |
||||
|
); |
||||
|
runOnlyForDeploymentPostprocessing = 0; |
||||
|
}; |
||||
|
/* End PBXSourcesBuildPhase section */ |
||||
|
|
||||
|
/* Begin XCBuildConfiguration section */ |
||||
|
B6DE654529F4255A00468184 /* Debug */ = { |
||||
|
isa = XCBuildConfiguration; |
||||
|
buildSettings = { |
||||
|
ALWAYS_SEARCH_USER_PATHS = NO; |
||||
|
CLANG_ANALYZER_NONNULL = YES; |
||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; |
||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; |
||||
|
CLANG_CXX_LIBRARY = "libc++"; |
||||
|
CLANG_ENABLE_MODULES = YES; |
||||
|
CLANG_ENABLE_OBJC_ARC = YES; |
||||
|
CLANG_ENABLE_OBJC_WEAK = YES; |
||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; |
||||
|
CLANG_WARN_BOOL_CONVERSION = YES; |
||||
|
CLANG_WARN_COMMA = YES; |
||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES; |
||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; |
||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; |
||||
|
CLANG_WARN_EMPTY_BODY = YES; |
||||
|
CLANG_WARN_ENUM_CONVERSION = YES; |
||||
|
CLANG_WARN_INFINITE_RECURSION = YES; |
||||
|
CLANG_WARN_INT_CONVERSION = YES; |
||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; |
||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; |
||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; |
||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; |
||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; |
||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES; |
||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES; |
||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; |
||||
|
CLANG_WARN_UNREACHABLE_CODE = YES; |
||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
||||
|
COPY_PHASE_STRIP = NO; |
||||
|
CURRENT_PROJECT_VERSION = 1; |
||||
|
DEBUG_INFORMATION_FORMAT = dwarf; |
||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES; |
||||
|
ENABLE_TESTABILITY = YES; |
||||
|
GCC_C_LANGUAGE_STANDARD = gnu11; |
||||
|
GCC_DYNAMIC_NO_PIC = NO; |
||||
|
GCC_NO_COMMON_BLOCKS = YES; |
||||
|
GCC_OPTIMIZATION_LEVEL = 0; |
||||
|
GCC_PREPROCESSOR_DEFINITIONS = ( |
||||
|
"DEBUG=1", |
||||
|
"$(inherited)", |
||||
|
); |
||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES; |
||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
||||
|
GCC_WARN_UNUSED_FUNCTION = YES; |
||||
|
GCC_WARN_UNUSED_VARIABLE = YES; |
||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0; |
||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; |
||||
|
MTL_FAST_MATH = YES; |
||||
|
ONLY_ACTIVE_ARCH = YES; |
||||
|
SDKROOT = iphoneos; |
||||
|
VERSIONING_SYSTEM = "apple-generic"; |
||||
|
VERSION_INFO_PREFIX = ""; |
||||
|
}; |
||||
|
name = Debug; |
||||
|
}; |
||||
|
B6DE654629F4255A00468184 /* Release */ = { |
||||
|
isa = XCBuildConfiguration; |
||||
|
buildSettings = { |
||||
|
ALWAYS_SEARCH_USER_PATHS = NO; |
||||
|
CLANG_ANALYZER_NONNULL = YES; |
||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; |
||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; |
||||
|
CLANG_CXX_LIBRARY = "libc++"; |
||||
|
CLANG_ENABLE_MODULES = YES; |
||||
|
CLANG_ENABLE_OBJC_ARC = YES; |
||||
|
CLANG_ENABLE_OBJC_WEAK = YES; |
||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; |
||||
|
CLANG_WARN_BOOL_CONVERSION = YES; |
||||
|
CLANG_WARN_COMMA = YES; |
||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES; |
||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; |
||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; |
||||
|
CLANG_WARN_EMPTY_BODY = YES; |
||||
|
CLANG_WARN_ENUM_CONVERSION = YES; |
||||
|
CLANG_WARN_INFINITE_RECURSION = YES; |
||||
|
CLANG_WARN_INT_CONVERSION = YES; |
||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; |
||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; |
||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; |
||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; |
||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; |
||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES; |
||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES; |
||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; |
||||
|
CLANG_WARN_UNREACHABLE_CODE = YES; |
||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
||||
|
COPY_PHASE_STRIP = NO; |
||||
|
CURRENT_PROJECT_VERSION = 1; |
||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; |
||||
|
ENABLE_NS_ASSERTIONS = NO; |
||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES; |
||||
|
GCC_C_LANGUAGE_STANDARD = gnu11; |
||||
|
GCC_NO_COMMON_BLOCKS = YES; |
||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES; |
||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
||||
|
GCC_WARN_UNUSED_FUNCTION = YES; |
||||
|
GCC_WARN_UNUSED_VARIABLE = YES; |
||||
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0; |
||||
|
MTL_ENABLE_DEBUG_INFO = NO; |
||||
|
MTL_FAST_MATH = YES; |
||||
|
SDKROOT = iphoneos; |
||||
|
VALIDATE_PRODUCT = YES; |
||||
|
VERSIONING_SYSTEM = "apple-generic"; |
||||
|
VERSION_INFO_PREFIX = ""; |
||||
|
}; |
||||
|
name = Release; |
||||
|
}; |
||||
|
B6DE654829F4255A00468184 /* Debug */ = { |
||||
|
isa = XCBuildConfiguration; |
||||
|
buildSettings = { |
||||
|
CODE_SIGN_IDENTITY = ""; |
||||
|
CODE_SIGN_STYLE = Automatic; |
||||
|
CURRENT_PROJECT_VERSION = 1; |
||||
|
DEFINES_MODULE = YES; |
||||
|
DEVELOPMENT_TEAM = ""; |
||||
|
DYLIB_COMPATIBILITY_VERSION = 1; |
||||
|
DYLIB_CURRENT_VERSION = 1; |
||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath"; |
||||
|
ENABLE_BITCODE = NO; |
||||
|
ENABLE_MODULE_VERIFIER = YES; |
||||
|
GENERATE_INFOPLIST_FILE = YES; |
||||
|
HEADER_SEARCH_PATHS = ""; |
||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = ""; |
||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; |
||||
|
LD_RUNPATH_SEARCH_PATHS = ( |
||||
|
"$(inherited)", |
||||
|
"@executable_path/Frameworks", |
||||
|
"@loader_path/Frameworks", |
||||
|
); |
||||
|
LIBRARY_SEARCH_PATHS = ( |
||||
|
../target, |
||||
|
"$(PROJECT_DIR)/../target", |
||||
|
); |
||||
|
MARKETING_VERSION = 1.0; |
||||
|
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; |
||||
|
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; |
||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy; |
||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; |
||||
|
SKIP_INSTALL = YES; |
||||
|
SWIFT_EMIT_LOC_STRINGS = YES; |
||||
|
TARGETED_DEVICE_FAMILY = "1,2"; |
||||
|
USER_HEADER_SEARCH_PATHS = ../target; |
||||
|
}; |
||||
|
name = Debug; |
||||
|
}; |
||||
|
B6DE654929F4255A00468184 /* Release */ = { |
||||
|
isa = XCBuildConfiguration; |
||||
|
buildSettings = { |
||||
|
CODE_SIGN_IDENTITY = ""; |
||||
|
CODE_SIGN_STYLE = Automatic; |
||||
|
CURRENT_PROJECT_VERSION = 1; |
||||
|
DEFINES_MODULE = YES; |
||||
|
DEVELOPMENT_TEAM = ""; |
||||
|
DYLIB_COMPATIBILITY_VERSION = 1; |
||||
|
DYLIB_CURRENT_VERSION = 1; |
||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath"; |
||||
|
ENABLE_BITCODE = NO; |
||||
|
ENABLE_MODULE_VERIFIER = YES; |
||||
|
GENERATE_INFOPLIST_FILE = YES; |
||||
|
HEADER_SEARCH_PATHS = ""; |
||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = ""; |
||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; |
||||
|
LD_RUNPATH_SEARCH_PATHS = ( |
||||
|
"$(inherited)", |
||||
|
"@executable_path/Frameworks", |
||||
|
"@loader_path/Frameworks", |
||||
|
); |
||||
|
LIBRARY_SEARCH_PATHS = ( |
||||
|
../target, |
||||
|
"$(PROJECT_DIR)/../target", |
||||
|
); |
||||
|
MARKETING_VERSION = 1.0; |
||||
|
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; |
||||
|
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; |
||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy; |
||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; |
||||
|
SKIP_INSTALL = YES; |
||||
|
SWIFT_EMIT_LOC_STRINGS = YES; |
||||
|
TARGETED_DEVICE_FAMILY = "1,2"; |
||||
|
USER_HEADER_SEARCH_PATHS = ../target; |
||||
|
}; |
||||
|
name = Release; |
||||
|
}; |
||||
|
/* End XCBuildConfiguration section */ |
||||
|
|
||||
|
/* Begin XCConfigurationList section */ |
||||
|
B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */ = { |
||||
|
isa = XCConfigurationList; |
||||
|
buildConfigurations = ( |
||||
|
B6DE654529F4255A00468184 /* Debug */, |
||||
|
B6DE654629F4255A00468184 /* Release */, |
||||
|
); |
||||
|
defaultConfigurationIsVisible = 0; |
||||
|
defaultConfigurationName = Release; |
||||
|
}; |
||||
|
B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */ = { |
||||
|
isa = XCConfigurationList; |
||||
|
buildConfigurations = ( |
||||
|
B6DE654829F4255A00468184 /* Debug */, |
||||
|
B6DE654929F4255A00468184 /* Release */, |
||||
|
); |
||||
|
defaultConfigurationIsVisible = 0; |
||||
|
defaultConfigurationName = Release; |
||||
|
}; |
||||
|
/* End XCConfigurationList section */ |
||||
|
}; |
||||
|
rootObject = B6DE653729F4255A00468184 /* Project object */; |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
//
|
||||
|
// Tun2proxyWrapper.h
|
||||
|
// tun2proxy
|
||||
|
//
|
||||
|
// Created by ssrlive on 2023/4/23.
|
||||
|
//
|
||||
|
|
||||
|
#ifndef Tun2proxyWrapper_h |
||||
|
#define Tun2proxyWrapper_h |
||||
|
|
||||
|
@interface Tun2proxyWrapper : NSObject |
||||
|
|
||||
|
+ (void)startWithConfig:(NSString *)proxy_url |
||||
|
tun_fd:(int)tun_fd |
||||
|
tun_mtu:(uint32_t)tun_mtu |
||||
|
dns_over_tcp:(bool)dns_over_tcp |
||||
|
verbose:(bool)verbose; |
||||
|
+ (void) shutdown; |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
#endif /* Tun2proxyWrapper_h */ |
||||
@ -0,0 +1,27 @@ |
|||||
|
// |
||||
|
// Tun2proxyWrapper.m |
||||
|
// tun2proxy |
||||
|
// |
||||
|
// Created by ssrlive on 2023/4/23. |
||||
|
// |
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
#import "Tun2proxyWrapper.h" |
||||
|
#include "tun2proxy-sys.h" |
||||
|
|
||||
|
@implementation Tun2proxyWrapper |
||||
|
|
||||
|
+ (void)startWithConfig:(NSString *)proxy_url |
||||
|
tun_fd:(int)tun_fd |
||||
|
tun_mtu:(uint32_t)tun_mtu |
||||
|
dns_over_tcp:(bool)dns_over_tcp |
||||
|
verbose:(bool)verbose { |
||||
|
tun2proxy_run(proxy_url.UTF8String, tun_fd, tun_mtu, dns_over_tcp, verbose); |
||||
|
} |
||||
|
|
||||
|
+ (void)shutdown { |
||||
|
tun2proxy_stop(); |
||||
|
} |
||||
|
|
||||
|
@end |
||||
@ -0,0 +1,18 @@ |
|||||
|
//
|
||||
|
// tun2proxy.h
|
||||
|
// tun2proxy
|
||||
|
//
|
||||
|
// Created by tun2proxy on 2023/4/22.
|
||||
|
//
|
||||
|
|
||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
//! Project version number for tun2proxy.
|
||||
|
FOUNDATION_EXPORT double tun2proxyVersionNumber; |
||||
|
|
||||
|
//! Project version string for tun2proxy.
|
||||
|
FOUNDATION_EXPORT const unsigned char tun2proxyVersionString[]; |
||||
|
|
||||
|
// In this header, you should import all the public headers of your framework using statements like #import <tun2proxy/PublicHeader.h>
|
||||
|
|
||||
|
#import <tun2proxy/Tun2proxyWrapper.h> |
||||
@ -0,0 +1,6 @@ |
|||||
|
[export] |
||||
|
include = ["tun2proxy_run", "tun2proxy_stop", "tun2proxy_set_log_callback"] |
||||
|
exclude = [ |
||||
|
"Java_com_github_shadowsocks_bg_Tun2proxy_run", |
||||
|
"Java_com_github_shadowsocks_bg_Tun2proxy_stop", |
||||
|
] |
||||
@ -0,0 +1,24 @@ |
|||||
|
# logoutput: /var/log/socks.log |
||||
|
internal: 10.0.0.3 port = 10800 |
||||
|
external: 10.0.0.3 |
||||
|
clientmethod: none |
||||
|
socksmethod: none |
||||
|
user.privileged: root |
||||
|
user.notprivileged: nobody |
||||
|
|
||||
|
client pass { |
||||
|
from: 0/0 to: 0/0 |
||||
|
log: error connect disconnect |
||||
|
} |
||||
|
|
||||
|
socks pass { |
||||
|
from: 0/0 to: 0/0 |
||||
|
command: bind connect udpassociate |
||||
|
log: error connect disconnect |
||||
|
socksmethod: none |
||||
|
} |
||||
|
|
||||
|
socks pass { |
||||
|
from: 0.0.0.0/0 to: 0.0.0.0/0 |
||||
|
command: bindreply udpreply |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
# sudo apt install iperf3 dante-server |
||||
|
# sudo systemctl stop danted |
||||
|
|
||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
||||
|
echo $SCRIPT_DIR |
||||
|
|
||||
|
netns="test" |
||||
|
dante="danted" |
||||
|
tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy" |
||||
|
|
||||
|
ip netns add "$netns" |
||||
|
|
||||
|
ip link add veth0 type veth peer name veth0 netns "$netns" |
||||
|
|
||||
|
# Configure veth0 in default ns |
||||
|
ip addr add 10.0.0.2/24 dev veth0 |
||||
|
ip link set dev veth0 up |
||||
|
|
||||
|
# Configure veth0 in child ns |
||||
|
ip netns exec "$netns" ip addr add 10.0.0.3/24 dev veth0 |
||||
|
ip netns exec "$netns" ip addr add 10.0.0.4/24 dev veth0 |
||||
|
ip netns exec "$netns" ip link set dev veth0 up |
||||
|
|
||||
|
# Configure lo interface in child ns |
||||
|
ip netns exec "$netns" ip addr add 127.0.0.1/8 dev lo |
||||
|
ip netns exec "$netns" ip link set dev lo up |
||||
|
|
||||
|
echo "Starting Dante in background ..." |
||||
|
ip netns exec "$netns" "$dante" -f ${SCRIPT_DIR}/dante.conf & |
||||
|
|
||||
|
# Start iperf3 server in netns |
||||
|
ip netns exec "$netns" iperf3 -s -B 10.0.0.4 & |
||||
|
|
||||
|
sleep 1 |
||||
|
|
||||
|
# Prepare tun2proxy |
||||
|
ip tuntap add name tun0 mode tun |
||||
|
ip link set tun0 up |
||||
|
ip route add 10.0.0.4 dev tun0 |
||||
|
"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off & |
||||
|
|
||||
|
sleep 3 |
||||
|
|
||||
|
# Run iperf client through tun2proxy |
||||
|
iperf3 -c 10.0.0.4 -P 10 -R |
||||
|
|
||||
|
sleep 3 |
||||
|
|
||||
|
iperf3 -c 10.0.0.4 -P 10 |
||||
|
|
||||
|
# Clean up |
||||
|
# sudo sh -c "pkill tun2proxy; pkill iperf3; pkill danted; ip link del tun0; ip netns del test" |
||||
@ -0,0 +1,66 @@ |
|||||
|
#! /usr/bin/bash -x |
||||
|
|
||||
|
# Please set the following parameters according to your environment |
||||
|
# BYPASS_IP=123.45.67.89 |
||||
|
PROXY_IP=127.0.0.1 |
||||
|
PROXY_PORT=1080 |
||||
|
PROXY_TYPE=SOCKS5 |
||||
|
|
||||
|
function core_function() { |
||||
|
local is_envonly="${1}" |
||||
|
local bypass_ip="${2}" |
||||
|
|
||||
|
sudo ip tuntap add name tun0 mode tun |
||||
|
sudo ip link set tun0 up |
||||
|
|
||||
|
sudo ip route add "${bypass_ip}" $(ip route | grep '^default' | cut -d ' ' -f 2-) |
||||
|
|
||||
|
sudo ip route add 128.0.0.0/1 dev tun0 |
||||
|
sudo ip route add 0.0.0.0/1 dev tun0 |
||||
|
|
||||
|
sudo ip route add ::/1 dev tun0 |
||||
|
sudo ip route add 8000::/1 dev tun0 |
||||
|
|
||||
|
sudo sh -c "echo nameserver 198.18.0.1 > /etc/resolv.conf" |
||||
|
|
||||
|
if [ "$is_envonly" = true ]; then |
||||
|
read -n 1 -s -r -p "Don't do anything. If you want to exit and clearup environment, press any key..." |
||||
|
echo "" |
||||
|
restore |
||||
|
else |
||||
|
trap 'echo "" && echo "tun2proxy exited with code: $?" && restore' EXIT |
||||
|
local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
||||
|
local APP_BIN_PATH="${SCRIPT_DIR}/../target/release/tun2proxy" |
||||
|
"${APP_BIN_PATH}" --tun tun0 --proxy "${PROXY_TYPE}://${PROXY_IP}:${PROXY_PORT}" -v trace |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
function restore() { |
||||
|
sudo ip link del tun0 |
||||
|
sudo systemctl restart systemd-resolved.service |
||||
|
} |
||||
|
|
||||
|
function main() { |
||||
|
local action=${1} |
||||
|
# [ -z ${1} ] && action="envonly" |
||||
|
|
||||
|
local bypass_ip=${2} |
||||
|
# [ -z ${2} ] && bypass_ip="123.45.67.89" |
||||
|
|
||||
|
case "${action}" in |
||||
|
envonly) |
||||
|
core_function true "${bypass_ip}" |
||||
|
;; |
||||
|
tun2proxy) |
||||
|
core_function false "${bypass_ip}" |
||||
|
;; |
||||
|
*) |
||||
|
echo "Arguments error! [${action}]" |
||||
|
echo "Usage: `basename $0` [envonly|tun2proxy] [bypass_ip]" |
||||
|
;; |
||||
|
esac |
||||
|
|
||||
|
exit 0 |
||||
|
} |
||||
|
|
||||
|
main "$@" |
||||
@ -0,0 +1,83 @@ |
|||||
|
#!/bin/bash |
||||
|
|
||||
|
function install_rperf_bin() { |
||||
|
local rperf_bin_url="https://github.com/ssrlive/rperf/releases/latest/download/rperf-x86_64-unknown-linux-musl.zip" |
||||
|
local rperf_bin_zip_file="rperf-x86_64-unknown-linux-musl.zip" |
||||
|
|
||||
|
command -v rperf > /dev/null |
||||
|
if [ $? -ne 0 ]; then |
||||
|
echo "Downloading rperf binary ..." |
||||
|
wget "$rperf_bin_url" >/dev/null 2>&1 |
||||
|
unzip "$rperf_bin_zip_file" rperf -d /usr/local/bin/ >/dev/null 2>&1 |
||||
|
rm "$rperf_bin_zip_file" |
||||
|
fi |
||||
|
|
||||
|
rperf -h >/dev/null 2>&1 |
||||
|
if [ $? -ne 0 ]; then |
||||
|
echo "Failed to install rperf binary" |
||||
|
exit 1 |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
install_rperf_bin |
||||
|
|
||||
|
sudo apt install dante-server -y >/dev/null 2>&1 |
||||
|
sudo systemctl stop danted |
||||
|
|
||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
||||
|
# echo $SCRIPT_DIR |
||||
|
|
||||
|
netns="test" |
||||
|
dante="danted" |
||||
|
tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy" |
||||
|
|
||||
|
ip netns add "$netns" |
||||
|
|
||||
|
ip link add veth0 type veth peer name veth0 netns "$netns" |
||||
|
|
||||
|
# Configure veth0 in default ns |
||||
|
ip addr add 10.0.0.2/24 dev veth0 |
||||
|
ip link set dev veth0 up |
||||
|
|
||||
|
# Configure veth0 in child ns |
||||
|
ip netns exec "$netns" ip addr add 10.0.0.3/24 dev veth0 |
||||
|
ip netns exec "$netns" ip addr add 10.0.0.4/24 dev veth0 |
||||
|
ip netns exec "$netns" ip link set dev veth0 up |
||||
|
|
||||
|
# Configure lo interface in child ns |
||||
|
ip netns exec "$netns" ip addr add 127.0.0.1/8 dev lo |
||||
|
ip netns exec "$netns" ip link set dev lo up |
||||
|
|
||||
|
echo "Starting Dante in background ..." |
||||
|
ip netns exec "$netns" "$dante" -f ${SCRIPT_DIR}/dante.conf & |
||||
|
|
||||
|
# Start rperf server in netns |
||||
|
ip netns exec "$netns" rperf -s -B 10.0.0.4 & |
||||
|
|
||||
|
sleep 1 |
||||
|
|
||||
|
# Prepare tun2proxy |
||||
|
ip tuntap add name tun0 mode tun |
||||
|
ip link set tun0 up |
||||
|
ip route add 10.0.0.4 dev tun0 |
||||
|
"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off & |
||||
|
|
||||
|
sleep 3 |
||||
|
|
||||
|
# Run rperf client through tun2proxy |
||||
|
rperf -c 10.0.0.4 -v off -P 1 -r |
||||
|
|
||||
|
sleep 3 |
||||
|
|
||||
|
rperf -c 10.0.0.4 -v off -P 1 |
||||
|
|
||||
|
sleep 3 |
||||
|
|
||||
|
rperf -c 10.0.0.4 -v off -P 1 -u |
||||
|
|
||||
|
sleep 3 |
||||
|
|
||||
|
rperf -c 10.0.0.4 -v trace -P 1 -u -r |
||||
|
|
||||
|
# Clean up |
||||
|
# sudo sh -c "pkill tun2proxy; pkill rperf; pkill danted; ip link del tun0; ip netns del test" |
||||
@ -0,0 +1,70 @@ |
|||||
|
#![cfg(any(target_os = "ios", target_os = "android"))] |
||||
|
|
||||
|
use crate::{Args, Builder, Quit}; |
||||
|
use std::{os::raw::c_int, sync::Arc}; |
||||
|
|
||||
|
static mut TUN_QUIT: Option<Arc<Quit>> = None; |
||||
|
|
||||
|
pub(crate) fn tun2proxy_internal_run(args: Args, tun_mtu: usize) -> c_int { |
||||
|
if unsafe { TUN_QUIT.is_some() } { |
||||
|
log::error!("tun2proxy already started"); |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
let block = async move { |
||||
|
log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); |
||||
|
|
||||
|
let mut config = tun2::Configuration::default(); |
||||
|
config.raw_fd(args.tun_fd.ok_or(crate::Error::from("tun_fd"))?); |
||||
|
|
||||
|
let device = tun2::create_as_async(&config).map_err(std::io::Error::from)?; |
||||
|
|
||||
|
#[cfg(target_os = "android")] |
||||
|
let tun2proxy = Builder::new(device, args).mtu(tun_mtu).build(); |
||||
|
#[cfg(target_os = "ios")] |
||||
|
let tun2proxy = Builder::new(device, args).mtu(tun_mtu).build(); |
||||
|
let (join_handle, quit) = tun2proxy.start(); |
||||
|
|
||||
|
unsafe { TUN_QUIT = Some(Arc::new(quit)) }; |
||||
|
|
||||
|
join_handle.await |
||||
|
}; |
||||
|
|
||||
|
match tokio::runtime::Builder::new_multi_thread().enable_all().build() { |
||||
|
Err(_err) => { |
||||
|
log::error!("failed to create tokio runtime with error: {:?}", _err); |
||||
|
-1 |
||||
|
} |
||||
|
Ok(rt) => match rt.block_on(block) { |
||||
|
Ok(_) => 0, |
||||
|
Err(_err) => { |
||||
|
log::error!("failed to run tun2proxy with error: {:?}", _err); |
||||
|
-2 |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub(crate) fn tun2proxy_internal_stop() -> c_int { |
||||
|
let res = match unsafe { &TUN_QUIT } { |
||||
|
None => { |
||||
|
log::error!("tun2proxy not started"); |
||||
|
-1 |
||||
|
} |
||||
|
Some(tun_quit) => match tokio::runtime::Builder::new_multi_thread().enable_all().build() { |
||||
|
Err(_err) => { |
||||
|
log::error!("failed to create tokio runtime with error: {:?}", _err); |
||||
|
-2 |
||||
|
} |
||||
|
Ok(rt) => match rt.block_on(async move { tun_quit.trigger().await }) { |
||||
|
Ok(_) => 0, |
||||
|
Err(_err) => { |
||||
|
log::error!("failed to stop tun2proxy with error: {:?}", _err); |
||||
|
-3 |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
}; |
||||
|
unsafe { TUN_QUIT = None }; |
||||
|
res |
||||
|
} |
||||
@ -0,0 +1,198 @@ |
|||||
|
use crate::{Error, Result}; |
||||
|
use socks5_impl::protocol::UserKey; |
||||
|
use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; |
||||
|
use tproxy_config::TUN_NAME; |
||||
|
|
||||
|
#[derive(Debug, Clone, clap::Parser)] |
||||
|
#[command(author, version, about = "tun2proxy application.", long_about = None)] |
||||
|
pub struct Args { |
||||
|
/// Proxy URL in the form proto://[username[:password]@]host:port,
|
||||
|
/// where proto is one of socks4, socks5, http. For example:
|
||||
|
/// socks5://myname:[email protected]:1080
|
||||
|
#[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")] |
||||
|
pub proxy: ArgProxy, |
||||
|
|
||||
|
/// Name of the tun interface
|
||||
|
#[arg(short, long, value_name = "name", conflicts_with = "tun_fd", default_value = TUN_NAME)] |
||||
|
pub tun: String, |
||||
|
|
||||
|
/// File descriptor of the tun interface
|
||||
|
#[arg(long, value_name = "fd", conflicts_with = "tun")] |
||||
|
pub tun_fd: Option<i32>, |
||||
|
|
||||
|
/// IPv6 enabled
|
||||
|
#[arg(short = '6', long)] |
||||
|
pub ipv6_enabled: bool, |
||||
|
|
||||
|
#[cfg(target_os = "linux")] |
||||
|
#[arg(short, long)] |
||||
|
/// Routing and system setup, which decides whether to setup the routing and system configuration,
|
||||
|
/// this option requires root privileges
|
||||
|
pub setup: bool, |
||||
|
|
||||
|
/// DNS handling strategy
|
||||
|
#[arg(short, long, value_name = "strategy", value_enum, default_value = "direct")] |
||||
|
pub dns: ArgDns, |
||||
|
|
||||
|
/// DNS resolver address
|
||||
|
#[arg(long, value_name = "IP", default_value = "8.8.8.8")] |
||||
|
pub dns_addr: IpAddr, |
||||
|
|
||||
|
/// IPs used in routing setup which should bypass the tunnel
|
||||
|
#[arg(short, long, value_name = "IP")] |
||||
|
pub bypass: Vec<IpAddr>, |
||||
|
|
||||
|
/// Verbosity level
|
||||
|
#[arg(short, long, value_name = "level", value_enum, default_value = "info")] |
||||
|
pub verbosity: ArgVerbosity, |
||||
|
} |
||||
|
|
||||
|
impl Default for Args { |
||||
|
fn default() -> Self { |
||||
|
Args { |
||||
|
proxy: ArgProxy::default(), |
||||
|
tun: TUN_NAME.to_string(), |
||||
|
tun_fd: None, |
||||
|
ipv6_enabled: false, |
||||
|
#[cfg(target_os = "linux")] |
||||
|
setup: false, |
||||
|
dns: ArgDns::default(), |
||||
|
dns_addr: "8.8.8.8".parse().unwrap(), |
||||
|
bypass: vec![], |
||||
|
verbosity: ArgVerbosity::Info, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl Args { |
||||
|
pub fn parse_args() -> Self { |
||||
|
use clap::Parser; |
||||
|
Self::parse() |
||||
|
} |
||||
|
|
||||
|
pub fn new(tun_fd: Option<i32>, proxy: ArgProxy, dns: ArgDns, verbosity: ArgVerbosity) -> Self { |
||||
|
Args { |
||||
|
proxy, |
||||
|
tun_fd, |
||||
|
dns, |
||||
|
verbosity, |
||||
|
..Args::default() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] |
||||
|
pub enum ArgVerbosity { |
||||
|
Off, |
||||
|
Error, |
||||
|
Warn, |
||||
|
#[default] |
||||
|
Info, |
||||
|
Debug, |
||||
|
Trace, |
||||
|
} |
||||
|
|
||||
|
impl std::fmt::Display for ArgVerbosity { |
||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
||||
|
match self { |
||||
|
ArgVerbosity::Off => write!(f, "off"), |
||||
|
ArgVerbosity::Error => write!(f, "error"), |
||||
|
ArgVerbosity::Warn => write!(f, "warn"), |
||||
|
ArgVerbosity::Info => write!(f, "info"), |
||||
|
ArgVerbosity::Debug => write!(f, "debug"), |
||||
|
ArgVerbosity::Trace => write!(f, "trace"), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// DNS query handling strategy
|
||||
|
/// - Virtual: Use a virtual DNS server to handle DNS queries, also known as Fake-IP mode
|
||||
|
/// - OverTcp: Use TCP to send DNS queries to the DNS server
|
||||
|
/// - Direct: Do not handle DNS by relying on DNS server bypassing
|
||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] |
||||
|
pub enum ArgDns { |
||||
|
Virtual, |
||||
|
OverTcp, |
||||
|
#[default] |
||||
|
Direct, |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone, Debug)] |
||||
|
pub struct ArgProxy { |
||||
|
pub proxy_type: ProxyType, |
||||
|
pub addr: SocketAddr, |
||||
|
pub credentials: Option<UserKey>, |
||||
|
} |
||||
|
|
||||
|
impl Default for ArgProxy { |
||||
|
fn default() -> Self { |
||||
|
ArgProxy { |
||||
|
proxy_type: ProxyType::Socks5, |
||||
|
addr: "127.0.0.1:1080".parse().unwrap(), |
||||
|
credentials: None, |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl ArgProxy { |
||||
|
pub fn from_url(s: &str) -> Result<ArgProxy> { |
||||
|
let e = format!("`{s}` is not a valid proxy URL"); |
||||
|
let url = url::Url::parse(s).map_err(|_| Error::from(&e))?; |
||||
|
let e = format!("`{s}` does not contain a host"); |
||||
|
let host = url.host_str().ok_or(Error::from(e))?; |
||||
|
|
||||
|
let mut url_host = String::from(host); |
||||
|
let e = format!("`{s}` does not contain a port"); |
||||
|
let port = url.port().ok_or(Error::from(&e))?; |
||||
|
url_host.push(':'); |
||||
|
url_host.push_str(port.to_string().as_str()); |
||||
|
|
||||
|
let e = format!("`{host}` could not be resolved"); |
||||
|
let mut addr_iter = url_host.to_socket_addrs().map_err(|_| Error::from(&e))?; |
||||
|
|
||||
|
let e = format!("`{host}` does not resolve to a usable IP address"); |
||||
|
let addr = addr_iter.next().ok_or(Error::from(&e))?; |
||||
|
|
||||
|
let credentials = if url.username() == "" && url.password().is_none() { |
||||
|
None |
||||
|
} else { |
||||
|
let username = String::from(url.username()); |
||||
|
let password = String::from(url.password().unwrap_or("")); |
||||
|
Some(UserKey::new(username, password)) |
||||
|
}; |
||||
|
|
||||
|
let scheme = url.scheme(); |
||||
|
|
||||
|
let proxy_type = match url.scheme().to_ascii_lowercase().as_str() { |
||||
|
"socks4" => Some(ProxyType::Socks4), |
||||
|
"socks5" => Some(ProxyType::Socks5), |
||||
|
"http" => Some(ProxyType::Http), |
||||
|
_ => None, |
||||
|
} |
||||
|
.ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?; |
||||
|
|
||||
|
Ok(ArgProxy { |
||||
|
proxy_type, |
||||
|
addr, |
||||
|
credentials, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] |
||||
|
pub enum ProxyType { |
||||
|
Socks4, |
||||
|
#[default] |
||||
|
Socks5, |
||||
|
Http, |
||||
|
} |
||||
|
|
||||
|
impl std::fmt::Display for ProxyType { |
||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
|
match self { |
||||
|
ProxyType::Socks4 => write!(f, "socks4"), |
||||
|
ProxyType::Socks5 => write!(f, "socks5"), |
||||
|
ProxyType::Http => write!(f, "http"), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; |
||||
|
use tun2::DEFAULT_MTU as MTU; |
||||
|
use tun2proxy::{Args, Builder}; |
||||
|
|
||||
|
#[tokio::main] |
||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> { |
||||
|
dotenvy::dotenv().ok(); |
||||
|
let args = Args::parse_args(); |
||||
|
|
||||
|
let bypass_ips = args.bypass.clone(); |
||||
|
|
||||
|
// let default = format!("{}={:?}", module_path!(), args.verbosity);
|
||||
|
let default = format!("{:?}", args.verbosity); |
||||
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); |
||||
|
|
||||
|
let mut config = tun2::Configuration::default(); |
||||
|
config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); |
||||
|
config.destination(TUN_GATEWAY); |
||||
|
if let Some(tun_fd) = args.tun_fd { |
||||
|
config.raw_fd(tun_fd); |
||||
|
} else { |
||||
|
config.name(&args.tun); |
||||
|
} |
||||
|
|
||||
|
#[cfg(target_os = "linux")] |
||||
|
config.platform_config(|config| { |
||||
|
#[allow(deprecated)] |
||||
|
config.packet_information(true); |
||||
|
config.ensure_root_privileges(args.setup); |
||||
|
}); |
||||
|
|
||||
|
#[cfg(target_os = "windows")] |
||||
|
config.platform_config(|config| { |
||||
|
config.device_guid(Some(12324323423423434234_u128)); |
||||
|
}); |
||||
|
|
||||
|
#[allow(unused_variables)] |
||||
|
let mut tproxy_args = TproxyArgs::new() |
||||
|
.tun_dns(args.dns_addr) |
||||
|
.proxy_addr(args.proxy.addr) |
||||
|
.bypass_ips(&bypass_ips); |
||||
|
#[allow(unused_assignments)] |
||||
|
if args.tun_fd.is_none() { |
||||
|
tproxy_args = tproxy_args.tun_name(&args.tun); |
||||
|
} |
||||
|
|
||||
|
#[allow(unused_mut, unused_assignments, unused_variables)] |
||||
|
let mut setup = true; |
||||
|
|
||||
|
#[cfg(target_os = "linux")] |
||||
|
{ |
||||
|
setup = args.setup; |
||||
|
if setup { |
||||
|
tproxy_config::tproxy_setup(&tproxy_args)?; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let device = tun2::create_as_async(&config)?; |
||||
|
|
||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))] |
||||
|
if setup { |
||||
|
tproxy_config::tproxy_setup(&tproxy_args)?; |
||||
|
} |
||||
|
|
||||
|
let tun2proxy = Builder::new(device, args).mtu(MTU).build(); |
||||
|
let (join_handle, quit) = tun2proxy.start(); |
||||
|
|
||||
|
ctrlc2::set_async_handler(async move { |
||||
|
quit.trigger().await.expect("quit error"); |
||||
|
}) |
||||
|
.await; |
||||
|
|
||||
|
if let Err(err) = join_handle.await { |
||||
|
log::trace!("main_entry error {}", err); |
||||
|
} |
||||
|
|
||||
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
||||
|
if setup { |
||||
|
tproxy_config::tproxy_remove(&tproxy_args)?; |
||||
|
} |
||||
|
|
||||
|
Ok(()) |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
#![allow(dead_code)] |
||||
|
|
||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)] |
||||
|
pub(crate) enum IncomingDirection { |
||||
|
FromServer, |
||||
|
FromClient, |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)] |
||||
|
pub(crate) enum OutgoingDirection { |
||||
|
ToServer, |
||||
|
ToClient, |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)] |
||||
|
pub(crate) enum Direction { |
||||
|
Incoming(IncomingDirection), |
||||
|
Outgoing(OutgoingDirection), |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone, Eq, PartialEq, Debug)] |
||||
|
pub(crate) struct DataEvent<'a, T> { |
||||
|
pub(crate) direction: T, |
||||
|
pub(crate) buffer: &'a [u8], |
||||
|
} |
||||
|
|
||||
|
pub(crate) type IncomingDataEvent<'a> = DataEvent<'a, IncomingDirection>; |
||||
|
pub(crate) type OutgoingDataEvent<'a> = DataEvent<'a, OutgoingDirection>; |
||||
@ -0,0 +1,71 @@ |
|||||
|
use std::{ |
||||
|
os::raw::{c_char, c_int, c_void}, |
||||
|
sync::Mutex, |
||||
|
}; |
||||
|
|
||||
|
pub(crate) static DUMP_CALLBACK: Mutex<Option<DumpCallback>> = Mutex::new(None); |
||||
|
|
||||
|
/// # Safety
|
||||
|
///
|
||||
|
/// set dump log info callback.
|
||||
|
#[no_mangle] |
||||
|
pub unsafe extern "C" fn tun2proxy_set_log_callback( |
||||
|
callback: Option<unsafe extern "C" fn(c_int, *const c_char, *mut c_void)>, |
||||
|
ctx: *mut c_void, |
||||
|
) { |
||||
|
*DUMP_CALLBACK.lock().unwrap() = Some(DumpCallback(callback, ctx)); |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone)] |
||||
|
pub struct DumpCallback(Option<unsafe extern "C" fn(c_int, *const c_char, *mut c_void)>, *mut c_void); |
||||
|
|
||||
|
impl DumpCallback { |
||||
|
unsafe fn call(self, dump_level: c_int, info: *const c_char) { |
||||
|
if let Some(cb) = self.0 { |
||||
|
cb(dump_level, info, self.1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
unsafe impl Send for DumpCallback {} |
||||
|
unsafe impl Sync for DumpCallback {} |
||||
|
|
||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)] |
||||
|
pub struct DumpLogger {} |
||||
|
|
||||
|
impl log::Log for DumpLogger { |
||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool { |
||||
|
metadata.level() <= log::Level::Trace |
||||
|
} |
||||
|
|
||||
|
fn log(&self, record: &log::Record) { |
||||
|
if self.enabled(record.metadata()) { |
||||
|
let current_crate_name = env!("CARGO_CRATE_NAME"); |
||||
|
if record.module_path().unwrap_or("").starts_with(current_crate_name) { |
||||
|
self.do_dump_log(record); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn flush(&self) {} |
||||
|
} |
||||
|
|
||||
|
impl DumpLogger { |
||||
|
fn do_dump_log(&self, record: &log::Record) { |
||||
|
let timestamp: chrono::DateTime<chrono::Local> = chrono::Local::now(); |
||||
|
let msg = format!( |
||||
|
"[{} {:<5} {}] - {}", |
||||
|
timestamp.format("%Y-%m-%d %H:%M:%S"), |
||||
|
record.level(), |
||||
|
record.module_path().unwrap_or(""), |
||||
|
record.args() |
||||
|
); |
||||
|
let c_msg = std::ffi::CString::new(msg).unwrap(); |
||||
|
let ptr = c_msg.as_ptr(); |
||||
|
if let Some(cb) = DUMP_CALLBACK.lock().unwrap().clone() { |
||||
|
unsafe { |
||||
|
cb.call(record.level() as c_int, ptr); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
#![cfg(target_os = "ios")] |
||||
|
|
||||
|
use crate::{ |
||||
|
args::{ArgDns, ArgProxy}, |
||||
|
ArgVerbosity, Args, |
||||
|
}; |
||||
|
use std::os::raw::{c_char, c_int, c_uint}; |
||||
|
|
||||
|
/// # Safety
|
||||
|
///
|
||||
|
/// Run the tun2proxy component with some arguments.
|
||||
|
#[no_mangle] |
||||
|
pub unsafe extern "C" fn tun2proxy_run( |
||||
|
proxy_url: *const c_char, |
||||
|
tun_fd: c_int, |
||||
|
tun_mtu: c_uint, |
||||
|
dns_over_tcp: c_char, |
||||
|
verbose: c_char, |
||||
|
) -> c_int { |
||||
|
use log::LevelFilter; |
||||
|
let log_level = if verbose != 0 { LevelFilter::Trace } else { LevelFilter::Info }; |
||||
|
log::set_max_level(log_level); |
||||
|
log::set_boxed_logger(Box::<crate::dump_logger::DumpLogger>::default()).unwrap(); |
||||
|
|
||||
|
let dns = if dns_over_tcp != 0 { ArgDns::OverTcp } else { ArgDns::Direct }; |
||||
|
let verbosity = if verbose != 0 { ArgVerbosity::Trace } else { ArgVerbosity::Info }; |
||||
|
let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); |
||||
|
let proxy = ArgProxy::from_url(proxy_url).unwrap(); |
||||
|
|
||||
|
let args = Args::new(Some(tun_fd), proxy, dns, verbosity); |
||||
|
|
||||
|
crate::api::tun2proxy_internal_run(args, tun_mtu as _) |
||||
|
} |
||||
|
|
||||
|
/// # Safety
|
||||
|
///
|
||||
|
/// Shutdown the tun2proxy component.
|
||||
|
#[no_mangle] |
||||
|
pub unsafe extern "C" fn tun2proxy_stop() -> c_int { |
||||
|
crate::api::tun2proxy_internal_stop() |
||||
|
} |
||||
@ -1,171 +1,472 @@ |
|||||
use crate::{ |
use crate::{ |
||||
error::Error, |
args::ProxyType, |
||||
|
directions::{IncomingDataEvent, IncomingDirection, OutgoingDirection}, |
||||
http::HttpManager, |
http::HttpManager, |
||||
socks::SocksProxyManager, |
session_info::{IpProtocol, SessionInfo}, |
||||
tun2proxy::{ConnectionManager, TunToProxy}, |
virtual_dns::VirtualDns, |
||||
}; |
}; |
||||
use smoltcp::wire::IpCidr; |
pub use clap; |
||||
use socks5_impl::protocol::UserKey; |
use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; |
||||
use std::{ |
use proxy_handler::{ProxyHandler, ProxyHandlerManager}; |
||||
net::{SocketAddr, ToSocketAddrs}, |
use socks::SocksProxyManager; |
||||
rc::Rc, |
use std::{collections::VecDeque, future::Future, net::SocketAddr, pin::Pin, sync::Arc}; |
||||
|
use tokio::{ |
||||
|
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, |
||||
|
net::TcpStream, |
||||
|
sync::{ |
||||
|
mpsc::{error::SendError, Receiver, Sender}, |
||||
|
Mutex, |
||||
|
}, |
||||
|
}; |
||||
|
use tproxy_config::is_private_ip; |
||||
|
use udp_stream::UdpStream; |
||||
|
pub use { |
||||
|
args::{ArgVerbosity, Args}, |
||||
|
error::{Error, Result}, |
||||
}; |
}; |
||||
|
|
||||
mod android; |
mod android; |
||||
|
mod api; |
||||
|
mod args; |
||||
|
mod directions; |
||||
mod dns; |
mod dns; |
||||
pub mod error; |
mod dump_logger; |
||||
|
mod error; |
||||
mod http; |
mod http; |
||||
pub mod setup; |
mod ios; |
||||
|
mod proxy_handler; |
||||
|
mod session_info; |
||||
mod socks; |
mod socks; |
||||
mod tun2proxy; |
mod virtual_dns; |
||||
pub mod util; |
|
||||
mod virtdevice; |
const DNS_PORT: u16 = 53; |
||||
mod virtdns; |
|
||||
#[cfg(target_os = "windows")] |
const MAX_SESSIONS: u64 = 200; |
||||
mod wintuninterface; |
|
||||
|
static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); |
||||
#[derive(Clone, Debug)] |
use std::sync::atomic::Ordering::Relaxed; |
||||
pub struct Proxy { |
|
||||
pub proxy_type: ProxyType, |
pub struct Builder<D> { |
||||
pub addr: SocketAddr, |
device: D, |
||||
pub credentials: Option<UserKey>, |
mtu: Option<usize>, |
||||
|
args: Args, |
||||
|
} |
||||
|
|
||||
|
impl<D: AsyncRead + AsyncWrite + Unpin + Send + 'static> Builder<D> { |
||||
|
pub fn new(device: D, args: Args) -> Self { |
||||
|
Builder { device, args, mtu: None } |
||||
|
} |
||||
|
pub fn mtu(mut self, mtu: usize) -> Self { |
||||
|
self.mtu = Some(mtu); |
||||
|
self |
||||
|
} |
||||
|
pub fn build(self) -> Tun2Socks5<impl Future<Output = crate::Result<()>> + Send + 'static> { |
||||
|
let (tx, rx) = tokio::sync::mpsc::channel::<()>(1); |
||||
|
|
||||
|
Tun2Socks5(run(self.device, self.mtu.unwrap_or(1500), self.args, rx), tx) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub struct Tun2Socks5<F: Future>(F, Sender<()>); |
||||
|
|
||||
|
impl<F: Future + Send + 'static> Tun2Socks5<F> |
||||
|
where |
||||
|
F::Output: Send, |
||||
|
{ |
||||
|
pub fn start(self) -> (JoinHandle<F::Output>, Quit) { |
||||
|
let r = tokio::spawn(self.0); |
||||
|
(JoinHandle(r), Quit(self.1)) |
||||
|
} |
||||
} |
} |
||||
|
|
||||
pub enum NetworkInterface { |
pub struct Quit(Sender<()>); |
||||
Named(String), |
|
||||
#[cfg(target_family = "unix")] |
impl Quit { |
||||
Fd(std::os::fd::RawFd), |
pub async fn trigger(&self) -> Result<(), SendError<()>> { |
||||
|
self.0.send(()).await |
||||
|
} |
||||
} |
} |
||||
|
|
||||
impl Proxy { |
#[repr(transparent)] |
||||
pub fn from_url(s: &str) -> Result<Proxy, Error> { |
struct TokioJoinError(tokio::task::JoinError); |
||||
let e = format!("`{s}` is not a valid proxy URL"); |
|
||||
let url = url::Url::parse(s).map_err(|_| Error::from(&e))?; |
|
||||
let e = format!("`{s}` does not contain a host"); |
|
||||
let host = url.host_str().ok_or(Error::from(e))?; |
|
||||
|
|
||||
let mut url_host = String::from(host); |
impl From<TokioJoinError> for crate::Result<()> { |
||||
let e = format!("`{s}` does not contain a port"); |
fn from(value: TokioJoinError) -> Self { |
||||
let port = url.port().ok_or(Error::from(&e))?; |
Err(crate::Error::Io(value.0.into())) |
||||
url_host.push(':'); |
} |
||||
url_host.push_str(port.to_string().as_str()); |
} |
||||
|
|
||||
let e = format!("`{host}` could not be resolved"); |
pub struct JoinHandle<R>(tokio::task::JoinHandle<R>); |
||||
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"); |
impl<R: From<TokioJoinError>> Future for JoinHandle<R> { |
||||
let addr = addr_iter.next().ok_or(Error::from(&e))?; |
type Output = R; |
||||
|
|
||||
let credentials = if url.username() == "" && url.password().is_none() { |
fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> { |
||||
None |
match std::task::ready!(Pin::new(&mut self.0).poll(cx)) { |
||||
|
Ok(r) => std::task::Poll::Ready(r), |
||||
|
Err(e) => std::task::Poll::Ready(TokioJoinError(e).into()), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub async fn run<D>(device: D, mtu: usize, args: Args, mut quit: Receiver<()>) -> crate::Result<()> |
||||
|
where |
||||
|
D: AsyncRead + AsyncWrite + Unpin + Send + 'static, |
||||
|
{ |
||||
|
let server_addr = args.proxy.addr; |
||||
|
let key = args.proxy.credentials.clone(); |
||||
|
let dns_addr = args.dns_addr; |
||||
|
let ipv6_enabled = args.ipv6_enabled; |
||||
|
let virtual_dns = if args.dns == args::ArgDns::Virtual { |
||||
|
Some(Arc::new(Mutex::new(VirtualDns::new()))) |
||||
} else { |
} else { |
||||
let username = String::from(url.username()); |
None |
||||
let password = String::from(url.password().unwrap_or("")); |
|
||||
Some(UserKey::new(username, password)) |
|
||||
}; |
}; |
||||
|
|
||||
let scheme = url.scheme(); |
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>, |
||||
|
}; |
||||
|
|
||||
|
let mut ipstack_config = ipstack::IpStackConfig::default(); |
||||
|
ipstack_config.mtu(mtu as _); |
||||
|
ipstack_config.tcp_timeout(std::time::Duration::from_secs(600)); // 10 minutes
|
||||
|
ipstack_config.udp_timeout(std::time::Duration::from_secs(10)); // 10 seconds
|
||||
|
|
||||
|
let mut ip_stack = ipstack::IpStack::new(ipstack_config, device); |
||||
|
|
||||
let proxy_type = match url.scheme().to_ascii_lowercase().as_str() { |
loop { |
||||
"socks4" => Some(ProxyType::Socks4), |
let virtual_dns = virtual_dns.clone(); |
||||
"socks5" => Some(ProxyType::Socks5), |
let ip_stack_stream = tokio::select! { |
||||
"http" => Some(ProxyType::Http), |
_ = quit.recv() => { |
||||
_ => None, |
log::info!(""); |
||||
|
log::info!("Ctrl-C recieved, exiting..."); |
||||
|
break; |
||||
} |
} |
||||
.ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?; |
ip_stack_stream = ip_stack.accept() => { |
||||
|
ip_stack_stream? |
||||
|
} |
||||
|
}; |
||||
|
match ip_stack_stream { |
||||
|
IpStackStream::Tcp(tcp) => { |
||||
|
if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { |
||||
|
log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); |
||||
|
continue; |
||||
|
} |
||||
|
log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); |
||||
|
let info = SessionInfo::new(tcp.local_addr(), tcp.peer_addr(), IpProtocol::Tcp); |
||||
|
let domain_name = if let Some(virtual_dns) = &virtual_dns { |
||||
|
let mut virtual_dns = virtual_dns.lock().await; |
||||
|
virtual_dns.touch_ip(&tcp.peer_addr().ip()); |
||||
|
virtual_dns.resolve_ip(&tcp.peer_addr().ip()).cloned() |
||||
|
} else { |
||||
|
None |
||||
|
}; |
||||
|
let proxy_handler = mgr.new_proxy_handler(info, domain_name, false).await?; |
||||
|
tokio::spawn(async move { |
||||
|
if let Err(err) = handle_tcp_session(tcp, server_addr, proxy_handler).await { |
||||
|
log::error!("{} error \"{}\"", info, err); |
||||
|
} |
||||
|
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); |
||||
|
}); |
||||
|
} |
||||
|
IpStackStream::Udp(udp) => { |
||||
|
if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { |
||||
|
log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); |
||||
|
continue; |
||||
|
} |
||||
|
log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); |
||||
|
let mut info = SessionInfo::new(udp.local_addr(), udp.peer_addr(), IpProtocol::Udp); |
||||
|
if info.dst.port() == DNS_PORT { |
||||
|
if is_private_ip(info.dst.ip()) { |
||||
|
info.dst.set_ip(dns_addr); |
||||
|
} |
||||
|
if args.dns == args::ArgDns::OverTcp { |
||||
|
let proxy_handler = mgr.new_proxy_handler(info, None, false).await?; |
||||
|
tokio::spawn(async move { |
||||
|
if let Err(err) = handle_dns_over_tcp_session(udp, server_addr, proxy_handler, ipv6_enabled).await { |
||||
|
log::error!("{} error \"{}\"", info, err); |
||||
|
} |
||||
|
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); |
||||
|
}); |
||||
|
continue; |
||||
|
} |
||||
|
if args.dns == args::ArgDns::Virtual { |
||||
|
tokio::spawn(async move { |
||||
|
if let Some(virtual_dns) = virtual_dns { |
||||
|
if let Err(err) = handle_virtual_dns_session(udp, virtual_dns).await { |
||||
|
log::error!("{} error \"{}\"", info, err); |
||||
|
} |
||||
|
} |
||||
|
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); |
||||
|
}); |
||||
|
continue; |
||||
|
} |
||||
|
assert_eq!(args.dns, args::ArgDns::Direct); |
||||
|
} |
||||
|
let domain_name = if let Some(virtual_dns) = &virtual_dns { |
||||
|
let mut virtual_dns = virtual_dns.lock().await; |
||||
|
virtual_dns.touch_ip(&udp.peer_addr().ip()); |
||||
|
virtual_dns.resolve_ip(&udp.peer_addr().ip()).cloned() |
||||
|
} else { |
||||
|
None |
||||
|
}; |
||||
|
let proxy_handler = mgr.new_proxy_handler(info, domain_name, true).await?; |
||||
|
tokio::spawn(async move { |
||||
|
if let Err(err) = handle_udp_associate_session(udp, server_addr, proxy_handler, ipv6_enabled).await { |
||||
|
log::error!("{} error \"{}\"", info, err); |
||||
|
} |
||||
|
log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); |
||||
|
}); |
||||
|
} |
||||
|
_ => { |
||||
|
log::trace!("Unknown transport"); |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
Ok(()) |
||||
|
} |
||||
|
|
||||
Ok(Proxy { |
async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc<Mutex<VirtualDns>>) -> crate::Result<()> { |
||||
proxy_type, |
let mut buf = [0_u8; 4096]; |
||||
addr, |
loop { |
||||
credentials, |
let len = udp.read(&mut buf).await?; |
||||
}) |
if len == 0 { |
||||
|
break; |
||||
} |
} |
||||
|
let (msg, qname, ip) = dns.lock().await.generate_query(&buf[..len])?; |
||||
|
udp.write_all(&msg).await?; |
||||
|
log::debug!("Virtual DNS query: {} -> {}", qname, ip); |
||||
|
} |
||||
|
Ok(()) |
||||
} |
} |
||||
|
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] |
async fn handle_tcp_session( |
||||
pub enum ProxyType { |
tcp_stack: IpStackTcpStream, |
||||
Socks4, |
server_addr: SocketAddr, |
||||
Socks5, |
proxy_handler: Arc<Mutex<dyn ProxyHandler>>, |
||||
Http, |
) -> crate::Result<()> { |
||||
|
let mut server = TcpStream::connect(server_addr).await?; |
||||
|
|
||||
|
let session_info = proxy_handler.lock().await.get_session_info(); |
||||
|
log::info!("Beginning {}", session_info); |
||||
|
|
||||
|
let _ = handle_proxy_session(&mut server, proxy_handler).await?; |
||||
|
|
||||
|
let (mut t_rx, mut t_tx) = tokio::io::split(tcp_stack); |
||||
|
let (mut s_rx, mut s_tx) = tokio::io::split(server); |
||||
|
|
||||
|
let result = tokio::join! { |
||||
|
tokio::io::copy(&mut t_rx, &mut s_tx), |
||||
|
tokio::io::copy(&mut s_rx, &mut t_tx), |
||||
|
}; |
||||
|
let result = match result { |
||||
|
(Ok(t), Ok(s)) => Ok((t, s)), |
||||
|
(Err(e), _) | (_, Err(e)) => Err(e), |
||||
|
}; |
||||
|
|
||||
|
log::info!("Ending {} with {:?}", session_info, result); |
||||
|
|
||||
|
Ok(()) |
||||
} |
} |
||||
|
|
||||
impl std::fmt::Display for ProxyType { |
async fn handle_udp_associate_session( |
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
mut udp_stack: IpStackUdpStream, |
||||
match self { |
server_addr: SocketAddr, |
||||
ProxyType::Socks4 => write!(f, "socks4"), |
proxy_handler: Arc<Mutex<dyn ProxyHandler>>, |
||||
ProxyType::Socks5 => write!(f, "socks5"), |
ipv6_enabled: bool, |
||||
ProxyType::Http => write!(f, "http"), |
) -> crate::Result<()> { |
||||
|
use socks5_impl::protocol::{Address, StreamOperation, UdpHeader}; |
||||
|
let mut server = TcpStream::connect(server_addr).await?; |
||||
|
let session_info = proxy_handler.lock().await.get_session_info(); |
||||
|
let domain_name = proxy_handler.lock().await.get_domain_name(); |
||||
|
log::info!("Beginning {}", session_info); |
||||
|
|
||||
|
let udp_addr = handle_proxy_session(&mut server, proxy_handler).await?; |
||||
|
let udp_addr = udp_addr.ok_or("udp associate failed")?; |
||||
|
|
||||
|
let mut udp_server = UdpStream::connect(udp_addr).await?; |
||||
|
|
||||
|
let mut buf1 = [0_u8; 4096]; |
||||
|
let mut buf2 = [0_u8; 4096]; |
||||
|
loop { |
||||
|
tokio::select! { |
||||
|
len = udp_stack.read(&mut buf1) => { |
||||
|
let len = len?; |
||||
|
if len == 0 { |
||||
|
break; |
||||
|
} |
||||
|
let buf1 = &buf1[..len]; |
||||
|
|
||||
|
let s5addr = if let Some(domain_name) = &domain_name { |
||||
|
Address::DomainAddress(domain_name.clone(), session_info.dst.port()) |
||||
|
} else { |
||||
|
session_info.dst.into() |
||||
|
}; |
||||
|
|
||||
|
// Add SOCKS5 UDP header to the incoming data
|
||||
|
let mut s5_udp_data = Vec::<u8>::new(); |
||||
|
UdpHeader::new(0, s5addr).write_to_stream(&mut s5_udp_data)?; |
||||
|
s5_udp_data.extend_from_slice(buf1); |
||||
|
|
||||
|
udp_server.write_all(&s5_udp_data).await?; |
||||
|
} |
||||
|
len = udp_server.read(&mut buf2) => { |
||||
|
let len = len?; |
||||
|
if len == 0 { |
||||
|
break; |
||||
|
} |
||||
|
let buf2 = &buf2[..len]; |
||||
|
|
||||
|
// Remove SOCKS5 UDP header from the server data
|
||||
|
let header = UdpHeader::retrieve_from_stream(&mut &buf2[..])?; |
||||
|
let data = &buf2[header.len()..]; |
||||
|
|
||||
|
let buf = if session_info.dst.port() == DNS_PORT { |
||||
|
let mut message = dns::parse_data_to_dns_message(data, false)?; |
||||
|
if !ipv6_enabled { |
||||
|
dns::remove_ipv6_entries(&mut message); |
||||
|
} |
||||
|
message.to_vec()? |
||||
|
} else { |
||||
|
data.to_vec() |
||||
|
}; |
||||
|
|
||||
|
udp_stack.write_all(&buf).await?; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
log::info!("Ending {}", session_info); |
||||
|
|
||||
|
Ok(()) |
||||
} |
} |
||||
|
|
||||
#[derive(Default)] |
async fn handle_dns_over_tcp_session( |
||||
pub struct Options { |
mut udp_stack: IpStackUdpStream, |
||||
virtual_dns: Option<virtdns::VirtualDns>, |
server_addr: SocketAddr, |
||||
mtu: Option<usize>, |
proxy_handler: Arc<Mutex<dyn ProxyHandler>>, |
||||
dns_over_tcp: bool, |
|
||||
dns_addr: Option<std::net::IpAddr>, |
|
||||
ipv6_enabled: bool, |
ipv6_enabled: bool, |
||||
pub setup: bool, |
) -> crate::Result<()> { |
||||
bypass: Vec<IpCidr>, |
let mut server = TcpStream::connect(server_addr).await?; |
||||
} |
|
||||
|
|
||||
impl Options { |
let session_info = proxy_handler.lock().await.get_session_info(); |
||||
pub fn new() -> Self { |
log::info!("Beginning {}", session_info); |
||||
Options::default() |
|
||||
} |
|
||||
|
|
||||
pub fn with_virtual_dns(mut self) -> Self { |
let _ = handle_proxy_session(&mut server, proxy_handler).await?; |
||||
self.virtual_dns = Some(virtdns::VirtualDns::new()); |
|
||||
self.dns_over_tcp = false; |
let mut buf1 = [0_u8; 4096]; |
||||
self |
let mut buf2 = [0_u8; 4096]; |
||||
|
loop { |
||||
|
tokio::select! { |
||||
|
len = udp_stack.read(&mut buf1) => { |
||||
|
let len = len?; |
||||
|
if len == 0 { |
||||
|
break; |
||||
} |
} |
||||
|
let buf1 = &buf1[..len]; |
||||
|
|
||||
pub fn with_dns_over_tcp(mut self) -> Self { |
_ = dns::parse_data_to_dns_message(buf1, false)?; |
||||
self.dns_over_tcp = true; |
|
||||
self.virtual_dns = None; |
// Insert the DNS message length in front of the payload
|
||||
self |
let len = u16::try_from(buf1.len())?; |
||||
|
let mut buf = Vec::with_capacity(std::mem::size_of::<u16>() + usize::from(len)); |
||||
|
buf.extend_from_slice(&len.to_be_bytes()); |
||||
|
buf.extend_from_slice(buf1); |
||||
|
|
||||
|
server.write_all(&buf).await?; |
||||
|
} |
||||
|
len = server.read(&mut buf2) => { |
||||
|
let len = len?; |
||||
|
if len == 0 { |
||||
|
break; |
||||
} |
} |
||||
|
let mut buf = buf2[..len].to_vec(); |
||||
|
|
||||
pub fn with_dns_addr(mut self, addr: Option<std::net::IpAddr>) -> Self { |
let mut to_send: VecDeque<Vec<u8>> = VecDeque::new(); |
||||
self.dns_addr = addr; |
loop { |
||||
self |
if buf.len() < 2 { |
||||
|
break; |
||||
|
} |
||||
|
let len = u16::from_be_bytes([buf[0], buf[1]]) as usize; |
||||
|
if buf.len() < len + 2 { |
||||
|
break; |
||||
} |
} |
||||
|
|
||||
pub fn with_ipv6_enabled(mut self) -> Self { |
// remove the length field
|
||||
self.ipv6_enabled = true; |
let data = buf[2..len + 2].to_vec(); |
||||
self |
|
||||
|
let mut message = dns::parse_data_to_dns_message(&data, false)?; |
||||
|
|
||||
|
let name = dns::extract_domain_from_dns_message(&message)?; |
||||
|
let ip = dns::extract_ipaddr_from_dns_message(&message); |
||||
|
log::trace!("DNS over TCP query result: {} -> {:?}", name, ip); |
||||
|
|
||||
|
if !ipv6_enabled { |
||||
|
dns::remove_ipv6_entries(&mut message); |
||||
} |
} |
||||
|
|
||||
pub fn with_mtu(mut self, mtu: usize) -> Self { |
to_send.push_back(message.to_vec()?); |
||||
self.mtu = Some(mtu); |
if len + 2 == buf.len() { |
||||
self |
break; |
||||
|
} |
||||
|
buf = buf[len + 2..].to_vec(); |
||||
} |
} |
||||
|
|
||||
pub fn with_bypass_ips<'a>(mut self, bypass_ips: impl IntoIterator<Item = &'a IpCidr>) -> Self { |
while let Some(packet) = to_send.pop_front() { |
||||
for bypass_ip in bypass_ips { |
udp_stack.write_all(&packet).await?; |
||||
self.bypass.push(*bypass_ip); |
} |
||||
|
} |
||||
} |
} |
||||
self |
|
||||
} |
} |
||||
|
|
||||
|
log::info!("Ending {}", session_info); |
||||
|
|
||||
|
Ok(()) |
||||
} |
} |
||||
|
|
||||
pub fn tun_to_proxy<'a>(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result<TunToProxy<'a>, Error> { |
async fn handle_proxy_session(server: &mut TcpStream, proxy_handler: Arc<Mutex<dyn ProxyHandler>>) -> crate::Result<Option<SocketAddr>> { |
||||
let mut ttp = TunToProxy::new(interface, options)?; |
let mut launched = false; |
||||
let credentials = proxy.credentials.clone(); |
let mut proxy_handler = proxy_handler.lock().await; |
||||
let server = proxy.addr; |
let dir = OutgoingDirection::ToServer; |
||||
use socks5_impl::protocol::Version::{V4, V5}; |
|
||||
let mgr = match proxy.proxy_type { |
loop { |
||||
ProxyType::Socks4 => Rc::new(SocksProxyManager::new(server, V4, credentials)) as Rc<dyn ConnectionManager>, |
if proxy_handler.connection_established() { |
||||
ProxyType::Socks5 => Rc::new(SocksProxyManager::new(server, V5, credentials)) as Rc<dyn ConnectionManager>, |
break; |
||||
ProxyType::Http => Rc::new(HttpManager::new(server, credentials)) as Rc<dyn ConnectionManager>, |
} |
||||
|
|
||||
|
if !launched { |
||||
|
let data = proxy_handler.peek_data(dir).buffer; |
||||
|
let len = data.len(); |
||||
|
if len == 0 { |
||||
|
return Err("proxy_handler launched went wrong".into()); |
||||
|
} |
||||
|
server.write_all(data).await?; |
||||
|
proxy_handler.consume_data(dir, len); |
||||
|
|
||||
|
launched = true; |
||||
|
} |
||||
|
|
||||
|
let mut buf = [0_u8; 4096]; |
||||
|
let len = server.read(&mut buf).await?; |
||||
|
if len == 0 { |
||||
|
return Err("server closed accidentially".into()); |
||||
|
} |
||||
|
let event = IncomingDataEvent { |
||||
|
direction: IncomingDirection::FromServer, |
||||
|
buffer: &buf[..len], |
||||
}; |
}; |
||||
ttp.set_connection_manager(Some(mgr)); |
proxy_handler.push_data(event).await?; |
||||
Ok(ttp) |
|
||||
} |
|
||||
|
|
||||
pub fn main_entry(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result<(), Error> { |
let data = proxy_handler.peek_data(dir).buffer; |
||||
let mut ttp = tun_to_proxy(interface, proxy, options)?; |
let len = data.len(); |
||||
ttp.run()?; |
if len > 0 { |
||||
Ok(()) |
server.write_all(data).await?; |
||||
|
proxy_handler.consume_data(dir, len); |
||||
|
} |
||||
|
} |
||||
|
Ok(proxy_handler.get_udp_associate()) |
||||
} |
} |
||||
|
|||||
@ -1,156 +0,0 @@ |
|||||
use clap::Parser; |
|
||||
use smoltcp::wire::IpCidr; |
|
||||
use std::{net::IpAddr, process::ExitCode}; |
|
||||
use tun2proxy::util::str_to_cidr; |
|
||||
use tun2proxy::{error::Error, main_entry, NetworkInterface, Options, Proxy}; |
|
||||
|
|
||||
#[cfg(target_os = "linux")] |
|
||||
use tun2proxy::setup::{get_default_cidrs, Setup}; |
|
||||
|
|
||||
/// Tunnel interface to proxy
|
|
||||
#[derive(Parser)] |
|
||||
#[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] |
|
||||
struct Args { |
|
||||
/// Name of the tun interface
|
|
||||
#[arg(short, long, value_name = "name", default_value = "tun0")] |
|
||||
tun: String, |
|
||||
|
|
||||
/// File descriptor of the tun interface
|
|
||||
#[arg(long, value_name = "fd")] |
|
||||
tun_fd: Option<i32>, |
|
||||
|
|
||||
/// MTU of the tun interface (only with tunnel file descriptor)
|
|
||||
#[arg(long, value_name = "mtu", default_value = "1500")] |
|
||||
tun_mtu: usize, |
|
||||
|
|
||||
/// Proxy URL in the form proto://[username[:password]@]host:port
|
|
||||
#[arg(short, long, value_parser = Proxy::from_url, value_name = "URL")] |
|
||||
proxy: Proxy, |
|
||||
|
|
||||
/// DNS handling strategy
|
|
||||
#[arg(short, long, value_name = "strategy", value_enum, default_value = "virtual")] |
|
||||
dns: ArgDns, |
|
||||
|
|
||||
/// DNS resolver address
|
|
||||
#[arg(long, value_name = "IP", default_value = "8.8.8.8")] |
|
||||
dns_addr: IpAddr, |
|
||||
|
|
||||
/// IPv6 enabled
|
|
||||
#[arg(short = '6', long)] |
|
||||
ipv6_enabled: bool, |
|
||||
|
|
||||
/// Routing and system setup
|
|
||||
#[arg(short, long, value_name = "method", value_enum, default_value = if cfg!(target_os = "linux") { "none" } else { "auto" })] |
|
||||
setup: Option<ArgSetup>, |
|
||||
|
|
||||
/// IPs used in routing setup which should bypass the tunnel
|
|
||||
#[arg(short, long, value_name = "IP|CIDR")] |
|
||||
bypass: Vec<String>, |
|
||||
|
|
||||
/// Verbosity level
|
|
||||
#[arg(short, long, value_name = "level", value_enum, default_value = "info")] |
|
||||
verbosity: ArgVerbosity, |
|
||||
} |
|
||||
|
|
||||
/// DNS query handling strategy
|
|
||||
/// - Virtual: Intercept DNS queries and resolve them locally with a fake IP address
|
|
||||
/// - OverTcp: Use TCP to send DNS queries to the DNS server
|
|
||||
/// - Direct: Do not handle DNS by relying on DNS server bypassing
|
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] |
|
||||
enum ArgDns { |
|
||||
Virtual, |
|
||||
OverTcp, |
|
||||
Direct, |
|
||||
} |
|
||||
|
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] |
|
||||
enum ArgSetup { |
|
||||
None, |
|
||||
Auto, |
|
||||
} |
|
||||
|
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] |
|
||||
enum ArgVerbosity { |
|
||||
Off, |
|
||||
Error, |
|
||||
Warn, |
|
||||
Info, |
|
||||
Debug, |
|
||||
Trace, |
|
||||
} |
|
||||
|
|
||||
fn main() -> ExitCode { |
|
||||
dotenvy::dotenv().ok(); |
|
||||
let args = Args::parse(); |
|
||||
|
|
||||
let default = format!("{}={:?}", module_path!(), args.verbosity); |
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); |
|
||||
|
|
||||
let addr = args.proxy.addr; |
|
||||
let proxy_type = args.proxy.proxy_type; |
|
||||
log::info!("Proxy {proxy_type} server: {addr}"); |
|
||||
|
|
||||
let mut options = Options::new(); |
|
||||
match args.dns { |
|
||||
ArgDns::Virtual => { |
|
||||
options = options.with_virtual_dns(); |
|
||||
} |
|
||||
ArgDns::OverTcp => { |
|
||||
options = options.with_dns_over_tcp(); |
|
||||
} |
|
||||
_ => {} |
|
||||
} |
|
||||
|
|
||||
options = options.with_dns_addr(Some(args.dns_addr)); |
|
||||
|
|
||||
if args.ipv6_enabled { |
|
||||
options = options.with_ipv6_enabled(); |
|
||||
} |
|
||||
|
|
||||
#[allow(unused_assignments)] |
|
||||
let interface = match args.tun_fd { |
|
||||
None => NetworkInterface::Named(args.tun.clone()), |
|
||||
Some(_fd) => { |
|
||||
options = options.with_mtu(args.tun_mtu); |
|
||||
#[cfg(not(target_family = "unix"))] |
|
||||
panic!("Not supported file descriptor"); |
|
||||
#[cfg(target_family = "unix")] |
|
||||
NetworkInterface::Fd(_fd) |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
options.setup = args.setup.map(|s| s == ArgSetup::Auto).unwrap_or(false); |
|
||||
|
|
||||
let block = || -> Result<(), Error> { |
|
||||
let mut bypass_ips = Vec::<IpCidr>::new(); |
|
||||
for cidr_str in args.bypass { |
|
||||
bypass_ips.push(str_to_cidr(&cidr_str)?); |
|
||||
} |
|
||||
if bypass_ips.is_empty() { |
|
||||
let prefix_len = if args.proxy.addr.ip().is_ipv6() { 128 } else { 32 }; |
|
||||
bypass_ips.push(IpCidr::new(args.proxy.addr.ip().into(), prefix_len)) |
|
||||
} |
|
||||
|
|
||||
options = options.with_bypass_ips(&bypass_ips); |
|
||||
|
|
||||
#[cfg(target_os = "linux")] |
|
||||
{ |
|
||||
let mut setup: Setup; |
|
||||
if options.setup { |
|
||||
setup = Setup::new(&args.tun, bypass_ips, get_default_cidrs()); |
|
||||
setup.configure()?; |
|
||||
setup.drop_privileges()?; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
main_entry(&interface, &args.proxy, options)?; |
|
||||
|
|
||||
Ok(()) |
|
||||
}; |
|
||||
if let Err(e) = block() { |
|
||||
log::error!("{e}"); |
|
||||
return ExitCode::FAILURE; |
|
||||
} |
|
||||
|
|
||||
ExitCode::SUCCESS |
|
||||
} |
|
||||
@ -0,0 +1,30 @@ |
|||||
|
use crate::{ |
||||
|
directions::{IncomingDataEvent, OutgoingDataEvent, OutgoingDirection}, |
||||
|
session_info::SessionInfo, |
||||
|
}; |
||||
|
use std::{net::SocketAddr, sync::Arc}; |
||||
|
use tokio::sync::Mutex; |
||||
|
|
||||
|
#[async_trait::async_trait] |
||||
|
pub(crate) trait ProxyHandler: Send + Sync { |
||||
|
fn get_session_info(&self) -> SessionInfo; |
||||
|
fn get_domain_name(&self) -> Option<String>; |
||||
|
async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()>; |
||||
|
fn consume_data(&mut self, dir: OutgoingDirection, size: usize); |
||||
|
fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent; |
||||
|
fn connection_established(&self) -> bool; |
||||
|
fn data_len(&self, dir: OutgoingDirection) -> usize; |
||||
|
fn reset_connection(&self) -> bool; |
||||
|
fn get_udp_associate(&self) -> Option<SocketAddr>; |
||||
|
} |
||||
|
|
||||
|
#[async_trait::async_trait] |
||||
|
pub(crate) trait ProxyHandlerManager: Send + Sync { |
||||
|
async fn new_proxy_handler( |
||||
|
&self, |
||||
|
info: SessionInfo, |
||||
|
domain_name: Option<String>, |
||||
|
udp_associate: bool, |
||||
|
) -> std::io::Result<Arc<Mutex<dyn ProxyHandler>>>; |
||||
|
fn get_server_addr(&self) -> SocketAddr; |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
use std::net::{Ipv4Addr, SocketAddr}; |
||||
|
|
||||
|
#[allow(dead_code)] |
||||
|
#[derive(Hash, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)] |
||||
|
pub(crate) enum IpProtocol { |
||||
|
#[default] |
||||
|
Tcp, |
||||
|
Udp, |
||||
|
Icmp, |
||||
|
Other(u8), |
||||
|
} |
||||
|
|
||||
|
impl std::fmt::Display for IpProtocol { |
||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
||||
|
match self { |
||||
|
IpProtocol::Tcp => write!(f, "TCP"), |
||||
|
IpProtocol::Udp => write!(f, "UDP"), |
||||
|
IpProtocol::Icmp => write!(f, "ICMP"), |
||||
|
IpProtocol::Other(v) => write!(f, "Other({})", v), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[derive(Hash, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] |
||||
|
pub(crate) struct SessionInfo { |
||||
|
pub(crate) src: SocketAddr, |
||||
|
pub(crate) dst: SocketAddr, |
||||
|
pub(crate) protocol: IpProtocol, |
||||
|
id: u64, |
||||
|
} |
||||
|
|
||||
|
impl Default for SessionInfo { |
||||
|
fn default() -> Self { |
||||
|
let src = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); |
||||
|
let dst = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0); |
||||
|
Self::new(src, dst, IpProtocol::Tcp) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static SESSION_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); |
||||
|
|
||||
|
impl SessionInfo { |
||||
|
pub fn new(src: SocketAddr, dst: SocketAddr, protocol: IpProtocol) -> Self { |
||||
|
let id = SESSION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); |
||||
|
Self { src, dst, protocol, id } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl std::fmt::Display for SessionInfo { |
||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
||||
|
write!(f, "#{} {} {} -> {}", self.id, self.protocol, self.src, self.dst) |
||||
|
} |
||||
|
} |
||||
@ -1,337 +0,0 @@ |
|||||
#![cfg(target_os = "linux")] |
|
||||
|
|
||||
use crate::error::Error; |
|
||||
use fork::Fork; |
|
||||
use smoltcp::wire::IpCidr; |
|
||||
use std::{ |
|
||||
convert::TryFrom, |
|
||||
ffi::OsStr, |
|
||||
fs, |
|
||||
io::BufRead, |
|
||||
net::{Ipv4Addr, Ipv6Addr}, |
|
||||
os::unix::io::RawFd, |
|
||||
process::{Command, Output}, |
|
||||
str::FromStr, |
|
||||
}; |
|
||||
|
|
||||
#[derive(Clone)] |
|
||||
pub struct Setup { |
|
||||
routes: Vec<IpCidr>, |
|
||||
tunnel_bypass_addrs: Vec<IpCidr>, |
|
||||
tun: String, |
|
||||
set_up: bool, |
|
||||
delete_proxy_routes: Vec<IpCidr>, |
|
||||
child: libc::pid_t, |
|
||||
unmount_resolvconf: bool, |
|
||||
restore_resolvconf_data: Option<Vec<u8>>, |
|
||||
} |
|
||||
|
|
||||
pub fn get_default_cidrs() -> [IpCidr; 4] { |
|
||||
[ |
|
||||
IpCidr::new(Ipv4Addr::from_str("0.0.0.0").unwrap().into(), 1), |
|
||||
IpCidr::new(Ipv4Addr::from_str("128.0.0.0").unwrap().into(), 1), |
|
||||
IpCidr::new(Ipv6Addr::from_str("::").unwrap().into(), 1), |
|
||||
IpCidr::new(Ipv6Addr::from_str("8000::").unwrap().into(), 1), |
|
||||
] |
|
||||
} |
|
||||
|
|
||||
fn run_iproute<I, S>(args: I, error: &str, require_success: bool) -> Result<Output, Error> |
|
||||
where |
|
||||
I: IntoIterator<Item = S>, |
|
||||
S: AsRef<OsStr>, |
|
||||
{ |
|
||||
let mut command = Command::new(""); |
|
||||
for (i, arg) in args.into_iter().enumerate() { |
|
||||
if i == 0 { |
|
||||
command = Command::new(arg); |
|
||||
} else { |
|
||||
command.arg(arg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let e = Error::from(error); |
|
||||
let output = command.output().map_err(|_| e)?; |
|
||||
if !require_success || output.status.success() { |
|
||||
Ok(output) |
|
||||
} else { |
|
||||
let mut args: Vec<&str> = command.get_args().map(|x| x.to_str().unwrap()).collect(); |
|
||||
let program = command.get_program().to_str().unwrap(); |
|
||||
let mut cmdline = Vec::<&str>::new(); |
|
||||
cmdline.push(program); |
|
||||
cmdline.append(&mut args); |
|
||||
let command = cmdline.as_slice().join(" "); |
|
||||
match String::from_utf8(output.stderr.clone()) { |
|
||||
Ok(output) => Err(format!("[{}] Command `{}` failed: {}", nix::unistd::getpid(), command, output).into()), |
|
||||
Err(_) => Err(format!("Command `{:?}` failed with exit code {}", command, output.status.code().unwrap()).into()), |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Setup { |
|
||||
pub fn new( |
|
||||
tun: impl Into<String>, |
|
||||
tunnel_bypass_addrs: impl IntoIterator<Item = IpCidr>, |
|
||||
routes: impl IntoIterator<Item = IpCidr>, |
|
||||
) -> Self { |
|
||||
let routes_cidr = routes.into_iter().collect(); |
|
||||
let bypass_cidrs = tunnel_bypass_addrs.into_iter().collect(); |
|
||||
Self { |
|
||||
tun: tun.into(), |
|
||||
tunnel_bypass_addrs: bypass_cidrs, |
|
||||
routes: routes_cidr, |
|
||||
set_up: false, |
|
||||
delete_proxy_routes: Vec::<IpCidr>::new(), |
|
||||
child: 0, |
|
||||
unmount_resolvconf: false, |
|
||||
restore_resolvconf_data: None, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fn bypass_cidr(cidr: &IpCidr) -> Result<bool, Error> { |
|
||||
let is_ipv6 = match cidr { |
|
||||
IpCidr::Ipv4(_) => false, |
|
||||
IpCidr::Ipv6(_) => true, |
|
||||
}; |
|
||||
let route_show_args = if is_ipv6 { |
|
||||
["ip", "-6", "route", "show"] |
|
||||
} else { |
|
||||
["ip", "-4", "route", "show"] |
|
||||
}; |
|
||||
|
|
||||
let routes = run_iproute(route_show_args, "failed to get routing table through the ip command", true)?; |
|
||||
|
|
||||
let mut route_info = Vec::<(IpCidr, Vec<String>)>::new(); |
|
||||
for line in routes.stdout.lines() { |
|
||||
if line.is_err() { |
|
||||
break; |
|
||||
} |
|
||||
let line = line.unwrap(); |
|
||||
if line.starts_with([' ', '\t']) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
let mut split = line.split_whitespace(); |
|
||||
let mut dst_str = split.next().unwrap(); |
|
||||
if dst_str == "default" { |
|
||||
dst_str = if is_ipv6 { "::/0" } else { "0.0.0.0/0" } |
|
||||
} |
|
||||
|
|
||||
let (addr_str, prefix_len_str) = match dst_str.split_once(['/']) { |
|
||||
None => (dst_str, if is_ipv6 { "128" } else { "32" }), |
|
||||
Some((addr_str, prefix_len_str)) => (addr_str, prefix_len_str), |
|
||||
}; |
|
||||
|
|
||||
let cidr: IpCidr = IpCidr::new( |
|
||||
std::net::IpAddr::from_str(addr_str).unwrap().into(), |
|
||||
u8::from_str(prefix_len_str).unwrap(), |
|
||||
); |
|
||||
let route_components: Vec<String> = split.map(String::from).collect(); |
|
||||
route_info.push((cidr, route_components)) |
|
||||
} |
|
||||
|
|
||||
// Sort routes by prefix length, the most specific route comes first.
|
|
||||
route_info.sort_by(|entry1, entry2| entry2.0.prefix_len().cmp(&entry1.0.prefix_len())); |
|
||||
|
|
||||
for (route_cidr, route_components) in route_info { |
|
||||
if !route_cidr.contains_subnet(cidr) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
// The IP address is routed through a more specific route than the default route.
|
|
||||
// In this case, there is nothing to do.
|
|
||||
if route_cidr.prefix_len() != 0 { |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
let mut proxy_route = vec!["ip".into(), "route".into(), "add".into()]; |
|
||||
proxy_route.push(cidr.to_string()); |
|
||||
proxy_route.extend(route_components.into_iter()); |
|
||||
run_iproute(proxy_route, "failed to clone route for proxy", false)?; |
|
||||
return Ok(true); |
|
||||
} |
|
||||
Ok(false) |
|
||||
} |
|
||||
|
|
||||
fn write_buffer_to_fd(fd: RawFd, data: &[u8]) -> Result<(), Error> { |
|
||||
let mut written = 0; |
|
||||
loop { |
|
||||
if written >= data.len() { |
|
||||
break; |
|
||||
} |
|
||||
written += nix::unistd::write(fd, &data[written..])?; |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn write_nameserver(fd: RawFd) -> Result<(), Error> { |
|
||||
let data = "nameserver 198.18.0.1\n".as_bytes(); |
|
||||
Self::write_buffer_to_fd(fd, data)?; |
|
||||
nix::sys::stat::fchmod(fd, nix::sys::stat::Mode::from_bits(0o444).unwrap())?; |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn setup_resolv_conf(&mut self) -> Result<(), Error> { |
|
||||
let mut fd = nix::fcntl::open( |
|
||||
"/tmp/tun2proxy-resolv.conf", |
|
||||
nix::fcntl::OFlag::O_RDWR | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT, |
|
||||
nix::sys::stat::Mode::from_bits(0o644).unwrap(), |
|
||||
)?; |
|
||||
Self::write_nameserver(fd)?; |
|
||||
let source = format!("/proc/self/fd/{}", fd); |
|
||||
if Ok(()) |
|
||||
!= nix::mount::mount( |
|
||||
source.as_str().into(), |
|
||||
"/etc/resolv.conf", |
|
||||
"".into(), |
|
||||
nix::mount::MsFlags::MS_BIND, |
|
||||
"".into(), |
|
||||
) |
|
||||
{ |
|
||||
log::warn!("failed to bind mount custom resolv.conf onto /etc/resolv.conf, resorting to direct write"); |
|
||||
nix::unistd::close(fd)?; |
|
||||
|
|
||||
self.restore_resolvconf_data = Some(fs::read("/etc/resolv.conf")?); |
|
||||
|
|
||||
fd = nix::fcntl::open( |
|
||||
"/etc/resolv.conf", |
|
||||
nix::fcntl::OFlag::O_WRONLY | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_TRUNC, |
|
||||
nix::sys::stat::Mode::from_bits(0o644).unwrap(), |
|
||||
)?; |
|
||||
Self::write_nameserver(fd)?; |
|
||||
} else { |
|
||||
self.unmount_resolvconf = true; |
|
||||
} |
|
||||
nix::unistd::close(fd)?; |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn add_tunnel_routes(&self) -> Result<(), Error> { |
|
||||
for route in &self.routes { |
|
||||
run_iproute( |
|
||||
["ip", "route", "add", route.to_string().as_str(), "dev", self.tun.as_str()], |
|
||||
"failed to add route", |
|
||||
true, |
|
||||
)?; |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn shutdown(&mut self) -> Result<(), Error> { |
|
||||
self.set_up = false; |
|
||||
log::info!("[{}] Restoring network configuration", nix::unistd::getpid()); |
|
||||
let _ = Command::new("ip").args(["link", "del", self.tun.as_str()]).output(); |
|
||||
|
|
||||
for cidr in &self.delete_proxy_routes { |
|
||||
let _ = Command::new("ip").args(["route", "del", cidr.to_string().as_str()]).output(); |
|
||||
} |
|
||||
|
|
||||
if self.unmount_resolvconf { |
|
||||
nix::mount::umount("/etc/resolv.conf")?; |
|
||||
} |
|
||||
|
|
||||
if let Some(data) = &self.restore_resolvconf_data { |
|
||||
fs::write("/etc/resolv.conf", data)?; |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn setup_and_handle_signals(&mut self, read_from_child: RawFd, write_to_parent: RawFd) { |
|
||||
if let Err(e) = (|| -> Result<(), Error> { |
|
||||
nix::unistd::close(read_from_child)?; |
|
||||
run_iproute( |
|
||||
["ip", "tuntap", "add", "name", self.tun.as_str(), "mode", "tun"], |
|
||||
"failed to create tunnel device", |
|
||||
true, |
|
||||
)?; |
|
||||
|
|
||||
self.set_up = true; |
|
||||
|
|
||||
run_iproute( |
|
||||
["ip", "link", "set", self.tun.as_str(), "up"], |
|
||||
"failed to bring up tunnel device", |
|
||||
true, |
|
||||
)?; |
|
||||
|
|
||||
let mut delete_proxy_route = Vec::<IpCidr>::new(); |
|
||||
for cidr in &self.tunnel_bypass_addrs { |
|
||||
if Self::bypass_cidr(cidr)? { |
|
||||
delete_proxy_route.push(*cidr); |
|
||||
} |
|
||||
} |
|
||||
self.delete_proxy_routes = delete_proxy_route; |
|
||||
self.setup_resolv_conf()?; |
|
||||
self.add_tunnel_routes()?; |
|
||||
|
|
||||
// Signal to child that we are done setting up everything.
|
|
||||
if nix::unistd::write(write_to_parent, &[1])? != 1 { |
|
||||
return Err("Failed to write to pipe".into()); |
|
||||
} |
|
||||
nix::unistd::close(write_to_parent)?; |
|
||||
|
|
||||
// Now wait for the termination signals.
|
|
||||
let mut mask = nix::sys::signal::SigSet::empty(); |
|
||||
mask.add(nix::sys::signal::SIGINT); |
|
||||
mask.add(nix::sys::signal::SIGTERM); |
|
||||
mask.add(nix::sys::signal::SIGQUIT); |
|
||||
mask.thread_block().unwrap(); |
|
||||
|
|
||||
let mut fd = nix::sys::signalfd::SignalFd::new(&mask).unwrap(); |
|
||||
loop { |
|
||||
let res = fd.read_signal().unwrap().unwrap(); |
|
||||
let signo = nix::sys::signal::Signal::try_from(res.ssi_signo as i32).unwrap(); |
|
||||
if signo == nix::sys::signal::SIGINT || signo == nix::sys::signal::SIGTERM || signo == nix::sys::signal::SIGQUIT { |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
self.shutdown()?; |
|
||||
Ok(()) |
|
||||
})() { |
|
||||
log::error!("{e}"); |
|
||||
self.shutdown().unwrap(); |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
pub fn drop_privileges(&self) -> Result<(), Error> { |
|
||||
// 65534 is usually the nobody user. Even in cases it is not, it is safer to use this ID
|
|
||||
// than running with UID and GID 0.
|
|
||||
nix::unistd::setgid(nix::unistd::Gid::from_raw(65534))?; |
|
||||
nix::unistd::setuid(nix::unistd::Uid::from_raw(65534))?; |
|
||||
|
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
pub fn configure(&mut self) -> Result<(), Error> { |
|
||||
log::info!("[{}] Setting up network configuration", nix::unistd::getpid()); |
|
||||
if nix::unistd::getuid() != 0.into() { |
|
||||
return Err("Automatic setup requires root privileges".into()); |
|
||||
} |
|
||||
|
|
||||
let (read_from_child, write_to_parent) = nix::unistd::pipe()?; |
|
||||
match fork::fork() { |
|
||||
Ok(Fork::Child) => { |
|
||||
prctl::set_death_signal(nix::sys::signal::SIGINT as isize).unwrap(); |
|
||||
self.setup_and_handle_signals(read_from_child, write_to_parent); |
|
||||
std::process::exit(0); |
|
||||
} |
|
||||
Ok(Fork::Parent(child)) => { |
|
||||
self.child = child; |
|
||||
nix::unistd::close(write_to_parent)?; |
|
||||
let mut buf = [0]; |
|
||||
if nix::unistd::read(read_from_child, &mut buf)? != 1 { |
|
||||
return Err("Failed to read from pipe".into()); |
|
||||
} |
|
||||
nix::unistd::close(read_from_child)?; |
|
||||
|
|
||||
Ok(()) |
|
||||
} |
|
||||
_ => Err("Failed to fork".into()), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub fn restore(&mut self) -> Result<(), Error> { |
|
||||
nix::sys::signal::kill(nix::unistd::Pid::from_raw(self.child), nix::sys::signal::SIGINT)?; |
|
||||
nix::sys::wait::waitpid(nix::unistd::Pid::from_raw(self.child), None)?; |
|
||||
Ok(()) |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
@ -1,22 +0,0 @@ |
|||||
use crate::error::Error; |
|
||||
use smoltcp::wire::IpCidr; |
|
||||
use std::net::IpAddr; |
|
||||
use std::str::FromStr; |
|
||||
|
|
||||
pub fn str_to_cidr(s: &str) -> Result<IpCidr, Error> { |
|
||||
// IpCidr's FromString implementation requires the netmask to be specified.
|
|
||||
// Try to parse as IP address without netmask before falling back.
|
|
||||
match IpAddr::from_str(s) { |
|
||||
Err(_) => (), |
|
||||
Ok(cidr) => { |
|
||||
let prefix_len = if cidr.is_ipv4() { 32 } else { 128 }; |
|
||||
return Ok(IpCidr::new(cidr.into(), prefix_len)); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
let cidr = IpCidr::from_str(s); |
|
||||
match cidr { |
|
||||
Err(()) => Err("Invalid CIDR: ".into()), |
|
||||
Ok(cidr) => Ok(cidr), |
|
||||
} |
|
||||
} |
|
||||
@ -1,80 +0,0 @@ |
|||||
use smoltcp::{ |
|
||||
phy::{self, Device, DeviceCapabilities}, |
|
||||
time::Instant, |
|
||||
}; |
|
||||
|
|
||||
/// Virtual device representing the remote proxy server.
|
|
||||
#[derive(Default)] |
|
||||
pub struct VirtualTunDevice { |
|
||||
capabilities: DeviceCapabilities, |
|
||||
inbuf: Vec<Vec<u8>>, |
|
||||
outbuf: Vec<Vec<u8>>, |
|
||||
} |
|
||||
|
|
||||
impl VirtualTunDevice { |
|
||||
pub fn inject_packet(&mut self, buffer: &[u8]) { |
|
||||
self.inbuf.push(buffer.to_vec()); |
|
||||
} |
|
||||
|
|
||||
pub fn exfiltrate_packet(&mut self) -> Option<Vec<u8>> { |
|
||||
self.outbuf.pop() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub struct VirtRxToken { |
|
||||
buffer: Vec<u8>, |
|
||||
} |
|
||||
|
|
||||
impl phy::RxToken for VirtRxToken { |
|
||||
fn consume<R, F>(mut self, f: F) -> R |
|
||||
where |
|
||||
F: FnOnce(&mut [u8]) -> R, |
|
||||
{ |
|
||||
f(&mut self.buffer[..]) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub struct VirtTxToken<'a>(&'a mut VirtualTunDevice); |
|
||||
|
|
||||
impl<'a> phy::TxToken for VirtTxToken<'a> { |
|
||||
fn consume<R, F>(self, len: usize, f: F) -> R |
|
||||
where |
|
||||
F: FnOnce(&mut [u8]) -> R, |
|
||||
{ |
|
||||
let mut buffer = vec![0; len]; |
|
||||
let result = f(&mut buffer); |
|
||||
self.0.outbuf.push(buffer); |
|
||||
result |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Device for VirtualTunDevice { |
|
||||
type RxToken<'a> = VirtRxToken; |
|
||||
type TxToken<'a> = VirtTxToken<'a>; |
|
||||
|
|
||||
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { |
|
||||
if let Some(buffer) = self.inbuf.pop() { |
|
||||
let rx = Self::RxToken { buffer }; |
|
||||
let tx = VirtTxToken(self); |
|
||||
return Some((rx, tx)); |
|
||||
} |
|
||||
None |
|
||||
} |
|
||||
|
|
||||
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> { |
|
||||
return Some(VirtTxToken(self)); |
|
||||
} |
|
||||
|
|
||||
fn capabilities(&self) -> DeviceCapabilities { |
|
||||
self.capabilities.clone() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl VirtualTunDevice { |
|
||||
pub fn new(capabilities: DeviceCapabilities) -> Self { |
|
||||
Self { |
|
||||
capabilities, |
|
||||
..VirtualTunDevice::default() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,546 +0,0 @@ |
|||||
use mio::{event, windows::NamedPipe, Interest, Registry, Token}; |
|
||||
use smoltcp::wire::IpCidr; |
|
||||
use smoltcp::{ |
|
||||
phy::{self, Device, DeviceCapabilities, Medium}, |
|
||||
time::Instant, |
|
||||
}; |
|
||||
use std::{ |
|
||||
cell::RefCell, |
|
||||
fs::OpenOptions, |
|
||||
io::{self, Read, Write}, |
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, |
|
||||
os::windows::prelude::{FromRawHandle, IntoRawHandle, OpenOptionsExt}, |
|
||||
rc::Rc, |
|
||||
sync::{Arc, Mutex}, |
|
||||
thread::JoinHandle, |
|
||||
vec::Vec, |
|
||||
}; |
|
||||
use windows::{ |
|
||||
core::{GUID, PWSTR}, |
|
||||
Win32::{ |
|
||||
Foundation::{ERROR_BUFFER_OVERFLOW, WIN32_ERROR}, |
|
||||
NetworkManagement::{ |
|
||||
IpHelper::{ |
|
||||
GetAdaptersAddresses, SetInterfaceDnsSettings, DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, |
|
||||
DNS_SETTING_NAMESERVER, GAA_FLAG_INCLUDE_GATEWAYS, GAA_FLAG_INCLUDE_PREFIX, IF_TYPE_ETHERNET_CSMACD, IF_TYPE_IEEE80211, |
|
||||
IP_ADAPTER_ADDRESSES_LH, |
|
||||
}, |
|
||||
Ndis::IfOperStatusUp, |
|
||||
}, |
|
||||
Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, |
|
||||
Storage::FileSystem::FILE_FLAG_OVERLAPPED, |
|
||||
}, |
|
||||
}; |
|
||||
|
|
||||
fn server() -> io::Result<(NamedPipe, String)> { |
|
||||
use rand::Rng; |
|
||||
let num: u64 = rand::thread_rng().gen(); |
|
||||
let name = format!(r"\\.\pipe\my-pipe-{}", num); |
|
||||
let pipe = NamedPipe::new(&name)?; |
|
||||
Ok((pipe, name)) |
|
||||
} |
|
||||
|
|
||||
fn client(name: &str) -> io::Result<NamedPipe> { |
|
||||
let mut opts = OpenOptions::new(); |
|
||||
opts.read(true).write(true).custom_flags(FILE_FLAG_OVERLAPPED.0); |
|
||||
let file = opts.open(name)?; |
|
||||
unsafe { Ok(NamedPipe::from_raw_handle(file.into_raw_handle())) } |
|
||||
} |
|
||||
|
|
||||
pub(crate) fn pipe() -> io::Result<(NamedPipe, NamedPipe)> { |
|
||||
let (pipe, name) = server()?; |
|
||||
Ok((pipe, client(&name)?)) |
|
||||
} |
|
||||
|
|
||||
/// A virtual TUN (IP) interface.
|
|
||||
pub struct WinTunInterface { |
|
||||
wintun_session: Arc<wintun::Session>, |
|
||||
mtu: usize, |
|
||||
medium: Medium, |
|
||||
pipe_server: Rc<RefCell<NamedPipe>>, |
|
||||
pipe_server_cache: Rc<RefCell<Vec<u8>>>, |
|
||||
pipe_client: Arc<Mutex<NamedPipe>>, |
|
||||
pipe_client_cache: Arc<Mutex<Vec<u8>>>, |
|
||||
wintun_reader_thread: Option<JoinHandle<()>>, |
|
||||
old_gateway: Option<IpAddr>, |
|
||||
} |
|
||||
|
|
||||
impl event::Source for WinTunInterface { |
|
||||
fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { |
|
||||
self.pipe_server.borrow_mut().register(registry, token, interests)?; |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { |
|
||||
self.pipe_server.borrow_mut().reregister(registry, token, interests)?; |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn deregister(&mut self, registry: &Registry) -> io::Result<()> { |
|
||||
self.pipe_server.borrow_mut().deregister(registry)?; |
|
||||
Ok(()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl WinTunInterface { |
|
||||
pub fn new(tun_name: &str, medium: Medium) -> io::Result<WinTunInterface> { |
|
||||
let wintun = unsafe { wintun::load() }.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; |
|
||||
let guid = 324435345345345345_u128; |
|
||||
let adapter = match wintun::Adapter::open(&wintun, tun_name) { |
|
||||
Ok(a) => a, |
|
||||
Err(_) => { |
|
||||
wintun::Adapter::create(&wintun, tun_name, tun_name, Some(guid)).map_err(|e| io::Error::new(io::ErrorKind::Other, e))? |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
let session = adapter |
|
||||
.start_session(wintun::MAX_RING_CAPACITY) |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; |
|
||||
let wintun_session = Arc::new(session); |
|
||||
|
|
||||
let (pipe_server, pipe_client) = pipe()?; |
|
||||
|
|
||||
let pipe_client = Arc::new(Mutex::new(pipe_client)); |
|
||||
let pipe_client_cache = Arc::new(Mutex::new(Vec::new())); |
|
||||
|
|
||||
let mtu = adapter.get_mtu().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; |
|
||||
|
|
||||
let reader_session = wintun_session.clone(); |
|
||||
let pipe_client_clone = pipe_client.clone(); |
|
||||
let pipe_client_cache_clone = pipe_client_cache.clone(); |
|
||||
let reader_thread = std::thread::spawn(move || { |
|
||||
let block = || -> Result<(), Box<dyn std::error::Error>> { |
|
||||
loop { |
|
||||
// Take the old data from pipe_client_cache and append the new data
|
|
||||
let cached_data = pipe_client_cache_clone.lock()?.drain(..).collect::<Vec<u8>>(); |
|
||||
let bytes = if cached_data.len() >= mtu { |
|
||||
// if the cached data is greater than mtu, then sleep 1ms and return the data
|
|
||||
std::thread::sleep(std::time::Duration::from_millis(1)); |
|
||||
cached_data |
|
||||
} else { |
|
||||
// read data from tunnel interface
|
|
||||
let packet = reader_session.receive_blocking()?; |
|
||||
let bytes = packet.bytes().to_vec(); |
|
||||
// and append to the end of cached data
|
|
||||
cached_data.into_iter().chain(bytes).collect::<Vec<u8>>() |
|
||||
}; |
|
||||
|
|
||||
if bytes.is_empty() { |
|
||||
continue; |
|
||||
} |
|
||||
let len = bytes.len(); |
|
||||
|
|
||||
// write data to named pipe_server
|
|
||||
let result = { pipe_client_clone.lock()?.write(&bytes) }; |
|
||||
match result { |
|
||||
Ok(n) => { |
|
||||
if n < len { |
|
||||
log::trace!("Wintun pipe_client write data {} less than buffer {}", n, len); |
|
||||
pipe_client_cache_clone.lock()?.extend_from_slice(&bytes[n..]); |
|
||||
} |
|
||||
} |
|
||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => { |
|
||||
log::trace!("Wintun pipe_client write WouldBlock (1) len {}", len); |
|
||||
pipe_client_cache_clone.lock()?.extend_from_slice(&bytes); |
|
||||
} |
|
||||
Err(err) => log::error!("Wintun pipe_client write data len {} error \"{}\"", len, err), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
if let Err(err) = block() { |
|
||||
log::trace!("Reader {}", err); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
Ok(WinTunInterface { |
|
||||
wintun_session, |
|
||||
mtu, |
|
||||
medium, |
|
||||
pipe_server: Rc::new(RefCell::new(pipe_server)), |
|
||||
pipe_server_cache: Rc::new(RefCell::new(Vec::new())), |
|
||||
pipe_client, |
|
||||
pipe_client_cache, |
|
||||
wintun_reader_thread: Some(reader_thread), |
|
||||
old_gateway: None, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
pub fn pipe_client(&self) -> Arc<Mutex<NamedPipe>> { |
|
||||
self.pipe_client.clone() |
|
||||
} |
|
||||
|
|
||||
pub fn pipe_client_event(&self, event: &event::Event) -> Result<(), io::Error> { |
|
||||
if event.is_readable() { |
|
||||
self.pipe_client_event_readable() |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; |
|
||||
} else if event.is_writable() { |
|
||||
self.pipe_client_event_writable() |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn pipe_client_event_readable(&self) -> Result<(), Box<dyn std::error::Error + '_>> { |
|
||||
let mut reader = self.pipe_client.lock()?; |
|
||||
let mut buffer = vec![0; self.mtu]; |
|
||||
loop { |
|
||||
// some data arieved to pipe_client from pipe_server
|
|
||||
match reader.read(&mut buffer[..]) { |
|
||||
Ok(len) => match self.wintun_session.allocate_send_packet(len as u16) { |
|
||||
Ok(mut write_pack) => { |
|
||||
write_pack.bytes_mut().copy_from_slice(&buffer[..len]); |
|
||||
// write data to tunnel interface
|
|
||||
self.wintun_session.send_packet(write_pack); |
|
||||
} |
|
||||
Err(err) => { |
|
||||
log::error!("Wintun: failed to allocate send packet: {}", err); |
|
||||
} |
|
||||
}, |
|
||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => break, |
|
||||
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue, |
|
||||
Err(err) => return Err(err.into()), |
|
||||
} |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
fn pipe_client_event_writable(&self) -> Result<(), Box<dyn std::error::Error + '_>> { |
|
||||
let cache = self.pipe_client_cache.lock()?.drain(..).collect::<Vec<u8>>(); |
|
||||
if cache.is_empty() { |
|
||||
return Ok(()); |
|
||||
} |
|
||||
let len = cache.len(); |
|
||||
let result = self.pipe_client.lock()?.write(&cache[..]); |
|
||||
match result { |
|
||||
Ok(n) => { |
|
||||
if n < len { |
|
||||
log::trace!("Wintun pipe_client write data {} less than buffer {}", n, len); |
|
||||
self.pipe_client_cache.lock()?.extend_from_slice(&cache[n..]); |
|
||||
} |
|
||||
} |
|
||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => { |
|
||||
log::trace!("Wintun pipe_client write WouldBlock (2) len {}", len); |
|
||||
self.pipe_client_cache.lock()?.extend_from_slice(&cache); |
|
||||
} |
|
||||
Err(err) => log::error!("Wintun pipe_client write data len {} error \"{}\"", len, err), |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
pub fn setup_config<'a>( |
|
||||
&mut self, |
|
||||
bypass_ips: impl IntoIterator<Item = &'a IpCidr>, |
|
||||
dns_addr: Option<IpAddr>, |
|
||||
) -> Result<(), io::Error> { |
|
||||
let adapter = self.wintun_session.get_adapter(); |
|
||||
|
|
||||
// Setup the adapter's address/mask/gateway
|
|
||||
let address = "10.1.0.33".parse::<IpAddr>().unwrap(); |
|
||||
let mask = "255.255.255.0".parse::<IpAddr>().unwrap(); |
|
||||
let gateway = "10.1.0.1".parse::<IpAddr>().unwrap(); |
|
||||
adapter |
|
||||
.set_network_addresses_tuple(address, mask, Some(gateway)) |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; |
|
||||
|
|
||||
// 1. Setup the adapter's DNS
|
|
||||
let interface = GUID::from(adapter.get_guid()); |
|
||||
let dns = dns_addr.unwrap_or("8.8.8.8".parse::<IpAddr>().unwrap()); |
|
||||
let dns2 = "8.8.4.4".parse::<IpAddr>().unwrap(); |
|
||||
set_interface_dns_settings(interface, &[dns, dns2])?; |
|
||||
|
|
||||
// 2. Route all traffic to the adapter, here the destination is adapter's gateway
|
|
||||
// command: `route add 0.0.0.0 mask 0.0.0.0 10.1.0.1 metric 6`
|
|
||||
let unspecified = Ipv4Addr::UNSPECIFIED.to_string(); |
|
||||
let gateway = gateway.to_string(); |
|
||||
let args = &["add", &unspecified, "mask", &unspecified, &gateway, "metric", "6"]; |
|
||||
run_command("route", args)?; |
|
||||
log::info!("route {:?}", args); |
|
||||
|
|
||||
let old_gateways = get_active_network_interface_gateways()?; |
|
||||
// find ipv4 gateway address, or error return
|
|
||||
let old_gateway = old_gateways |
|
||||
.iter() |
|
||||
.find(|addr| addr.is_ipv4()) |
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "No ipv4 gateway found"))?; |
|
||||
let old_gateway = old_gateway.ip(); |
|
||||
self.old_gateway = Some(old_gateway); |
|
||||
|
|
||||
// 3. route the bypass ip to the old gateway
|
|
||||
// command: `route add bypass_ip old_gateway metric 1`
|
|
||||
for bypass_ip in bypass_ips { |
|
||||
let args = &["add", &bypass_ip.to_string(), &old_gateway.to_string(), "metric", "1"]; |
|
||||
run_command("route", args)?; |
|
||||
log::info!("route {:?}", args); |
|
||||
} |
|
||||
|
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
pub fn restore_config(&mut self) -> Result<(), io::Error> { |
|
||||
if self.old_gateway.is_none() { |
|
||||
return Ok(()); |
|
||||
} |
|
||||
let unspecified = Ipv4Addr::UNSPECIFIED.to_string(); |
|
||||
|
|
||||
// 1. Remove current adapter's route
|
|
||||
// command: `route delete 0.0.0.0 mask 0.0.0.0`
|
|
||||
let args = &["delete", &unspecified, "mask", &unspecified]; |
|
||||
run_command("route", args)?; |
|
||||
|
|
||||
// 2. Add back the old gateway route
|
|
||||
// command: `route add 0.0.0.0 mask 0.0.0.0 old_gateway metric 200`
|
|
||||
let old_gateway = self.old_gateway.take().unwrap().to_string(); |
|
||||
let args = &["add", &unspecified, "mask", &unspecified, &old_gateway, "metric", "200"]; |
|
||||
run_command("route", args)?; |
|
||||
|
|
||||
Ok(()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Drop for WinTunInterface { |
|
||||
fn drop(&mut self) { |
|
||||
if let Err(e) = self.restore_config() { |
|
||||
log::error!("Faild to unsetup config: {}", e); |
|
||||
} |
|
||||
if let Err(e) = self.wintun_session.shutdown() { |
|
||||
log::error!("phy: failed to shutdown interface: {}", e); |
|
||||
} |
|
||||
if let Some(thread) = self.wintun_reader_thread.take() { |
|
||||
if let Err(e) = thread.join() { |
|
||||
log::error!("phy: failed to join reader thread: {:?}", e); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl Device for WinTunInterface { |
|
||||
type RxToken<'a> = RxToken; |
|
||||
type TxToken<'a> = TxToken; |
|
||||
|
|
||||
fn capabilities(&self) -> DeviceCapabilities { |
|
||||
let mut v = DeviceCapabilities::default(); |
|
||||
v.max_transmission_unit = self.mtu; |
|
||||
v.medium = self.medium; |
|
||||
v |
|
||||
} |
|
||||
|
|
||||
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { |
|
||||
let mut buffer = vec![0; self.mtu]; |
|
||||
match self.pipe_server.borrow_mut().read(&mut buffer[..]) { |
|
||||
Ok(size) => { |
|
||||
buffer.resize(size, 0); |
|
||||
let rx = RxToken { buffer }; |
|
||||
let tx = TxToken { |
|
||||
pipe_server: self.pipe_server.clone(), |
|
||||
pipe_server_cache: self.pipe_server_cache.clone(), |
|
||||
}; |
|
||||
Some((rx, tx)) |
|
||||
} |
|
||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, |
|
||||
Err(err) => panic!("{}", err), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> { |
|
||||
Some(TxToken { |
|
||||
pipe_server: self.pipe_server.clone(), |
|
||||
pipe_server_cache: self.pipe_server_cache.clone(), |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#[doc(hidden)] |
|
||||
pub struct RxToken { |
|
||||
buffer: Vec<u8>, |
|
||||
} |
|
||||
|
|
||||
impl phy::RxToken for RxToken { |
|
||||
fn consume<R, F>(mut self, f: F) -> R |
|
||||
where |
|
||||
F: FnOnce(&mut [u8]) -> R, |
|
||||
{ |
|
||||
f(&mut self.buffer[..]) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#[doc(hidden)] |
|
||||
pub struct TxToken { |
|
||||
pipe_server: Rc<RefCell<NamedPipe>>, |
|
||||
pipe_server_cache: Rc<RefCell<Vec<u8>>>, |
|
||||
} |
|
||||
|
|
||||
impl phy::TxToken for TxToken { |
|
||||
fn consume<R, F>(self, len: usize, f: F) -> R |
|
||||
where |
|
||||
F: FnOnce(&mut [u8]) -> R, |
|
||||
{ |
|
||||
let mut buffer = vec![0; len]; |
|
||||
let result = f(&mut buffer); |
|
||||
|
|
||||
let buffer = self.pipe_server_cache.borrow_mut().drain(..).chain(buffer).collect::<Vec<_>>(); |
|
||||
if buffer.is_empty() { |
|
||||
// log::trace!("Wintun TxToken (pipe_server) is empty");
|
|
||||
return result; |
|
||||
} |
|
||||
let len = buffer.len(); |
|
||||
|
|
||||
match self.pipe_server.borrow_mut().write(&buffer[..]) { |
|
||||
Ok(n) => { |
|
||||
if n < len { |
|
||||
log::trace!("Wintun TxToken (pipe_server) sent {} less than buffer len {}", n, len); |
|
||||
self.pipe_server_cache.borrow_mut().extend_from_slice(&buffer[n..]); |
|
||||
} |
|
||||
} |
|
||||
Err(err) if err.kind() == io::ErrorKind::WouldBlock => { |
|
||||
self.pipe_server_cache.borrow_mut().extend_from_slice(&buffer[..]); |
|
||||
log::trace!("Wintun TxToken (pipe_server) WouldBlock data len: {}", len) |
|
||||
} |
|
||||
Err(err) => log::error!("Wintun TxToken (pipe_server) len {} error \"{}\"", len, err), |
|
||||
} |
|
||||
result |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub struct NamedPipeSource(pub Arc<Mutex<NamedPipe>>); |
|
||||
|
|
||||
impl event::Source for NamedPipeSource { |
|
||||
fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { |
|
||||
self.0 |
|
||||
.lock() |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? |
|
||||
.register(registry, token, interests) |
|
||||
} |
|
||||
|
|
||||
fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> io::Result<()> { |
|
||||
self.0 |
|
||||
.lock() |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? |
|
||||
.reregister(registry, token, interests) |
|
||||
} |
|
||||
|
|
||||
fn deregister(&mut self, registry: &Registry) -> io::Result<()> { |
|
||||
self.0 |
|
||||
.lock() |
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))? |
|
||||
.deregister(registry) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub(crate) fn run_command(command: &str, args: &[&str]) -> io::Result<()> { |
|
||||
let out = std::process::Command::new(command).args(args).output()?; |
|
||||
if !out.status.success() { |
|
||||
let err = String::from_utf8_lossy(if out.stderr.is_empty() { &out.stdout } else { &out.stderr }); |
|
||||
let info = format!("{} failed with: \"{}\"", command, err); |
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, info)); |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
pub(crate) fn set_interface_dns_settings(interface: GUID, dns: &[IpAddr]) -> io::Result<()> { |
|
||||
// format L"1.1.1.1 8.8.8.8", or L"1.1.1.1,8.8.8.8".
|
|
||||
let dns = dns.iter().map(|ip| ip.to_string()).collect::<Vec<_>>().join(","); |
|
||||
let dns = dns.encode_utf16().chain(std::iter::once(0)).collect::<Vec<_>>(); |
|
||||
|
|
||||
let settings = DNS_INTERFACE_SETTINGS { |
|
||||
Version: DNS_INTERFACE_SETTINGS_VERSION1, |
|
||||
Flags: DNS_SETTING_NAMESERVER as _, |
|
||||
NameServer: PWSTR(dns.as_ptr() as _), |
|
||||
..DNS_INTERFACE_SETTINGS::default() |
|
||||
}; |
|
||||
|
|
||||
unsafe { SetInterfaceDnsSettings(interface, &settings as *const _)? }; |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
pub(crate) fn get_active_network_interface_gateways() -> io::Result<Vec<SocketAddr>> { |
|
||||
let mut addrs = vec![]; |
|
||||
get_adapters_addresses(|adapter| { |
|
||||
if adapter.OperStatus == IfOperStatusUp && [IF_TYPE_ETHERNET_CSMACD, IF_TYPE_IEEE80211].contains(&adapter.IfType) { |
|
||||
let mut current_gateway = adapter.FirstGatewayAddress; |
|
||||
while !current_gateway.is_null() { |
|
||||
let gateway = unsafe { &*current_gateway }; |
|
||||
{ |
|
||||
let sockaddr_ptr = gateway.Address.lpSockaddr; |
|
||||
let sockaddr = unsafe { &*(sockaddr_ptr as *const SOCKADDR) }; |
|
||||
let a = unsafe { sockaddr_to_socket_addr(sockaddr) }?; |
|
||||
addrs.push(a); |
|
||||
} |
|
||||
current_gateway = gateway.Next; |
|
||||
} |
|
||||
} |
|
||||
Ok(()) |
|
||||
})?; |
|
||||
Ok(addrs) |
|
||||
} |
|
||||
|
|
||||
pub(crate) fn get_adapters_addresses<F>(mut callback: F) -> io::Result<()> |
|
||||
where |
|
||||
F: FnMut(IP_ADAPTER_ADDRESSES_LH) -> io::Result<()>, |
|
||||
{ |
|
||||
let mut size = 0; |
|
||||
let flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS; |
|
||||
let family = AF_UNSPEC.0 as u32; |
|
||||
|
|
||||
// Make an initial call to GetAdaptersAddresses to get the
|
|
||||
// size needed into the size variable
|
|
||||
let result = unsafe { GetAdaptersAddresses(family, flags, None, None, &mut size) }; |
|
||||
|
|
||||
if WIN32_ERROR(result) != ERROR_BUFFER_OVERFLOW { |
|
||||
WIN32_ERROR(result).ok()?; |
|
||||
} |
|
||||
// Allocate memory for the buffer
|
|
||||
let mut addresses: Vec<u8> = vec![0; (size + 4) as usize]; |
|
||||
|
|
||||
// Make a second call to GetAdaptersAddresses to get the actual data we want
|
|
||||
let result = unsafe { |
|
||||
let addr = Some(addresses.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH); |
|
||||
GetAdaptersAddresses(family, flags, None, addr, &mut size) |
|
||||
}; |
|
||||
|
|
||||
WIN32_ERROR(result).ok()?; |
|
||||
|
|
||||
// If successful, output some information from the data we received
|
|
||||
let mut current_addresses = addresses.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH; |
|
||||
while !current_addresses.is_null() { |
|
||||
unsafe { |
|
||||
callback(*current_addresses)?; |
|
||||
current_addresses = (*current_addresses).Next; |
|
||||
} |
|
||||
} |
|
||||
Ok(()) |
|
||||
} |
|
||||
|
|
||||
pub(crate) unsafe fn sockaddr_to_socket_addr(sock_addr: *const SOCKADDR) -> io::Result<SocketAddr> { |
|
||||
let address = match (*sock_addr).sa_family { |
|
||||
AF_INET => sockaddr_in_to_socket_addr(&*(sock_addr as *const SOCKADDR_IN)), |
|
||||
AF_INET6 => sockaddr_in6_to_socket_addr(&*(sock_addr as *const SOCKADDR_IN6)), |
|
||||
_ => return Err(io::Error::new(io::ErrorKind::Other, "Unsupported address type")), |
|
||||
}; |
|
||||
Ok(address) |
|
||||
} |
|
||||
|
|
||||
pub(crate) unsafe fn sockaddr_in_to_socket_addr(sockaddr_in: &SOCKADDR_IN) -> SocketAddr { |
|
||||
let ip = Ipv4Addr::new( |
|
||||
sockaddr_in.sin_addr.S_un.S_un_b.s_b1, |
|
||||
sockaddr_in.sin_addr.S_un.S_un_b.s_b2, |
|
||||
sockaddr_in.sin_addr.S_un.S_un_b.s_b3, |
|
||||
sockaddr_in.sin_addr.S_un.S_un_b.s_b4, |
|
||||
); |
|
||||
let port = u16::from_be(sockaddr_in.sin_port); |
|
||||
SocketAddr::new(ip.into(), port) |
|
||||
} |
|
||||
|
|
||||
pub(crate) unsafe fn sockaddr_in6_to_socket_addr(sockaddr_in6: &SOCKADDR_IN6) -> SocketAddr { |
|
||||
let ip = IpAddr::V6(Ipv6Addr::new( |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[0]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[1]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[2]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[3]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[4]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[5]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[6]), |
|
||||
u16::from_be(sockaddr_in6.sin6_addr.u.Word[7]), |
|
||||
)); |
|
||||
let port = u16::from_be(sockaddr_in6.sin6_port); |
|
||||
SocketAddr::new(ip, port) |
|
||||
} |
|
||||
@ -1,151 +0,0 @@ |
|||||
#[cfg(target_os = "linux")] |
|
||||
#[cfg(test)] |
|
||||
mod tests { |
|
||||
extern crate reqwest; |
|
||||
|
|
||||
use fork::Fork; |
|
||||
use nix::sys::signal; |
|
||||
use nix::unistd::Pid; |
|
||||
use serial_test::serial; |
|
||||
use smoltcp::wire::IpCidr; |
|
||||
use std::env; |
|
||||
|
|
||||
use tun2proxy::setup::{get_default_cidrs, Setup}; |
|
||||
use tun2proxy::util::str_to_cidr; |
|
||||
use tun2proxy::{main_entry, NetworkInterface, Options, Proxy, ProxyType}; |
|
||||
|
|
||||
#[derive(Clone, Debug)] |
|
||||
struct Test { |
|
||||
proxy: Proxy, |
|
||||
} |
|
||||
|
|
||||
static TUN_TEST_DEVICE: &str = "tun0"; |
|
||||
|
|
||||
fn proxy_from_env(env_var: &str) -> Result<Proxy, String> { |
|
||||
let url = env::var(env_var).map_err(|_| format!("{env_var} environment variable not found"))?; |
|
||||
Proxy::from_url(url.as_str()).map_err(|_| format!("{env_var} URL cannot be parsed")) |
|
||||
} |
|
||||
|
|
||||
fn test_from_env(env_var: &str) -> Result<Test, String> { |
|
||||
let proxy = proxy_from_env(env_var)?; |
|
||||
Ok(Test { proxy }) |
|
||||
} |
|
||||
|
|
||||
fn tests() -> [Result<Test, String>; 3] { |
|
||||
[ |
|
||||
test_from_env("SOCKS4_SERVER"), |
|
||||
test_from_env("SOCKS5_SERVER"), |
|
||||
test_from_env("HTTP_SERVER"), |
|
||||
] |
|
||||
} |
|
||||
|
|
||||
#[cfg(test)] |
|
||||
#[ctor::ctor] |
|
||||
fn init() { |
|
||||
dotenvy::dotenv().ok(); |
|
||||
} |
|
||||
|
|
||||
fn request_ip_host_http() { |
|
||||
reqwest::blocking::get("http://1.1.1.1").expect("failed to issue HTTP request"); |
|
||||
} |
|
||||
|
|
||||
fn request_example_https() { |
|
||||
reqwest::blocking::get("https://example.org").expect("failed to issue HTTPs request"); |
|
||||
} |
|
||||
|
|
||||
fn run_test<F, T>(filter: F, test_function: T) |
|
||||
where |
|
||||
F: Fn(&Test) -> bool, |
|
||||
T: Fn(), |
|
||||
{ |
|
||||
for potential_test in tests() { |
|
||||
match potential_test { |
|
||||
Ok(test) => { |
|
||||
if !filter(&test) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
let mut bypass_ips = Vec::<IpCidr>::new(); |
|
||||
|
|
||||
match env::var("BYPASS_IP") { |
|
||||
Err(_) => { |
|
||||
let prefix_len = if test.proxy.addr.ip().is_ipv6() { 128 } else { 32 }; |
|
||||
bypass_ips.push(IpCidr::new(test.proxy.addr.ip().into(), prefix_len)); |
|
||||
} |
|
||||
Ok(ip_str) => bypass_ips.push(str_to_cidr(&ip_str).expect("Invalid bypass IP")), |
|
||||
}; |
|
||||
|
|
||||
let mut setup = Setup::new(TUN_TEST_DEVICE, bypass_ips, get_default_cidrs()); |
|
||||
setup.configure().unwrap(); |
|
||||
|
|
||||
match fork::fork() { |
|
||||
Ok(Fork::Parent(child)) => { |
|
||||
test_function(); |
|
||||
signal::kill(Pid::from_raw(child), signal::SIGINT).expect("failed to kill child"); |
|
||||
setup.restore().unwrap(); |
|
||||
} |
|
||||
Ok(Fork::Child) => { |
|
||||
prctl::set_death_signal(signal::SIGINT as isize).unwrap(); |
|
||||
let _ = main_entry( |
|
||||
&NetworkInterface::Named(TUN_TEST_DEVICE.into()), |
|
||||
&test.proxy, |
|
||||
Options::new().with_virtual_dns(), |
|
||||
); |
|
||||
std::process::exit(0); |
|
||||
} |
|
||||
Err(_) => panic!(), |
|
||||
} |
|
||||
} |
|
||||
Err(_) => { |
|
||||
continue; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fn require_var(var: &str) { |
|
||||
env::var(var).unwrap_or_else(|_| panic!("{} environment variable required", var)); |
|
||||
} |
|
||||
|
|
||||
#[serial] |
|
||||
#[test_log::test] |
|
||||
fn test_socks4() { |
|
||||
require_var("SOCKS4_SERVER"); |
|
||||
run_test(|test| test.proxy.proxy_type == ProxyType::Socks4, request_ip_host_http) |
|
||||
} |
|
||||
|
|
||||
#[serial] |
|
||||
#[test_log::test] |
|
||||
fn test_socks5() { |
|
||||
require_var("SOCKS5_SERVER"); |
|
||||
run_test(|test| test.proxy.proxy_type == ProxyType::Socks5, request_ip_host_http) |
|
||||
} |
|
||||
|
|
||||
#[serial] |
|
||||
#[test_log::test] |
|
||||
fn test_http() { |
|
||||
require_var("HTTP_SERVER"); |
|
||||
run_test(|test| test.proxy.proxy_type == ProxyType::Http, request_ip_host_http) |
|
||||
} |
|
||||
|
|
||||
#[serial] |
|
||||
#[test_log::test] |
|
||||
fn test_socks4_dns() { |
|
||||
require_var("SOCKS4_SERVER"); |
|
||||
run_test(|test| test.proxy.proxy_type == ProxyType::Socks4, request_example_https) |
|
||||
} |
|
||||
|
|
||||
#[serial] |
|
||||
#[test_log::test] |
|
||||
fn test_socks5_dns() { |
|
||||
require_var("SOCKS5_SERVER"); |
|
||||
run_test(|test| test.proxy.proxy_type == ProxyType::Socks5, request_example_https) |
|
||||
} |
|
||||
|
|
||||
#[serial] |
|
||||
#[test_log::test] |
|
||||
fn test_http_dns() { |
|
||||
require_var("HTTP_SERVER"); |
|
||||
run_test(|test| test.proxy.proxy_type == ProxyType::Http, request_example_https) |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue