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/ |
|||
tmp/ |
|||
.* |
|||
*.secret |
|||
*.iml |
|||
!/.github |
|||
!/.cargo |
|||
/target |
|||
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] |
|||
authors = ["B. Blechschmidt", "ssrlive"] |
|||
edition = "2021" |
|||
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] |
|||
crate-type = ["cdylib", "lib"] |
|||
crate-type = ["staticlib", "cdylib", "lib"] |
|||
|
|||
[dependencies] |
|||
async-recursion = "1.0" |
|||
async-trait = "0.1" |
|||
base64 = { version = "0.21" } |
|||
clap = { version = "4.4", features = ["derive"] } |
|||
ctrlc2 = { version = "3.5", features = ["termination"] } |
|||
chrono = "0.4" |
|||
clap = { version = "4.4", features = ["derive", "wrap_help", "color"] } |
|||
ctrlc2 = { version = "3.5", features = ["tokio", "termination"] } |
|||
digest_auth = "0.3" |
|||
dotenvy = "0.15" |
|||
env_logger = "0.10" |
|||
env_logger = "0.11" |
|||
hashlink = "0.9" |
|||
httparse = "1.8" |
|||
libc = "0.2" |
|||
log = "0.4" |
|||
mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } |
|||
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 } |
|||
ipstack = { version = "0.0", features = ["log"] } |
|||
log = { version = "0.4", features = ["std"] } |
|||
socks5-impl = { version = "0.5" } |
|||
thiserror = "1.0" |
|||
tokio = { version = "1.35", features = ["full"] } |
|||
tproxy-config = { version = "0.1", features = ["log"] } |
|||
trust-dns-proto = "0.23" |
|||
tun2 = { version = "1.0", features = ["async"] } |
|||
udp-stream = { version = "0.0", default-features = false } |
|||
unicase = "2.7" |
|||
url = "2.5" |
|||
|
|||
[target.'cfg(target_family="unix")'.dependencies] |
|||
fork = "0.1" |
|||
|
|||
[target.'cfg(target_os="android")'.dependencies] |
|||
android_logger = "0.13" |
|||
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] |
|||
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::{ |
|||
error::Error, |
|||
args::ProxyType, |
|||
directions::{IncomingDataEvent, IncomingDirection, OutgoingDirection}, |
|||
http::HttpManager, |
|||
socks::SocksProxyManager, |
|||
tun2proxy::{ConnectionManager, TunToProxy}, |
|||
session_info::{IpProtocol, SessionInfo}, |
|||
virtual_dns::VirtualDns, |
|||
}; |
|||
use smoltcp::wire::IpCidr; |
|||
use socks5_impl::protocol::UserKey; |
|||
use std::{ |
|||
net::{SocketAddr, ToSocketAddrs}, |
|||
rc::Rc, |
|||
pub use clap; |
|||
use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; |
|||
use proxy_handler::{ProxyHandler, ProxyHandlerManager}; |
|||
use socks::SocksProxyManager; |
|||
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 api; |
|||
mod args; |
|||
mod directions; |
|||
mod dns; |
|||
pub mod error; |
|||
mod dump_logger; |
|||
mod error; |
|||
mod http; |
|||
pub mod setup; |
|||
mod ios; |
|||
mod proxy_handler; |
|||
mod session_info; |
|||
mod socks; |
|||
mod tun2proxy; |
|||
pub mod util; |
|||
mod virtdevice; |
|||
mod virtdns; |
|||
#[cfg(target_os = "windows")] |
|||
mod wintuninterface; |
|||
|
|||
#[derive(Clone, Debug)] |
|||
pub struct Proxy { |
|||
pub proxy_type: ProxyType, |
|||
pub addr: SocketAddr, |
|||
pub credentials: Option<UserKey>, |
|||
} |
|||
mod virtual_dns; |
|||
|
|||
pub enum NetworkInterface { |
|||
Named(String), |
|||
#[cfg(target_family = "unix")] |
|||
Fd(std::os::fd::RawFd), |
|||
} |
|||
const DNS_PORT: u16 = 53; |
|||
|
|||
impl Proxy { |
|||
pub fn from_url(s: &str) -> Result<Proxy, Error> { |
|||
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)) |
|||
}; |
|||
const MAX_SESSIONS: u64 = 200; |
|||
|
|||
let scheme = url.scheme(); |
|||
static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); |
|||
use std::sync::atomic::Ordering::Relaxed; |
|||
|
|||
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")))?; |
|||
pub struct Builder<D> { |
|||
device: D, |
|||
mtu: Option<usize>, |
|||
args: Args, |
|||
} |
|||
|
|||
Ok(Proxy { |
|||
proxy_type, |
|||
addr, |
|||
credentials, |
|||
}) |
|||
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); |
|||
|
|||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] |
|||
pub enum ProxyType { |
|||
Socks4, |
|||
Socks5, |
|||
Http, |
|||
Tun2Socks5(run(self.device, self.mtu.unwrap_or(1500), self.args, rx), tx) |
|||
} |
|||
} |
|||
|
|||
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"), |
|||
} |
|||
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)) |
|||
} |
|||
} |
|||
|
|||
#[derive(Default)] |
|||
pub struct Options { |
|||
virtual_dns: Option<virtdns::VirtualDns>, |
|||
mtu: Option<usize>, |
|||
dns_over_tcp: bool, |
|||
dns_addr: Option<std::net::IpAddr>, |
|||
ipv6_enabled: bool, |
|||
pub setup: bool, |
|||
bypass: Vec<IpCidr>, |
|||
} |
|||
pub struct Quit(Sender<()>); |
|||
|
|||
impl Options { |
|||
pub fn new() -> Self { |
|||
Options::default() |
|||
impl Quit { |
|||
pub async fn trigger(&self) -> Result<(), SendError<()>> { |
|||
self.0.send(()).await |
|||
} |
|||
} |
|||
|
|||
pub fn with_virtual_dns(mut self) -> Self { |
|||
self.virtual_dns = Some(virtdns::VirtualDns::new()); |
|||
self.dns_over_tcp = false; |
|||
self |
|||
} |
|||
#[repr(transparent)] |
|||
struct TokioJoinError(tokio::task::JoinError); |
|||
|
|||
pub fn with_dns_over_tcp(mut self) -> Self { |
|||
self.dns_over_tcp = true; |
|||
self.virtual_dns = None; |
|||
self |
|||
impl From<TokioJoinError> for crate::Result<()> { |
|||
fn from(value: TokioJoinError) -> Self { |
|||
Err(crate::Error::Io(value.0.into())) |
|||
} |
|||
} |
|||
|
|||
pub fn with_dns_addr(mut self, addr: Option<std::net::IpAddr>) -> Self { |
|||
self.dns_addr = addr; |
|||
self |
|||
} |
|||
pub struct JoinHandle<R>(tokio::task::JoinHandle<R>); |
|||
|
|||
pub fn with_ipv6_enabled(mut self) -> Self { |
|||
self.ipv6_enabled = true; |
|||
self |
|||
impl<R: From<TokioJoinError>> Future for JoinHandle<R> { |
|||
type Output = R; |
|||
|
|||
fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> { |
|||
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 fn with_mtu(mut self, mtu: usize) -> Self { |
|||
self.mtu = Some(mtu); |
|||
self |
|||
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 { |
|||
None |
|||
}; |
|||
|
|||
use socks5_impl::protocol::Version::{V4, V5}; |
|||
let mgr = match args.proxy.proxy_type { |
|||
ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc<dyn ProxyHandlerManager>, |
|||
ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc<dyn ProxyHandlerManager>, |
|||
ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc<dyn ProxyHandlerManager>, |
|||
}; |
|||
|
|||
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); |
|||
|
|||
loop { |
|||
let virtual_dns = virtual_dns.clone(); |
|||
let ip_stack_stream = tokio::select! { |
|||
_ = quit.recv() => { |
|||
log::info!(""); |
|||
log::info!("Ctrl-C recieved, exiting..."); |
|||
break; |
|||
} |
|||
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(()) |
|||
} |
|||
|
|||
pub fn with_bypass_ips<'a>(mut self, bypass_ips: impl IntoIterator<Item = &'a IpCidr>) -> Self { |
|||
for bypass_ip in bypass_ips { |
|||
self.bypass.push(*bypass_ip); |
|||
async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc<Mutex<VirtualDns>>) -> crate::Result<()> { |
|||
let mut buf = [0_u8; 4096]; |
|||
loop { |
|||
let len = udp.read(&mut buf).await?; |
|||
if len == 0 { |
|||
break; |
|||
} |
|||
self |
|||
let (msg, qname, ip) = dns.lock().await.generate_query(&buf[..len])?; |
|||
udp.write_all(&msg).await?; |
|||
log::debug!("Virtual DNS query: {} -> {}", qname, ip); |
|||
} |
|||
Ok(()) |
|||
} |
|||
|
|||
pub fn tun_to_proxy<'a>(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result<TunToProxy<'a>, Error> { |
|||
let mut ttp = TunToProxy::new(interface, options)?; |
|||
let credentials = proxy.credentials.clone(); |
|||
let server = proxy.addr; |
|||
use socks5_impl::protocol::Version::{V4, V5}; |
|||
let mgr = match proxy.proxy_type { |
|||
ProxyType::Socks4 => Rc::new(SocksProxyManager::new(server, V4, credentials)) as Rc<dyn ConnectionManager>, |
|||
ProxyType::Socks5 => Rc::new(SocksProxyManager::new(server, V5, credentials)) as Rc<dyn ConnectionManager>, |
|||
ProxyType::Http => Rc::new(HttpManager::new(server, credentials)) as Rc<dyn ConnectionManager>, |
|||
async fn handle_tcp_session( |
|||
tcp_stack: IpStackTcpStream, |
|||
server_addr: SocketAddr, |
|||
proxy_handler: Arc<Mutex<dyn ProxyHandler>>, |
|||
) -> 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), |
|||
}; |
|||
ttp.set_connection_manager(Some(mgr)); |
|||
Ok(ttp) |
|||
|
|||
log::info!("Ending {} with {:?}", session_info, result); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
pub fn main_entry(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result<(), Error> { |
|||
let mut ttp = tun_to_proxy(interface, proxy, options)?; |
|||
ttp.run()?; |
|||
async fn handle_udp_associate_session( |
|||
mut udp_stack: IpStackUdpStream, |
|||
server_addr: SocketAddr, |
|||
proxy_handler: Arc<Mutex<dyn ProxyHandler>>, |
|||
ipv6_enabled: bool, |
|||
) -> 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(()) |
|||
} |
|||
|
|||
async fn handle_dns_over_tcp_session( |
|||
mut udp_stack: IpStackUdpStream, |
|||
server_addr: SocketAddr, |
|||
proxy_handler: Arc<Mutex<dyn ProxyHandler>>, |
|||
ipv6_enabled: bool, |
|||
) -> 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 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]; |
|||
|
|||
_ = dns::parse_data_to_dns_message(buf1, false)?; |
|||
|
|||
// Insert the DNS message length in front of the payload
|
|||
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(); |
|||
|
|||
let mut to_send: VecDeque<Vec<u8>> = VecDeque::new(); |
|||
loop { |
|||
if buf.len() < 2 { |
|||
break; |
|||
} |
|||
let len = u16::from_be_bytes([buf[0], buf[1]]) as usize; |
|||
if buf.len() < len + 2 { |
|||
break; |
|||
} |
|||
|
|||
// remove the length field
|
|||
let data = buf[2..len + 2].to_vec(); |
|||
|
|||
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); |
|||
} |
|||
|
|||
to_send.push_back(message.to_vec()?); |
|||
if len + 2 == buf.len() { |
|||
break; |
|||
} |
|||
buf = buf[len + 2..].to_vec(); |
|||
} |
|||
|
|||
while let Some(packet) = to_send.pop_front() { |
|||
udp_stack.write_all(&packet).await?; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
log::info!("Ending {}", session_info); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
async fn handle_proxy_session(server: &mut TcpStream, proxy_handler: Arc<Mutex<dyn ProxyHandler>>) -> crate::Result<Option<SocketAddr>> { |
|||
let mut launched = false; |
|||
let mut proxy_handler = proxy_handler.lock().await; |
|||
let dir = OutgoingDirection::ToServer; |
|||
|
|||
loop { |
|||
if proxy_handler.connection_established() { |
|||
break; |
|||
} |
|||
|
|||
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], |
|||
}; |
|||
proxy_handler.push_data(event).await?; |
|||
|
|||
let data = proxy_handler.peek_data(dir).buffer; |
|||
let len = data.len(); |
|||
if len > 0 { |
|||
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