mirror of
https://github.com/tun2proxy/tun2proxy.git
synced 2025-04-11 17:42:02 +00:00
beginning async version (#84)
This commit is contained in:
parent
337619169e
commit
9c4fa4260a
41 changed files with 2022 additions and 3286 deletions
|
@ -2,4 +2,8 @@
|
|||
protocol = "sparse"
|
||||
|
||||
[build]
|
||||
# target = ["x86_64-unknown-linux-gnu", "aarch64-linux-android"]
|
||||
# target = ["x86_64-unknown-linux-gnu"]
|
||||
# target = ["aarch64-linux-android"]
|
||||
# target = ["aarch64-apple-ios"]
|
||||
# target = ["x86_64-pc-windows-msvc"]
|
||||
# target = ["x86_64-apple-darwin"]
|
||||
|
|
77
.github/workflows/format-build.yml
vendored
77
.github/workflows/format-build.yml
vendored
|
@ -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
|
48
.github/workflows/publish-docker.yml
vendored
48
.github/workflows/publish-docker.yml
vendored
|
@ -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 }}
|
26
.github/workflows/rust.yml
vendored
Normal file
26
.github/workflows/rust.yml
vendored
Normal file
|
@ -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
|
45
.github/workflows/tests.yml
vendored
45
.github/workflows/tests.yml
vendored
|
@ -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
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -1,11 +1,9 @@
|
|||
examples/
|
||||
.env
|
||||
project.xcworkspace/
|
||||
xcuserdata/
|
||||
.vscode/
|
||||
.VSCodeCounter/
|
||||
build/
|
||||
tmp/
|
||||
.*
|
||||
*.secret
|
||||
*.iml
|
||||
!/.github
|
||||
!/.cargo
|
||||
/target
|
||||
Cargo.lock
|
||||
manual-test.sh
|
||||
target/
|
||||
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -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.
|
65
Cargo.toml
65
Cargo.toml
|
@ -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"
|
||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -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"]
|
21
apple/readme.md
Normal file
21
apple/readme.md
Normal file
|
@ -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
|
||||
```
|
398
apple/tun2proxy.xcodeproj/project.pbxproj
Normal file
398
apple/tun2proxy.xcodeproj/project.pbxproj
Normal file
|
@ -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 */;
|
||||
}
|
22
apple/tun2proxy/Tun2proxyWrapper.h
Normal file
22
apple/tun2proxy/Tun2proxyWrapper.h
Normal file
|
@ -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 */
|
27
apple/tun2proxy/Tun2proxyWrapper.m
Normal file
27
apple/tun2proxy/Tun2proxyWrapper.m
Normal file
|
@ -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
|
18
apple/tun2proxy/tun2proxy.h
Normal file
18
apple/tun2proxy/tun2proxy.h
Normal file
|
@ -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>
|
6
cbindgen.toml
Normal file
6
cbindgen.toml
Normal file
|
@ -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",
|
||||
]
|
24
scripts/dante.conf
Normal file
24
scripts/dante.conf
Normal file
|
@ -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
|
||||
}
|
54
scripts/iperf3.sh
Executable file
54
scripts/iperf3.sh
Executable file
|
@ -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"
|
66
scripts/linux.sh
Executable file
66
scripts/linux.sh
Executable file
|
@ -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 "$@"
|
83
scripts/rperf.sh
Executable file
83
scripts/rperf.sh
Executable file
|
@ -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"
|
|
@ -1,14 +1,16 @@
|
|||
#![cfg(target_os = "android")]
|
||||
|
||||
use crate::{error::Error, tun2proxy::TunToProxy, tun_to_proxy, NetworkInterface, Options, Proxy};
|
||||
use crate::{
|
||||
args::{ArgDns, ArgProxy},
|
||||
error::{Error, Result},
|
||||
ArgVerbosity, Args,
|
||||
};
|
||||
use jni::{
|
||||
objects::{JClass, JString},
|
||||
sys::{jboolean, jint},
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
static mut TUN_TO_PROXY: Option<TunToProxy> = None;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Running tun2proxy
|
||||
|
@ -22,8 +24,9 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
|
|||
verbose: jboolean,
|
||||
dns_over_tcp: jboolean,
|
||||
) -> jint {
|
||||
let log_level = if verbose != 0 { "trace" } else { "info" };
|
||||
let filter_str = &format!("off,tun2proxy={log_level}");
|
||||
let dns = if dns_over_tcp != 0 { ArgDns::OverTcp } else { ArgDns::Direct };
|
||||
let verbosity = if verbose != 0 { ArgVerbosity::Trace } else { ArgVerbosity::Info };
|
||||
let filter_str = &format!("off,tun2proxy={verbosity}");
|
||||
let filter = android_logger::FilterBuilder::new().parse(filter_str).build();
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default()
|
||||
|
@ -31,31 +34,11 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
|
|||
.with_max_level(log::LevelFilter::Trace)
|
||||
.with_filter(filter),
|
||||
);
|
||||
let proxy_url = get_java_string(&mut env, &proxy_url).unwrap();
|
||||
let proxy = ArgProxy::from_url(proxy_url).unwrap();
|
||||
|
||||
let mut block = || -> Result<(), Error> {
|
||||
let proxy_url = get_java_string(&mut env, &proxy_url)?;
|
||||
let proxy = Proxy::from_url(proxy_url)?;
|
||||
|
||||
let addr = proxy.addr;
|
||||
let proxy_type = proxy.proxy_type;
|
||||
log::info!("Proxy {proxy_type} server: {addr}");
|
||||
|
||||
let dns_addr = "8.8.8.8".parse::<std::net::IpAddr>().unwrap();
|
||||
let options = Options::new().with_dns_addr(Some(dns_addr)).with_mtu(tun_mtu as usize);
|
||||
let options = if dns_over_tcp != 0 { options.with_dns_over_tcp() } else { options };
|
||||
|
||||
let interface = NetworkInterface::Fd(tun_fd);
|
||||
let tun2proxy = tun_to_proxy(&interface, &proxy, options)?;
|
||||
TUN_TO_PROXY = Some(tun2proxy);
|
||||
if let Some(tun2proxy) = &mut TUN_TO_PROXY {
|
||||
tun2proxy.run()?;
|
||||
}
|
||||
Ok::<(), Error>(())
|
||||
};
|
||||
if let Err(error) = block() {
|
||||
log::error!("failed to run tun2proxy with error: {:?}", error);
|
||||
}
|
||||
0
|
||||
let args = Args::new(Some(tun_fd), proxy, dns, verbosity);
|
||||
crate::api::tun2proxy_internal_run(args, tun_mtu as _)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
|
@ -63,20 +46,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run(
|
|||
/// Shutdown tun2proxy
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint {
|
||||
match &mut TUN_TO_PROXY {
|
||||
None => {
|
||||
log::error!("tun2proxy not started");
|
||||
1
|
||||
}
|
||||
Some(tun2proxy) => {
|
||||
if let Err(e) = tun2proxy.shutdown() {
|
||||
log::error!("failed to shutdown tun2proxy with error: {:?}", e);
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::api::tun2proxy_internal_stop()
|
||||
}
|
||||
|
||||
unsafe fn get_java_string<'a>(env: &'a mut JNIEnv, string: &'a JString) -> Result<&'a str, Error> {
|
||||
|
|
70
src/api.rs
Normal file
70
src/api.rs
Normal file
|
@ -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
|
||||
}
|
198
src/args.rs
Normal file
198
src/args.rs
Normal file
|
@ -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:password@127.0.0.1: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"),
|
||||
}
|
||||
}
|
||||
}
|
83
src/bin/main.rs
Normal file
83
src/bin/main.rs
Normal file
|
@ -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(())
|
||||
}
|
28
src/directions.rs
Normal file
28
src/directions.rs
Normal file
|
@ -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>;
|
45
src/dns.rs
45
src/dns.rs
|
@ -1,39 +1,10 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
use trust_dns_proto::op::MessageType;
|
||||
use trust_dns_proto::{
|
||||
op::{Message, ResponseCode},
|
||||
rr::{record_type::RecordType, Name, RData, Record},
|
||||
};
|
||||
|
||||
#[cfg(feature = "use-rand")]
|
||||
pub fn build_dns_request(domain: &str, query_type: RecordType, used_by_tcp: bool) -> Result<Vec<u8>, String> {
|
||||
// [dependencies]
|
||||
// rand = "0.8"
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use trust_dns_proto::op::{header::MessageType, op_code::OpCode, query::Query};
|
||||
let name = Name::from_str(domain).map_err(|e| e.to_string())?;
|
||||
let query = Query::query(name, query_type);
|
||||
let mut msg = Message::new();
|
||||
msg.add_query(query)
|
||||
.set_id(StdRng::from_entropy().gen())
|
||||
.set_op_code(OpCode::Query)
|
||||
.set_message_type(MessageType::Query)
|
||||
.set_recursion_desired(true);
|
||||
let mut msg_buf = msg.to_vec().map_err(|e| e.to_string())?;
|
||||
if used_by_tcp {
|
||||
let mut buf = (msg_buf.len() as u16).to_be_bytes().to_vec();
|
||||
buf.append(&mut msg_buf);
|
||||
Ok(buf)
|
||||
} else {
|
||||
Ok(msg_buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result<Message, String> {
|
||||
let record = match ip {
|
||||
IpAddr::V4(ip) => {
|
||||
|
@ -105,17 +76,3 @@ pub fn parse_data_to_dns_message(data: &[u8], used_by_tcp: bool) -> Result<Messa
|
|||
let message = Message::from_vec(data).map_err(|e| e.to_string())?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
// FIXME: use IpAddr::is_global() instead when it's stable
|
||||
pub fn addr_is_private(addr: &SocketAddr) -> bool {
|
||||
fn is_benchmarking(addr: &Ipv4Addr) -> bool {
|
||||
addr.octets()[0] == 198 && (addr.octets()[1] & 0xfe) == 18
|
||||
}
|
||||
fn addr_v4_is_private(addr: &Ipv4Addr) -> bool {
|
||||
is_benchmarking(addr) || addr.is_private() || addr.is_loopback() || addr.is_link_local()
|
||||
}
|
||||
match addr {
|
||||
SocketAddr::V4(addr) => addr_v4_is_private(addr.ip()),
|
||||
SocketAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
|
71
src/dump_logger.rs
Normal file
71
src/dump_logger.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
src/error.rs
47
src/error.rs
|
@ -3,9 +3,6 @@ pub enum Error {
|
|||
#[error("std::ffi::NulError {0:?}")]
|
||||
Nul(#[from] std::ffi::NulError),
|
||||
|
||||
#[error("ctrlc2::Error {0:?}")]
|
||||
InterruptHandler(#[from] ctrlc2::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
|
@ -15,35 +12,23 @@ pub enum Error {
|
|||
#[error("std::net::AddrParseError {0}")]
|
||||
AddrParse(#[from] std::net::AddrParseError),
|
||||
|
||||
#[error("smoltcp::iface::RouteTableFull {0:?}")]
|
||||
RouteTableFull(#[from] smoltcp::iface::RouteTableFull),
|
||||
|
||||
#[error("smoltcp::socket::tcp::RecvError {0:?}")]
|
||||
Recv(#[from] smoltcp::socket::tcp::RecvError),
|
||||
|
||||
#[error("smoltcp::socket::tcp::ListenError {0:?}")]
|
||||
Listen(#[from] smoltcp::socket::tcp::ListenError),
|
||||
|
||||
#[error("smoltcp::socket::udp::BindError {0:?}")]
|
||||
Bind(#[from] smoltcp::socket::udp::BindError),
|
||||
|
||||
#[error("smoltcp::socket::tcp::SendError {0:?}")]
|
||||
Send(#[from] smoltcp::socket::tcp::SendError),
|
||||
|
||||
#[error("smoltcp::socket::udp::SendError {0:?}")]
|
||||
UdpSend(#[from] smoltcp::socket::udp::SendError),
|
||||
|
||||
#[error("smoltcp::wire::Error {0:?}")]
|
||||
Wire(#[from] smoltcp::wire::Error),
|
||||
|
||||
#[error("std::str::Utf8Error {0:?}")]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
|
||||
#[error("TryFromSliceError {0:?}")]
|
||||
TryFromSlice(#[from] std::array::TryFromSliceError),
|
||||
|
||||
#[error("ProtoError {0:?}")]
|
||||
ProtoError(#[from] trust_dns_proto::error::ProtoError),
|
||||
#[error("IpStackError {0:?}")]
|
||||
IpStack(#[from] ipstack::IpStackError),
|
||||
|
||||
#[error("DnsProtoError {0:?}")]
|
||||
DnsProto(#[from] trust_dns_proto::error::ProtoError),
|
||||
|
||||
#[error("httparse::Error {0:?}")]
|
||||
Httparse(#[from] httparse::Error),
|
||||
|
||||
#[error("digest_auth::Error {0:?}")]
|
||||
DigestAuth(#[from] digest_auth::Error),
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[error("jni::errors::Error {0:?}")]
|
||||
|
@ -52,18 +37,8 @@ pub enum Error {
|
|||
#[error("{0}")]
|
||||
String(String),
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
#[error("nix::errno::Errno {0:?}")]
|
||||
OSError(#[from] nix::errno::Errno),
|
||||
|
||||
#[error("std::num::ParseIntError {0:?}")]
|
||||
IntParseError(#[from] std::num::ParseIntError),
|
||||
|
||||
#[error("httparse::Error {0}")]
|
||||
HttpError(#[from] httparse::Error),
|
||||
|
||||
#[error("digest_auth::Error {0}")]
|
||||
DigestAuthError(#[from] digest_auth::Error),
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
|
|
129
src/http.rs
129
src/http.rs
|
@ -1,22 +1,20 @@
|
|||
use crate::{
|
||||
error::Error,
|
||||
tun2proxy::{
|
||||
ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection,
|
||||
ProxyHandler,
|
||||
},
|
||||
directions::{IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection},
|
||||
error::{Error, Result},
|
||||
proxy_handler::{ProxyHandler, ProxyHandlerManager},
|
||||
session_info::{IpProtocol, SessionInfo},
|
||||
};
|
||||
use base64::Engine;
|
||||
use httparse::Response;
|
||||
use smoltcp::wire::IpProtocol;
|
||||
use socks5_impl::protocol::UserKey;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{hash_map::RandomState, HashMap, VecDeque},
|
||||
iter::FromIterator,
|
||||
net::SocketAddr,
|
||||
rc::Rc,
|
||||
str,
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use unicase::UniCase;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
|
@ -48,10 +46,11 @@ pub struct HttpConnection {
|
|||
crlf_state: u8,
|
||||
counter: usize,
|
||||
skip: usize,
|
||||
digest_state: Rc<RefCell<Option<DigestState>>>,
|
||||
digest_state: Arc<Mutex<Option<DigestState>>>,
|
||||
before: bool,
|
||||
credentials: Option<UserKey>,
|
||||
info: ConnectionInfo,
|
||||
info: SessionInfo,
|
||||
domain_name: Option<String>,
|
||||
}
|
||||
|
||||
static PROXY_AUTHENTICATE: &str = "Proxy-Authenticate";
|
||||
|
@ -61,7 +60,12 @@ static TRANSFER_ENCODING: &str = "Transfer-Encoding";
|
|||
static CONTENT_LENGTH: &str = "Content-Length";
|
||||
|
||||
impl HttpConnection {
|
||||
fn new(info: &ConnectionInfo, credentials: Option<UserKey>, digest_state: Rc<RefCell<Option<DigestState>>>) -> Result<Self, Error> {
|
||||
async fn new(
|
||||
info: SessionInfo,
|
||||
domain_name: Option<String>,
|
||||
credentials: Option<UserKey>,
|
||||
digest_state: Arc<Mutex<Option<DigestState>>>,
|
||||
) -> Result<Self> {
|
||||
let mut res = Self {
|
||||
state: HttpState::ExpectResponseHeaders,
|
||||
client_inbuf: VecDeque::default(),
|
||||
|
@ -74,38 +78,50 @@ impl HttpConnection {
|
|||
digest_state,
|
||||
before: false,
|
||||
credentials,
|
||||
info: info.clone(),
|
||||
info,
|
||||
domain_name,
|
||||
};
|
||||
|
||||
res.send_tunnel_request()?;
|
||||
res.send_tunnel_request().await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn send_tunnel_request(&mut self) -> Result<(), Error> {
|
||||
async fn send_tunnel_request(&mut self) -> Result<(), Error> {
|
||||
let host = if let Some(domain_name) = &self.domain_name {
|
||||
format!("{}:{}", domain_name, self.info.dst.port())
|
||||
} else {
|
||||
self.info.dst.to_string()
|
||||
};
|
||||
|
||||
self.server_outbuf.extend(b"CONNECT ");
|
||||
self.server_outbuf.extend(self.info.dst.to_string().as_bytes());
|
||||
self.server_outbuf.extend(host.as_bytes());
|
||||
self.server_outbuf.extend(b" HTTP/1.1\r\nHost: ");
|
||||
self.server_outbuf.extend(self.info.dst.to_string().as_bytes());
|
||||
self.server_outbuf.extend(host.as_bytes());
|
||||
self.server_outbuf.extend(b"\r\n");
|
||||
|
||||
self.send_auth_data(if self.digest_state.borrow().is_none() {
|
||||
let scheme = if self.digest_state.lock().await.is_none() {
|
||||
AuthenticationScheme::Basic
|
||||
} else {
|
||||
AuthenticationScheme::Digest
|
||||
})?;
|
||||
};
|
||||
self.send_auth_data(scheme).await?;
|
||||
|
||||
self.server_outbuf.extend(b"\r\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_auth_data(&mut self, scheme: AuthenticationScheme) -> Result<(), Error> {
|
||||
async fn send_auth_data(&mut self, scheme: AuthenticationScheme) -> Result<()> {
|
||||
let Some(credentials) = &self.credentials else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match scheme {
|
||||
AuthenticationScheme::Digest => {
|
||||
let uri = self.info.dst.to_string();
|
||||
let uri = if let Some(domain_name) = &self.domain_name {
|
||||
format!("{}:{}", domain_name, self.info.dst.port())
|
||||
} else {
|
||||
self.info.dst.to_string()
|
||||
};
|
||||
|
||||
let context = digest_auth::AuthContext::new_with_method(
|
||||
&credentials.username,
|
||||
|
@ -115,8 +131,8 @@ impl HttpConnection {
|
|||
digest_auth::HttpMethod::CONNECT,
|
||||
);
|
||||
|
||||
let mut state = self.digest_state.borrow_mut();
|
||||
let response = state.as_mut().unwrap().respond(&context)?;
|
||||
let mut state = self.digest_state.lock().await;
|
||||
let response = state.as_mut().unwrap().respond(&context).unwrap();
|
||||
|
||||
self.server_outbuf
|
||||
.extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes());
|
||||
|
@ -133,7 +149,8 @@ impl HttpConnection {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn state_change(&mut self) -> Result<(), Error> {
|
||||
#[async_recursion::async_recursion]
|
||||
async fn state_change(&mut self) -> Result<()> {
|
||||
match self.state {
|
||||
HttpState::ExpectResponseHeaders => {
|
||||
while self.counter < self.server_inbuf.len() {
|
||||
|
@ -176,7 +193,7 @@ impl HttpConnection {
|
|||
// Connection successful
|
||||
self.state = HttpState::Established;
|
||||
self.server_inbuf.clear();
|
||||
return self.state_change();
|
||||
return self.state_change().await;
|
||||
}
|
||||
|
||||
if status_code != 407 {
|
||||
|
@ -209,7 +226,7 @@ impl HttpConnection {
|
|||
}
|
||||
|
||||
// Update the digest state
|
||||
self.digest_state.replace(Some(state));
|
||||
self.digest_state.lock().await.replace(state);
|
||||
self.before = true;
|
||||
|
||||
let closed = match headers_map.get(&UniCase::new(CONNECTION)) {
|
||||
|
@ -222,7 +239,7 @@ impl HttpConnection {
|
|||
// Reset all the buffers
|
||||
self.server_inbuf.clear();
|
||||
self.server_outbuf.clear();
|
||||
self.send_tunnel_request()?;
|
||||
self.send_tunnel_request().await?;
|
||||
|
||||
self.state = HttpState::Reset;
|
||||
return Ok(());
|
||||
|
@ -260,7 +277,7 @@ impl HttpConnection {
|
|||
// Close the connection by information miss
|
||||
self.server_inbuf.clear();
|
||||
self.server_outbuf.clear();
|
||||
self.send_tunnel_request()?;
|
||||
self.send_tunnel_request().await?;
|
||||
|
||||
self.state = HttpState::Reset;
|
||||
return Ok(());
|
||||
|
@ -271,7 +288,7 @@ impl HttpConnection {
|
|||
self.state = HttpState::ExpectResponse;
|
||||
self.skip = content_length + len;
|
||||
|
||||
return self.state_change();
|
||||
return self.state_change().await;
|
||||
}
|
||||
HttpState::ExpectResponse => {
|
||||
if self.skip > 0 {
|
||||
|
@ -285,10 +302,10 @@ impl HttpConnection {
|
|||
|
||||
// self.server_outbuf.append(&mut self.data_buf);
|
||||
// self.data_buf.clear();
|
||||
self.send_tunnel_request()?;
|
||||
self.send_tunnel_request().await?;
|
||||
self.state = HttpState::ExpectResponseHeaders;
|
||||
|
||||
return self.state_change();
|
||||
return self.state_change().await;
|
||||
}
|
||||
}
|
||||
HttpState::Established => {
|
||||
|
@ -299,7 +316,7 @@ impl HttpConnection {
|
|||
}
|
||||
HttpState::Reset => {
|
||||
self.state = HttpState::ExpectResponseHeaders;
|
||||
return self.state_change();
|
||||
return self.state_change().await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -307,12 +324,17 @@ impl HttpConnection {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ProxyHandler for HttpConnection {
|
||||
fn get_connection_info(&self) -> &ConnectionInfo {
|
||||
&self.info
|
||||
fn get_session_info(&self) -> SessionInfo {
|
||||
self.info
|
||||
}
|
||||
|
||||
fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error> {
|
||||
fn get_domain_name(&self) -> Option<String> {
|
||||
self.domain_name.clone()
|
||||
}
|
||||
|
||||
async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()> {
|
||||
let direction = event.direction;
|
||||
let buffer = event.buffer;
|
||||
match direction {
|
||||
|
@ -324,7 +346,8 @@ impl ProxyHandler for HttpConnection {
|
|||
}
|
||||
}
|
||||
|
||||
self.state_change()
|
||||
self.state_change().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn consume_data(&mut self, dir: OutgoingDirection, size: usize) {
|
||||
|
@ -352,16 +375,10 @@ impl ProxyHandler for HttpConnection {
|
|||
self.state == HttpState::Established
|
||||
}
|
||||
|
||||
fn data_len(&self, dir: Direction) -> usize {
|
||||
fn data_len(&self, dir: OutgoingDirection) -> usize {
|
||||
match dir {
|
||||
Direction::Incoming(incoming) => match incoming {
|
||||
IncomingDirection::FromServer => self.server_inbuf.len(),
|
||||
IncomingDirection::FromClient => self.client_inbuf.len(),
|
||||
},
|
||||
Direction::Outgoing(outgoing) => match outgoing {
|
||||
OutgoingDirection::ToServer => self.server_outbuf.len(),
|
||||
OutgoingDirection::ToClient => self.client_outbuf.len(),
|
||||
},
|
||||
OutgoingDirection::ToServer => self.server_outbuf.len(),
|
||||
OutgoingDirection::ToClient => self.client_outbuf.len(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,19 +394,23 @@ impl ProxyHandler for HttpConnection {
|
|||
pub(crate) struct HttpManager {
|
||||
server: SocketAddr,
|
||||
credentials: Option<UserKey>,
|
||||
digest_state: Rc<RefCell<Option<DigestState>>>,
|
||||
digest_state: Arc<Mutex<Option<DigestState>>>,
|
||||
}
|
||||
|
||||
impl ConnectionManager for HttpManager {
|
||||
fn new_proxy_handler(&self, info: &ConnectionInfo, _: bool) -> Result<Box<dyn ProxyHandler>, Error> {
|
||||
#[async_trait::async_trait]
|
||||
impl ProxyHandlerManager for HttpManager {
|
||||
async fn new_proxy_handler(
|
||||
&self,
|
||||
info: SessionInfo,
|
||||
domain_name: Option<String>,
|
||||
_udp_associate: bool,
|
||||
) -> std::io::Result<Arc<Mutex<dyn ProxyHandler>>> {
|
||||
if info.protocol != IpProtocol::Tcp {
|
||||
return Err("Invalid protocol".into());
|
||||
return Err(Error::from("Invalid protocol").into());
|
||||
}
|
||||
Ok(Box::new(HttpConnection::new(
|
||||
info,
|
||||
self.credentials.clone(),
|
||||
self.digest_state.clone(),
|
||||
)?))
|
||||
Ok(Arc::new(Mutex::new(
|
||||
HttpConnection::new(info, domain_name, self.credentials.clone(), self.digest_state.clone()).await?,
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_server_addr(&self) -> SocketAddr {
|
||||
|
@ -402,7 +423,7 @@ impl HttpManager {
|
|||
Self {
|
||||
server,
|
||||
credentials,
|
||||
digest_state: Rc::new(RefCell::new(None)),
|
||||
digest_state: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
41
src/ios.rs
Normal file
41
src/ios.rs
Normal file
|
@ -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()
|
||||
}
|
587
src/lib.rs
587
src/lib.rs
|
@ -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;
|
||||
mod virtual_dns;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Proxy {
|
||||
pub proxy_type: ProxyType,
|
||||
pub addr: SocketAddr,
|
||||
pub credentials: Option<UserKey>,
|
||||
}
|
||||
const DNS_PORT: u16 = 53;
|
||||
|
||||
pub enum NetworkInterface {
|
||||
Named(String),
|
||||
#[cfg(target_family = "unix")]
|
||||
Fd(std::os::fd::RawFd),
|
||||
}
|
||||
const MAX_SESSIONS: u64 = 200;
|
||||
|
||||
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))?;
|
||||
static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
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(Proxy {
|
||||
proxy_type,
|
||||
addr,
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub enum ProxyType {
|
||||
Socks4,
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Options {
|
||||
virtual_dns: Option<virtdns::VirtualDns>,
|
||||
pub struct Builder<D> {
|
||||
device: D,
|
||||
mtu: Option<usize>,
|
||||
dns_over_tcp: bool,
|
||||
dns_addr: Option<std::net::IpAddr>,
|
||||
ipv6_enabled: bool,
|
||||
pub setup: bool,
|
||||
bypass: Vec<IpCidr>,
|
||||
args: Args,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn new() -> Self {
|
||||
Options::default()
|
||||
impl<D: AsyncRead + AsyncWrite + Unpin + Send + 'static> Builder<D> {
|
||||
pub fn new(device: D, args: Args) -> Self {
|
||||
Builder { device, args, mtu: None }
|
||||
}
|
||||
|
||||
pub fn with_virtual_dns(mut self) -> Self {
|
||||
self.virtual_dns = Some(virtdns::VirtualDns::new());
|
||||
self.dns_over_tcp = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dns_over_tcp(mut self) -> Self {
|
||||
self.dns_over_tcp = true;
|
||||
self.virtual_dns = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dns_addr(mut self, addr: Option<std::net::IpAddr>) -> Self {
|
||||
self.dns_addr = addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ipv6_enabled(mut self) -> Self {
|
||||
self.ipv6_enabled = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mtu(mut self, mtu: usize) -> Self {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
self
|
||||
Tun2Socks5(run(self.device, self.mtu.unwrap_or(1500), self.args, rx), tx)
|
||||
}
|
||||
}
|
||||
|
||||
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>,
|
||||
};
|
||||
ttp.set_connection_manager(Some(mgr));
|
||||
Ok(ttp)
|
||||
pub struct Tun2Socks5<F: Future>(F, Sender<()>);
|
||||
|
||||
impl<F: Future + Send + 'static> Tun2Socks5<F>
|
||||
where
|
||||
F::Output: Send,
|
||||
{
|
||||
pub fn start(self) -> (JoinHandle<F::Output>, Quit) {
|
||||
let r = tokio::spawn(self.0);
|
||||
(JoinHandle(r), Quit(self.1))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_entry(interface: &NetworkInterface, proxy: &Proxy, options: Options) -> Result<(), Error> {
|
||||
let mut ttp = tun_to_proxy(interface, proxy, options)?;
|
||||
ttp.run()?;
|
||||
pub struct Quit(Sender<()>);
|
||||
|
||||
impl Quit {
|
||||
pub async fn trigger(&self) -> Result<(), SendError<()>> {
|
||||
self.0.send(()).await
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct TokioJoinError(tokio::task::JoinError);
|
||||
|
||||
impl From<TokioJoinError> for crate::Result<()> {
|
||||
fn from(value: TokioJoinError) -> Self {
|
||||
Err(crate::Error::Io(value.0.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JoinHandle<R>(tokio::task::JoinHandle<R>);
|
||||
|
||||
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 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(())
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
let (msg, qname, ip) = dns.lock().await.generate_query(&buf[..len])?;
|
||||
udp.write_all(&msg).await?;
|
||||
log::debug!("Virtual DNS query: {} -> {}", qname, ip);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
log::info!("Ending {} with {:?}", session_info, result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
|
156
src/main.rs
156
src/main.rs
|
@ -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
|
||||
}
|
30
src/proxy_handler.rs
Normal file
30
src/proxy_handler.rs
Normal file
|
@ -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;
|
||||
}
|
53
src/session_info.rs
Normal file
53
src/session_info.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
337
src/setup.rs
337
src/setup.rs
|
@ -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(())
|
||||
}
|
||||
}
|
161
src/socks.rs
161
src/socks.rs
|
@ -1,15 +1,14 @@
|
|||
use crate::{
|
||||
directions::{IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection},
|
||||
error::{Error, Result},
|
||||
tun2proxy::{
|
||||
ConnectionInfo, ConnectionManager, Direction, IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection,
|
||||
ProxyHandler,
|
||||
},
|
||||
proxy_handler::{ProxyHandler, ProxyHandlerManager},
|
||||
session_info::SessionInfo,
|
||||
};
|
||||
use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version};
|
||||
use std::{collections::VecDeque, convert::TryFrom, net::SocketAddr};
|
||||
use std::{collections::VecDeque, net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum SocksState {
|
||||
ClientHello,
|
||||
ServerHello,
|
||||
|
@ -21,7 +20,8 @@ enum SocksState {
|
|||
}
|
||||
|
||||
struct SocksProxyImpl {
|
||||
info: ConnectionInfo,
|
||||
info: SessionInfo,
|
||||
domain_name: Option<String>,
|
||||
state: SocksState,
|
||||
client_inbuf: VecDeque<u8>,
|
||||
server_inbuf: VecDeque<u8>,
|
||||
|
@ -34,10 +34,17 @@ struct SocksProxyImpl {
|
|||
}
|
||||
|
||||
impl SocksProxyImpl {
|
||||
fn new(info: &ConnectionInfo, credentials: Option<UserKey>, version: Version, command: protocol::Command) -> Result<Self> {
|
||||
fn new(
|
||||
info: SessionInfo,
|
||||
domain_name: Option<String>,
|
||||
credentials: Option<UserKey>,
|
||||
version: Version,
|
||||
command: protocol::Command,
|
||||
) -> Result<Self> {
|
||||
let mut result = Self {
|
||||
info: info.clone(),
|
||||
state: SocksState::ServerHello,
|
||||
info,
|
||||
domain_name,
|
||||
state: SocksState::ClientHello,
|
||||
client_inbuf: VecDeque::default(),
|
||||
server_inbuf: VecDeque::default(),
|
||||
client_outbuf: VecDeque::default(),
|
||||
|
@ -58,16 +65,17 @@ impl SocksProxyImpl {
|
|||
let mut ip_vec = Vec::<u8>::new();
|
||||
let mut name_vec = Vec::<u8>::new();
|
||||
match &self.info.dst {
|
||||
Address::SocketAddress(SocketAddr::V4(addr)) => {
|
||||
ip_vec.extend(addr.ip().octets().as_ref());
|
||||
SocketAddr::V4(addr) => {
|
||||
if let Some(host) = &self.domain_name {
|
||||
ip_vec.extend(&[0, 0, 0, host.len() as u8]);
|
||||
name_vec.extend(host.as_bytes());
|
||||
name_vec.push(0);
|
||||
} else {
|
||||
ip_vec.extend(addr.ip().octets().as_ref());
|
||||
}
|
||||
}
|
||||
Address::SocketAddress(SocketAddr::V6(_)) => {
|
||||
return Err("SOCKS4 does not support IPv6".into());
|
||||
}
|
||||
Address::DomainAddress(host, _) => {
|
||||
ip_vec.extend(&[0, 0, 0, host.len() as u8]);
|
||||
name_vec.extend(host.as_bytes());
|
||||
name_vec.push(0);
|
||||
SocketAddr::V6(addr) => {
|
||||
return Err(format!("SOCKS4 does not support IPv6: {}", addr).into());
|
||||
}
|
||||
}
|
||||
self.server_outbuf.extend(ip_vec);
|
||||
|
@ -85,14 +93,7 @@ impl SocksProxyImpl {
|
|||
|
||||
fn send_client_hello_socks5(&mut self) -> Result<(), Error> {
|
||||
let credentials = &self.credentials;
|
||||
// Providing unassigned methods is supposed to bypass China's GFW.
|
||||
// For details, refer to https://github.com/blechschmidt/tun2proxy/issues/35.
|
||||
#[rustfmt::skip]
|
||||
let mut methods = vec![
|
||||
AuthMethod::NoAuth,
|
||||
AuthMethod::from(4_u8),
|
||||
AuthMethod::from(100_u8),
|
||||
];
|
||||
let mut methods = vec![AuthMethod::NoAuth, AuthMethod::from(4_u8), AuthMethod::from(100_u8)];
|
||||
if credentials.is_some() {
|
||||
methods.push(AuthMethod::UserPass);
|
||||
}
|
||||
|
@ -113,29 +114,29 @@ impl SocksProxyImpl {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_server_hello_socks4(&mut self) -> Result<(), Error> {
|
||||
fn receive_server_hello_socks4(&mut self) -> std::io::Result<()> {
|
||||
if self.server_inbuf.len() < 8 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.server_inbuf[1] != 0x5a {
|
||||
return Err("SOCKS4 server replied with an unexpected reply code.".into());
|
||||
return Err(crate::Error::from("SOCKS4 server replied with an unexpected reply code.").into());
|
||||
}
|
||||
|
||||
self.server_inbuf.drain(0..8);
|
||||
|
||||
self.state = SocksState::Established;
|
||||
self.state_change()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_server_hello_socks5(&mut self) -> Result<(), Error> {
|
||||
fn receive_server_hello_socks5(&mut self) -> std::io::Result<()> {
|
||||
let response = handshake::Response::retrieve_from_stream(&mut self.server_inbuf.clone());
|
||||
if let Err(e) = &response {
|
||||
if let Err(e) = response {
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
log::trace!("receive_server_hello_socks5 needs more data \"{}\"...", e);
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(e.to_string().into());
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let respones = response?;
|
||||
|
@ -145,7 +146,7 @@ impl SocksProxyImpl {
|
|||
if auth_method != AuthMethod::NoAuth && self.credentials.is_none()
|
||||
|| (auth_method != AuthMethod::NoAuth && auth_method != AuthMethod::UserPass) && self.credentials.is_some()
|
||||
{
|
||||
return Err("SOCKS5 server requires an unsupported authentication method.".into());
|
||||
return Err(crate::Error::from("SOCKS5 server requires an unsupported authentication method.").into());
|
||||
}
|
||||
|
||||
self.state = if auth_method == AuthMethod::UserPass {
|
||||
|
@ -156,75 +157,77 @@ impl SocksProxyImpl {
|
|||
self.state_change()
|
||||
}
|
||||
|
||||
fn receive_server_hello(&mut self) -> Result<(), Error> {
|
||||
fn receive_server_hello(&mut self) -> std::io::Result<()> {
|
||||
match self.version {
|
||||
Version::V4 => self.receive_server_hello_socks4(),
|
||||
Version::V5 => self.receive_server_hello_socks5(),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_auth_data(&mut self) -> Result<(), Error> {
|
||||
fn send_auth_data(&mut self) -> std::io::Result<()> {
|
||||
let tmp = UserKey::default();
|
||||
let credentials = self.credentials.as_ref().unwrap_or(&tmp);
|
||||
let request = password_method::Request::new(&credentials.username, &credentials.password);
|
||||
request.write_to_stream(&mut self.server_outbuf)?;
|
||||
self.state = SocksState::ReceiveAuthResponse;
|
||||
self.state_change()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_auth_data(&mut self) -> Result<(), Error> {
|
||||
fn receive_auth_data(&mut self) -> std::io::Result<()> {
|
||||
use password_method::Response;
|
||||
let response = Response::retrieve_from_stream(&mut self.server_inbuf.clone());
|
||||
if let Err(e) = &response {
|
||||
if let Err(e) = response {
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
log::trace!("receive_auth_data needs more data \"{}\"...", e);
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(e.to_string().into());
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let response = response?;
|
||||
self.server_inbuf.drain(0..response.len());
|
||||
if response.status != password_method::Status::Succeeded {
|
||||
return Err(format!("SOCKS authentication failed: {:?}", response.status).into());
|
||||
return Err(crate::Error::from(format!("SOCKS authentication failed: {:?}", response.status)).into());
|
||||
}
|
||||
self.state = SocksState::SendRequest;
|
||||
self.state_change()
|
||||
}
|
||||
|
||||
fn send_request_socks5(&mut self) -> Result<(), Error> {
|
||||
fn send_request_socks5(&mut self) -> std::io::Result<()> {
|
||||
let addr = if self.command == protocol::Command::UdpAssociate {
|
||||
Address::unspecified()
|
||||
} else if let Some(domain_name) = &self.domain_name {
|
||||
Address::DomainAddress(domain_name.clone(), self.info.dst.port())
|
||||
} else {
|
||||
self.info.dst.clone()
|
||||
self.info.dst.into()
|
||||
};
|
||||
protocol::Request::new(self.command, addr).write_to_stream(&mut self.server_outbuf)?;
|
||||
self.state = SocksState::ReceiveResponse;
|
||||
self.state_change()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receive_connection_status(&mut self) -> Result<(), Error> {
|
||||
fn receive_connection_status(&mut self) -> std::io::Result<()> {
|
||||
let response = protocol::Response::retrieve_from_stream(&mut self.server_inbuf.clone());
|
||||
if let Err(e) = &response {
|
||||
if let Err(e) = response {
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
log::trace!("receive_connection_status needs more data \"{}\"...", e);
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(e.to_string().into());
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
let response = response?;
|
||||
self.server_inbuf.drain(0..response.len());
|
||||
if response.reply != protocol::Reply::Succeeded {
|
||||
return Err(format!("SOCKS connection failed: {}", response.reply).into());
|
||||
return Err(crate::Error::from(format!("SOCKS connection failed: {}", response.reply)).into());
|
||||
}
|
||||
if self.command == protocol::Command::UdpAssociate {
|
||||
self.udp_associate = Some(SocketAddr::try_from(&response.address)?);
|
||||
log::trace!("UDP associate recieved address {}", response.address);
|
||||
// log::trace!("UDP associate recieved address {}", response.address);
|
||||
}
|
||||
|
||||
self.state = SocksState::Established;
|
||||
self.state_change()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn relay_traffic(&mut self) -> Result<(), Error> {
|
||||
|
@ -235,31 +238,37 @@ impl SocksProxyImpl {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn state_change(&mut self) -> Result<(), Error> {
|
||||
fn state_change(&mut self) -> std::io::Result<()> {
|
||||
match self.state {
|
||||
SocksState::ServerHello => self.receive_server_hello(),
|
||||
SocksState::ServerHello => self.receive_server_hello()?,
|
||||
|
||||
SocksState::SendAuthData => self.send_auth_data(),
|
||||
SocksState::SendAuthData => self.send_auth_data()?,
|
||||
|
||||
SocksState::ReceiveAuthResponse => self.receive_auth_data(),
|
||||
SocksState::ReceiveAuthResponse => self.receive_auth_data()?,
|
||||
|
||||
SocksState::SendRequest => self.send_request_socks5(),
|
||||
SocksState::SendRequest => self.send_request_socks5()?,
|
||||
|
||||
SocksState::ReceiveResponse => self.receive_connection_status(),
|
||||
SocksState::ReceiveResponse => self.receive_connection_status()?,
|
||||
|
||||
SocksState::Established => self.relay_traffic(),
|
||||
SocksState::Established => self.relay_traffic()?,
|
||||
|
||||
_ => Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ProxyHandler for SocksProxyImpl {
|
||||
fn get_connection_info(&self) -> &ConnectionInfo {
|
||||
&self.info
|
||||
fn get_session_info(&self) -> SessionInfo {
|
||||
self.info
|
||||
}
|
||||
|
||||
fn push_data(&mut self, event: IncomingDataEvent<'_>) -> Result<(), Error> {
|
||||
fn get_domain_name(&self) -> Option<String> {
|
||||
self.domain_name.clone()
|
||||
}
|
||||
|
||||
async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()> {
|
||||
let IncomingDataEvent { direction, buffer } = event;
|
||||
match direction {
|
||||
IncomingDirection::FromServer => {
|
||||
|
@ -296,16 +305,10 @@ impl ProxyHandler for SocksProxyImpl {
|
|||
self.state == SocksState::Established
|
||||
}
|
||||
|
||||
fn data_len(&self, dir: Direction) -> usize {
|
||||
fn data_len(&self, dir: OutgoingDirection) -> usize {
|
||||
match dir {
|
||||
Direction::Incoming(incoming) => match incoming {
|
||||
IncomingDirection::FromServer => self.server_inbuf.len(),
|
||||
IncomingDirection::FromClient => self.client_inbuf.len(),
|
||||
},
|
||||
Direction::Outgoing(outgoing) => match outgoing {
|
||||
OutgoingDirection::ToServer => self.server_outbuf.len(),
|
||||
OutgoingDirection::ToClient => self.client_outbuf.len(),
|
||||
},
|
||||
OutgoingDirection::ToServer => self.server_outbuf.len(),
|
||||
OutgoingDirection::ToClient => self.client_outbuf.len(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,12 +327,24 @@ pub(crate) struct SocksProxyManager {
|
|||
version: Version,
|
||||
}
|
||||
|
||||
impl ConnectionManager for SocksProxyManager {
|
||||
fn new_proxy_handler(&self, info: &ConnectionInfo, udp_associate: bool) -> Result<Box<dyn ProxyHandler>> {
|
||||
#[async_trait::async_trait]
|
||||
impl ProxyHandlerManager for SocksProxyManager {
|
||||
async fn new_proxy_handler(
|
||||
&self,
|
||||
info: SessionInfo,
|
||||
domain_name: Option<String>,
|
||||
udp_associate: bool,
|
||||
) -> std::io::Result<Arc<Mutex<dyn ProxyHandler>>> {
|
||||
use socks5_impl::protocol::Command::{Connect, UdpAssociate};
|
||||
let command = if udp_associate { UdpAssociate } else { Connect };
|
||||
let credentials = self.credentials.clone();
|
||||
Ok(Box::new(SocksProxyImpl::new(info, credentials, self.version, command)?))
|
||||
Ok(Arc::new(Mutex::new(SocksProxyImpl::new(
|
||||
info,
|
||||
domain_name,
|
||||
credentials,
|
||||
self.version,
|
||||
command,
|
||||
)?)))
|
||||
}
|
||||
|
||||
fn get_server_addr(&self) -> SocketAddr {
|
||||
|
|
1338
src/tun2proxy.rs
1338
src/tun2proxy.rs
File diff suppressed because it is too large
Load diff
22
src/util.rs
22
src/util.rs
|
@ -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,8 +1,5 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use crate::error::Result;
|
||||
use hashlink::{linked_hash_map::RawEntryMut, LruCache};
|
||||
use smoltcp::wire::Ipv4Cidr;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::TryInto,
|
||||
|
@ -18,6 +15,9 @@ struct NameCacheEntry {
|
|||
expiry: Instant,
|
||||
}
|
||||
|
||||
/// A virtual DNS server which allocates IP addresses to clients.
|
||||
/// The IP addresses are in the range of private IP addresses.
|
||||
/// The DNS server is implemented as a LRU cache.
|
||||
pub struct VirtualDns {
|
||||
lru_cache: LruCache<IpAddr, NameCacheEntry>,
|
||||
name_to_ip: HashMap<String, IpAddr>,
|
||||
|
@ -29,13 +29,16 @@ pub struct VirtualDns {
|
|||
impl Default for VirtualDns {
|
||||
fn default() -> Self {
|
||||
let start_addr = Ipv4Addr::from_str("198.18.0.0").unwrap();
|
||||
let cidr = Ipv4Cidr::new(start_addr.into(), 15);
|
||||
let prefix_len = 15;
|
||||
|
||||
let network_addr = calculate_network_addr(start_addr, prefix_len);
|
||||
let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len);
|
||||
|
||||
Self {
|
||||
next_addr: start_addr.into(),
|
||||
name_to_ip: HashMap::default(),
|
||||
network_addr: IpAddr::from(cidr.network().address().into_address()),
|
||||
broadcast_addr: IpAddr::from(cidr.broadcast().unwrap().into_address()),
|
||||
network_addr: IpAddr::from(network_addr),
|
||||
broadcast_addr: IpAddr::from(broadcast_addr),
|
||||
lru_cache: LruCache::new_unbounded(),
|
||||
}
|
||||
}
|
||||
|
@ -46,13 +49,14 @@ impl VirtualDns {
|
|||
VirtualDns::default()
|
||||
}
|
||||
|
||||
pub fn receive_query(&mut self, data: &[u8]) -> Result<Vec<u8>> {
|
||||
/// Returns the DNS response to send back to the client.
|
||||
pub fn generate_query(&mut self, data: &[u8]) -> Result<(Vec<u8>, String, IpAddr)> {
|
||||
use crate::dns;
|
||||
let message = dns::parse_data_to_dns_message(data, false)?;
|
||||
let qname = dns::extract_domain_from_dns_message(&message)?;
|
||||
let ip = self.allocate_ip(qname.clone())?;
|
||||
let message = dns::build_dns_response(message, &qname, ip, 5)?;
|
||||
Ok(message.to_vec()?)
|
||||
Ok((message.to_vec()?, qname, ip))
|
||||
}
|
||||
|
||||
fn increment_ip(addr: IpAddr) -> Result<IpAddr> {
|
||||
|
@ -140,3 +144,30 @@ impl VirtualDns {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_network_addr(ip: std::net::Ipv4Addr, prefix_len: u8) -> std::net::Ipv4Addr {
|
||||
let mask = (!0u32) << (32 - prefix_len);
|
||||
let ip_u32 = u32::from_be_bytes(ip.octets());
|
||||
std::net::Ipv4Addr::from((ip_u32 & mask).to_be_bytes())
|
||||
}
|
||||
|
||||
fn calculate_broadcast_addr(ip: std::net::Ipv4Addr, prefix_len: u8) -> std::net::Ipv4Addr {
|
||||
let mask = (!0u32) >> prefix_len;
|
||||
let ip_u32 = u32::from_be_bytes(ip.octets());
|
||||
std::net::Ipv4Addr::from((ip_u32 | mask).to_be_bytes())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cidr_addr() {
|
||||
let start_addr = Ipv4Addr::from_str("198.18.0.0").unwrap();
|
||||
let prefix_len = 15;
|
||||
let network_addr = calculate_network_addr(start_addr, prefix_len);
|
||||
let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len);
|
||||
assert_eq!(network_addr, Ipv4Addr::from_str("198.18.0.0").unwrap());
|
||||
assert_eq!(broadcast_addr, Ipv4Addr::from_str("198.19.255.255").unwrap());
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
151
tests/proxy.rs
151
tests/proxy.rs
|
@ -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…
Add table
Reference in a new issue