Compare commits
14 Commits
remove_loc
...
v2.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84bfac144c | ||
|
|
9eddb4b072 | ||
|
|
4fca0f40fe | ||
|
|
43b9e6e6e9 | ||
|
|
583c768f40 | ||
|
|
b1b2421561 | ||
|
|
3d610c0f0f | ||
|
|
2ec88da823 | ||
|
|
5514de1187 | ||
|
|
e70eed74e2 | ||
|
|
7dc5988620 | ||
|
|
354a4e1d7b | ||
|
|
5409c5bbe7 | ||
|
|
33ff9554cd |
@@ -19,6 +19,10 @@ SYSROOT = "/usr/local/ohos-sdk/linux/native/sysroot"
|
|||||||
linker = "aarch64-unknown-linux-musl-gcc"
|
linker = "aarch64-unknown-linux-musl-gcc"
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
|
[target.riscv64gc-unknown-linux-musl]
|
||||||
|
linker = "riscv64-unknown-linux-musl-gcc"
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
[target.'cfg(all(windows, target_env = "msvc"))']
|
[target.'cfg(all(windows, target_env = "msvc"))']
|
||||||
rustflags = ["-C", "target-feature=+crt-static"]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
|
||||||
|
|||||||
7
.github/workflows/core.yml
vendored
7
.github/workflows/core.yml
vendored
@@ -83,6 +83,9 @@ jobs:
|
|||||||
- TARGET: x86_64-unknown-linux-musl
|
- TARGET: x86_64-unknown-linux-musl
|
||||||
OS: ubuntu-22.04
|
OS: ubuntu-22.04
|
||||||
ARTIFACT_NAME: linux-x86_64
|
ARTIFACT_NAME: linux-x86_64
|
||||||
|
- TARGET: riscv64gc-unknown-linux-musl
|
||||||
|
OS: ubuntu-22.04
|
||||||
|
ARTIFACT_NAME: linux-riscv64
|
||||||
- TARGET: mips-unknown-linux-musl
|
- TARGET: mips-unknown-linux-musl
|
||||||
OS: ubuntu-22.04
|
OS: ubuntu-22.04
|
||||||
ARTIFACT_NAME: linux-mips
|
ARTIFACT_NAME: linux-mips
|
||||||
@@ -189,6 +192,8 @@ jobs:
|
|||||||
if [[ $OS =~ ^windows.*$ ]]; then
|
if [[ $OS =~ ^windows.*$ ]]; then
|
||||||
SUFFIX=.exe
|
SUFFIX=.exe
|
||||||
CORE_FEATURES="--features=mimalloc"
|
CORE_FEATURES="--features=mimalloc"
|
||||||
|
elif [[ $TARGET =~ ^riscv64.*$ ]]; then
|
||||||
|
CORE_FEATURES="--features=mimalloc"
|
||||||
else
|
else
|
||||||
CORE_FEATURES="--features=jemalloc"
|
CORE_FEATURES="--features=jemalloc"
|
||||||
fi
|
fi
|
||||||
@@ -255,7 +260,7 @@ jobs:
|
|||||||
TAG=$GITHUB_SHA
|
TAG=$GITHUB_SHA
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^.*freebsd$ && ! $TARGET =~ ^loongarch.*$ ]]; then
|
if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^.*freebsd$ && ! $TARGET =~ ^loongarch.*$ && ! $TARGET =~ ^riscv64.*$ ]]; then
|
||||||
UPX_VERSION=4.2.4
|
UPX_VERSION=4.2.4
|
||||||
curl -L https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-amd64_linux.tar.xz -s | tar xJvf -
|
curl -L https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-amd64_linux.tar.xz -s | tar xJvf -
|
||||||
cp upx-${UPX_VERSION}-amd64_linux/upx .
|
cp upx-${UPX_VERSION}-amd64_linux/upx .
|
||||||
|
|||||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
image_tag:
|
image_tag:
|
||||||
description: 'Tag for this image build'
|
description: 'Tag for this image build'
|
||||||
type: string
|
type: string
|
||||||
default: 'v2.4.0'
|
default: 'v2.4.1'
|
||||||
required: true
|
required: true
|
||||||
mark_latest:
|
mark_latest:
|
||||||
description: 'Mark this image as latest'
|
description: 'Mark this image as latest'
|
||||||
|
|||||||
2
.github/workflows/install_rust.sh
vendored
2
.github/workflows/install_rust.sh
vendored
@@ -15,6 +15,8 @@ if [[ $OS =~ ^ubuntu.*$ ]]; then
|
|||||||
# if target is mips or mipsel, we should use soft-float version of musl
|
# if target is mips or mipsel, we should use soft-float version of musl
|
||||||
if [[ $TARGET =~ ^mips.*$ || $TARGET =~ ^mipsel.*$ ]]; then
|
if [[ $TARGET =~ ^mips.*$ || $TARGET =~ ^mipsel.*$ ]]; then
|
||||||
MUSL_TARGET=${TARGET}sf
|
MUSL_TARGET=${TARGET}sf
|
||||||
|
elif [[ $TARGET =~ ^riscv64gc-.*$ ]]; then
|
||||||
|
MUSL_TARGET=${TARGET/#riscv64gc-/riscv64-}
|
||||||
fi
|
fi
|
||||||
if [[ $MUSL_TARGET =~ musl ]]; then
|
if [[ $MUSL_TARGET =~ musl ]]; then
|
||||||
mkdir -p ./musl_gcc
|
mkdir -p ./musl_gcc
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: 'Version for this release'
|
description: 'Version for this release'
|
||||||
type: string
|
type: string
|
||||||
default: 'v2.4.0'
|
default: 'v2.4.1'
|
||||||
required: true
|
required: true
|
||||||
make_latest:
|
make_latest:
|
||||||
description: 'Mark this release as latest'
|
description: 'Mark this release as latest'
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@ target-*/
|
|||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
/.idea
|
/.idea
|
||||||
|
/.direnv/
|
||||||
|
|
||||||
# perf & flamegraph
|
# perf & flamegraph
|
||||||
perf.data
|
perf.data
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ Thank you for your interest in contributing to EasyTier! This document provides
|
|||||||
# Core build dependencies
|
# Core build dependencies
|
||||||
sudo apt-get update && sudo apt-get install -y \
|
sudo apt-get update && sudo apt-get install -y \
|
||||||
musl-tools \
|
musl-tools \
|
||||||
libappindicator3-dev \
|
|
||||||
llvm \
|
llvm \
|
||||||
clang \
|
clang \
|
||||||
protobuf-compiler
|
protobuf-compiler
|
||||||
@@ -53,6 +52,7 @@ sudo apt install -y \
|
|||||||
librsvg2-dev \
|
librsvg2-dev \
|
||||||
libxdo-dev \
|
libxdo-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
|
libappindicator3-dev \
|
||||||
patchelf
|
patchelf
|
||||||
|
|
||||||
# Testing dependencies
|
# Testing dependencies
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
# 核心构建依赖
|
# 核心构建依赖
|
||||||
sudo apt-get update && sudo apt-get install -y \
|
sudo apt-get update && sudo apt-get install -y \
|
||||||
musl-tools \
|
musl-tools \
|
||||||
libappindicator3-dev \
|
|
||||||
llvm \
|
llvm \
|
||||||
clang \
|
clang \
|
||||||
protobuf-compiler
|
protobuf-compiler
|
||||||
@@ -61,6 +60,7 @@ sudo apt install -y \
|
|||||||
librsvg2-dev \
|
librsvg2-dev \
|
||||||
libxdo-dev \
|
libxdo-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
|
libappindicator3-dev \
|
||||||
patchelf
|
patchelf
|
||||||
|
|
||||||
# 测试依赖
|
# 测试依赖
|
||||||
|
|||||||
25
Cargo.lock
generated
25
Cargo.lock
generated
@@ -1876,7 +1876,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users 0.5.0",
|
"redox_users 0.5.0",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2119,9 +2119,10 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"dunce",
|
"dunce",
|
||||||
"easytier",
|
"easytier",
|
||||||
"elevated-command",
|
|
||||||
"gethostname 1.0.2",
|
"gethostname 1.0.2",
|
||||||
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"security-framework-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
@@ -2137,6 +2138,8 @@ dependencies = [
|
|||||||
"thunk-rs",
|
"thunk-rs",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"winapi",
|
||||||
|
"windows 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2207,20 +2210,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "elevated-command"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "54c410eccdcc5b759704fdb6a792afe6b01ab8a062e2c003ff2567e2697a94aa"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"base64 0.21.7",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"winapi",
|
|
||||||
"windows 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "3.0.5"
|
version = "3.0.5"
|
||||||
@@ -7557,9 +7546,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.7"
|
version = "0.5.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
|
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -105,9 +105,9 @@ After successful execution, you can check the network status using `easytier-cli
|
|||||||
```text
|
```text
|
||||||
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
|
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
|
||||||
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
|
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
|
||||||
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.0-70e69a38~ |
|
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.1-70e69a38~ |
|
||||||
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.0-70e69a38~ |
|
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.1-70e69a38~ |
|
||||||
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.0-70e69a38~ |
|
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.1-70e69a38~ |
|
||||||
```
|
```
|
||||||
|
|
||||||
You can test connectivity between nodes:
|
You can test connectivity between nodes:
|
||||||
@@ -302,14 +302,18 @@ CDN acceleration and security protection for this project are sponsored by Tence
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Special thanks to [Langlang Cloud](https://langlang.cloud/) for sponsoring our public servers.
|
Special thanks to [Langlang Cloud](https://langlangy.cn/?i26c5a5) and [RainCloud](https://www.rainyun.com/NjM0NzQ1_) for sponsoring our public servers.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://langlangy.cn/?i26c5a5" target="_blank">
|
<a href="https://langlangy.cn/?i26c5a5" target="_blank">
|
||||||
<img src="assets/langlang.png" width="200">
|
<img src="assets/langlang.png" width="200">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://langlangy.cn/?i26c5a5" target="_blank">
|
||||||
|
<img src="assets/raincloud.png" width="200">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
If you find EasyTier helpful, please consider sponsoring us. Software development and maintenance require a lot of time and effort, and your sponsorship will help us better maintain and improve EasyTier.
|
If you find EasyTier helpful, please consider sponsoring us. Software development and maintenance require a lot of time and effort, and your sponsorship will help us better maintain and improve EasyTier.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|||||||
11
README_CN.md
11
README_CN.md
@@ -106,9 +106,9 @@ sudo easytier-core -d --network-name abc --network-secret abc -p tcp://public.ea
|
|||||||
```text
|
```text
|
||||||
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
|
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
|
||||||
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
|
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
|
||||||
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.0-70e69a38~ |
|
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.1-70e69a38~ |
|
||||||
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.0-70e69a38~ |
|
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.1-70e69a38~ |
|
||||||
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.0-70e69a38~ |
|
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.1-70e69a38~ |
|
||||||
```
|
```
|
||||||
|
|
||||||
您可以测试节点之间的连通性:
|
您可以测试节点之间的连通性:
|
||||||
@@ -303,12 +303,15 @@ EasyTier 在 [LGPL-3.0](https://github.com/EasyTier/EasyTier/blob/main/LICENSE)
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
特别感谢 [浪浪云](https://langlang.cloud/) 赞助我们的公共服务器。
|
特别感谢 [浪浪云](https://langlangy.cn/?i26c5a5) 和 [雨云](https://www.rainyun.com/NjM0NzQ1_) 赞助我们的公共服务器。
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://langlangy.cn/?i26c5a5" target="_blank">
|
<a href="https://langlangy.cn/?i26c5a5" target="_blank">
|
||||||
<img src="assets/langlang.png" width="200">
|
<img src="assets/langlang.png" width="200">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://langlangy.cn/?i26c5a5" target="_blank">
|
||||||
|
<img src="assets/raincloud.png" width="200">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
如果您觉得 EasyTier 有帮助,请考虑赞助我们。软件开发和维护需要大量的时间和精力,您的赞助将帮助我们更好地维护和改进 EasyTier。
|
如果您觉得 EasyTier 有帮助,请考虑赞助我们。软件开发和维护需要大量的时间和精力,您的赞助将帮助我们更好地维护和改进 EasyTier。
|
||||||
|
|||||||
BIN
assets/raincloud.png
Normal file
BIN
assets/raincloud.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -1,6 +1,6 @@
|
|||||||
id=easytier_magisk
|
id=easytier_magisk
|
||||||
name=EasyTier_Magisk
|
name=EasyTier_Magisk
|
||||||
version=v2.4.0
|
version=v2.4.1
|
||||||
versionCode=1
|
versionCode=1
|
||||||
author=EasyTier
|
author=EasyTier
|
||||||
description=easytier magisk module @EasyTier(https://github.com/EasyTier/EasyTier)
|
description=easytier magisk module @EasyTier(https://github.com/EasyTier/EasyTier)
|
||||||
|
|||||||
2
easytier-contrib/easytier-ohrs/Cargo.lock
generated
2
easytier-contrib/easytier-ohrs/Cargo.lock
generated
@@ -1010,7 +1010,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "easytier"
|
name = "easytier"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
source = "git+https://github.com/EasyTier/EasyTier.git#a4bb555fac1046d0099c44676fa9d0d8cca55c99"
|
source = "git+https://github.com/EasyTier/EasyTier.git#a4bb555fac1046d0099c44676fa9d0d8cca55c99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "easytier-gui",
|
"name": "easytier-gui",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.4.0",
|
"version": "2.4.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
description = "EasyTier GUI"
|
description = "EasyTier GUI"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -40,7 +40,6 @@ chrono = { version = "0.4.37", features = ["serde"] }
|
|||||||
|
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
dashmap = "6.0"
|
dashmap = "6.0"
|
||||||
elevated-command = "1.1.2"
|
|
||||||
gethostname = "1.0.2"
|
gethostname = "1.0.2"
|
||||||
|
|
||||||
dunce = "1.0.4"
|
dunce = "1.0.4"
|
||||||
@@ -54,6 +53,15 @@ tauri-plugin-os = "2.3.0"
|
|||||||
tauri-plugin-autostart = "2.5.0"
|
tauri-plugin-autostart = "2.5.0"
|
||||||
uuid = "1.17.0"
|
uuid = "1.17.0"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
windows = { version = "0.52", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||||
|
winapi = { version = "0.3.9", features = ["securitybaseapi", "processthreadsapi"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_family = "unix")'.dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
security-framework-sys = "2.9.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||||
|
|||||||
97
easytier-gui/src-tauri/src/elevate/linux.rs
Normal file
97
easytier-gui/src-tauri/src/elevate/linux.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Luis Liu. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
use super::Command;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use std::env;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Command as StdCommand, Output};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// The implementation of state check and elevated executing varies on each platform
|
||||||
|
impl Command {
|
||||||
|
/// Check the state the current program running
|
||||||
|
///
|
||||||
|
/// Return `true` if the program is running as root, otherwise false
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let is_elevated = Command::is_elevated();
|
||||||
|
///
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn is_elevated() -> bool {
|
||||||
|
let uid = unsafe { libc::getuid() };
|
||||||
|
if uid == 0 {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompting the user with a graphical OS dialog for the root password,
|
||||||
|
/// excuting the command with escalated privileges, and return the output
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// let output = elevated_cmd.output().unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn output(&self) -> Result<Output> {
|
||||||
|
let pkexec = PathBuf::from_str("/bin/pkexec")?;
|
||||||
|
let mut command = StdCommand::new(pkexec);
|
||||||
|
let display = env::var("DISPLAY");
|
||||||
|
let xauthority = env::var("XAUTHORITY");
|
||||||
|
let home = env::var("HOME");
|
||||||
|
|
||||||
|
command.arg("--disable-internal-agent");
|
||||||
|
if display.is_ok() || xauthority.is_ok() || home.is_ok() {
|
||||||
|
command.arg("env");
|
||||||
|
if let Ok(display) = display {
|
||||||
|
command.arg(format!("DISPLAY={}", display));
|
||||||
|
}
|
||||||
|
if let Ok(xauthority) = xauthority {
|
||||||
|
command.arg(format!("XAUTHORITY={}", xauthority));
|
||||||
|
}
|
||||||
|
if let Ok(home) = home {
|
||||||
|
command.arg(format!("HOME={}", home));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.cmd.get_envs().any(|(_, v)| v.is_some()) {
|
||||||
|
command.arg("env");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (k, v) in self.cmd.get_envs() {
|
||||||
|
if let Some(value) = v {
|
||||||
|
command.arg(format!(
|
||||||
|
"{}={}",
|
||||||
|
k.to_str().ok_or(anyhow!("invalid key"))?,
|
||||||
|
value.to_str().ok_or(anyhow!("invalid value"))?
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command.arg(self.cmd.get_program());
|
||||||
|
let args: Vec<&OsStr> = self.cmd.get_args().collect();
|
||||||
|
if !args.is_empty() {
|
||||||
|
command.args(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = command.output()?;
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
182
easytier-gui/src-tauri/src/elevate/macos.rs
Normal file
182
easytier-gui/src-tauri/src/elevate/macos.rs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Luis Liu. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// Thanks to https://github.com/jorangreef/sudo-prompt/blob/master/index.js
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2015 Joran Dirk Greef
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// ...
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
use super::Command;
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{ExitStatus, Output};
|
||||||
|
|
||||||
|
use std::ffi::{CString, OsString};
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use libc::{fcntl, fileno, waitpid, EINTR, F_GETOWN};
|
||||||
|
use security_framework_sys::authorization::{
|
||||||
|
errAuthorizationSuccess, kAuthorizationFlagDefaults, kAuthorizationFlagDestroyRights,
|
||||||
|
AuthorizationCreate, AuthorizationExecuteWithPrivileges, AuthorizationFree, AuthorizationRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENV_PATH: &str = "PATH";
|
||||||
|
|
||||||
|
fn get_exe_path<P: AsRef<Path>>(exe_name: P) -> Option<PathBuf> {
|
||||||
|
let exe_name = exe_name.as_ref();
|
||||||
|
if exe_name.has_root() {
|
||||||
|
return Some(exe_name.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(abs_path) = exe_name.canonicalize() {
|
||||||
|
if abs_path.is_file() {
|
||||||
|
return Some(abs_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env::var_os(ENV_PATH).and_then(|paths| {
|
||||||
|
env::split_paths(&paths)
|
||||||
|
.filter_map(|dir| {
|
||||||
|
let full_path = dir.join(exe_name);
|
||||||
|
if full_path.is_file() {
|
||||||
|
Some(full_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! make_cstring {
|
||||||
|
($s:expr) => {
|
||||||
|
match CString::new($s.as_bytes()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::Other, "null byte in string"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn gui_runas(prog: *const i8, argv: *const *const i8) -> i32 {
|
||||||
|
let mut authref: AuthorizationRef = ptr::null_mut();
|
||||||
|
let mut pipe: *mut libc::FILE = ptr::null_mut();
|
||||||
|
|
||||||
|
if AuthorizationCreate(
|
||||||
|
ptr::null(),
|
||||||
|
ptr::null(),
|
||||||
|
kAuthorizationFlagDefaults,
|
||||||
|
&mut authref,
|
||||||
|
) != errAuthorizationSuccess
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if AuthorizationExecuteWithPrivileges(
|
||||||
|
authref,
|
||||||
|
prog,
|
||||||
|
kAuthorizationFlagDefaults,
|
||||||
|
argv as *const *mut _,
|
||||||
|
&mut pipe,
|
||||||
|
) != errAuthorizationSuccess
|
||||||
|
{
|
||||||
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pid = fcntl(fileno(pipe), F_GETOWN, 0);
|
||||||
|
let mut status = 0;
|
||||||
|
loop {
|
||||||
|
let r = waitpid(pid, &mut status, 0);
|
||||||
|
if r == -1 && io::Error::last_os_error().raw_os_error() == Some(EINTR) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationFree(authref, kAuthorizationFlagDestroyRights);
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runas_root_gui(cmd: &Command) -> io::Result<ExitStatus> {
|
||||||
|
let exe: OsString = match get_exe_path(&cmd.cmd.get_program()) {
|
||||||
|
Some(exe) => exe.into(),
|
||||||
|
None => unsafe {
|
||||||
|
return Ok(mem::transmute(!0));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let prog = make_cstring!(exe);
|
||||||
|
let mut args = vec![];
|
||||||
|
for arg in cmd.cmd.get_args() {
|
||||||
|
args.push(make_cstring!(arg))
|
||||||
|
}
|
||||||
|
let mut argv: Vec<_> = args.iter().map(|x| x.as_ptr()).collect();
|
||||||
|
argv.push(ptr::null());
|
||||||
|
|
||||||
|
unsafe { Ok(mem::transmute(gui_runas(prog.as_ptr(), argv.as_ptr()))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The implementation of state check and elevated executing varies on each platform
|
||||||
|
impl Command {
|
||||||
|
/// Check the state the current program running
|
||||||
|
///
|
||||||
|
/// Return `true` if the program is running as root, otherwise false
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let is_elevated = Command::is_elevated();
|
||||||
|
///
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn is_elevated() -> bool {
|
||||||
|
let uid = unsafe { libc::getuid() };
|
||||||
|
let euid = unsafe { libc::geteuid() };
|
||||||
|
|
||||||
|
match (uid, euid) {
|
||||||
|
(0, 0) => true,
|
||||||
|
(_, 0) => true,
|
||||||
|
(_, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompting the user with a graphical OS dialog for the root password,
|
||||||
|
/// excuting the command with escalated privileges, and return the output
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// let output = elevated_cmd.output().unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn output(&self) -> Result<Output> {
|
||||||
|
let status = runas_root_gui(self)?;
|
||||||
|
Ok(Output {
|
||||||
|
status,
|
||||||
|
stdout: Vec::new(),
|
||||||
|
stderr: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
182
easytier-gui/src-tauri/src/elevate/mod.rs
Normal file
182
easytier-gui/src-tauri/src/elevate/mod.rs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Luis Liu. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
use std::convert::From;
|
||||||
|
use std::process::Command as StdCommand;
|
||||||
|
|
||||||
|
/// Wrap of std::process::command and escalate privileges while executing
|
||||||
|
pub struct Command {
|
||||||
|
cmd: StdCommand,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
icon: Option<Vec<u8>>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command initialization shares the same logic across all the platforms
|
||||||
|
impl Command {
|
||||||
|
/// Constructs a new `Command` from a std::process::Command
|
||||||
|
/// instance, it would read the following configuration from
|
||||||
|
/// the instance while executing:
|
||||||
|
///
|
||||||
|
/// * The instance's path to the program
|
||||||
|
/// * The instance's arguments
|
||||||
|
/// * The instance's environment variables
|
||||||
|
///
|
||||||
|
/// So far, the new `Command` would only take the environment variables explicitly
|
||||||
|
/// set by std::process::Command::env and std::process::Command::env,
|
||||||
|
/// without the ones inherited from the parent process
|
||||||
|
///
|
||||||
|
/// And the environment variables would only be taken on Linux and MacOS,
|
||||||
|
/// they would be ignored on Windows
|
||||||
|
///
|
||||||
|
/// Current working directory would be the following while executing the command:
|
||||||
|
/// - %SystemRoot%\System32 on Windows
|
||||||
|
/// - /root on Linux
|
||||||
|
/// - $TMPDIR/sudo_prompt_applet/applet.app/Contents/MacOS on MacOS
|
||||||
|
///
|
||||||
|
/// To pass environment variables on Windows,
|
||||||
|
/// to inherit environment variables from the parent process and
|
||||||
|
/// to change the working directory will be supported in later versions
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
///
|
||||||
|
/// cmd.arg("some arg");
|
||||||
|
/// cmd.env("some key", "some value");
|
||||||
|
///
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn new(cmd: StdCommand) -> Self {
|
||||||
|
Self {
|
||||||
|
cmd,
|
||||||
|
icon: None,
|
||||||
|
name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the `Take`, returning the wrapped std::process::Command
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// let cmd = elevated_cmd.into_inner();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn into_inner(self) -> StdCommand {
|
||||||
|
self.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the underlying std::process::Command
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// let cmd = elevated_cmd.get_ref();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn get_ref(&self) -> &StdCommand {
|
||||||
|
&self.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the underlying std::process::Command
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// let cmd = elevated_cmd.get_mut();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn get_mut(&mut self) -> &mut StdCommand {
|
||||||
|
&mut self.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the `icon` for the pop-up graphical OS dialog
|
||||||
|
///
|
||||||
|
/// This method is only applicable on `MacOS`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// elevated_cmd.icon(include_bytes!("path to the icon").to_vec());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn icon(&mut self, icon: Vec<u8>) -> &mut Self {
|
||||||
|
self.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the name for the pop-up graphical OS dialog
|
||||||
|
///
|
||||||
|
/// This method is only applicable on `MacOS`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// elevated_cmd.name("some name".to_string());
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn name(&mut self, name: String) -> &mut Self {
|
||||||
|
self.name = Some(name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StdCommand> for Command {
|
||||||
|
/// Converts from a std::process::Command
|
||||||
|
///
|
||||||
|
/// It is similiar with the construct method
|
||||||
|
fn from(cmd: StdCommand) -> Self {
|
||||||
|
Self {
|
||||||
|
cmd,
|
||||||
|
icon: None,
|
||||||
|
name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod macos;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod windows;
|
||||||
114
easytier-gui/src-tauri/src/elevate/windows.rs
Normal file
114
easytier-gui/src-tauri/src/elevate/windows.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Luis Liu. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
use super::Command;
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::windows::process::ExitStatusExt;
|
||||||
|
use std::process::{ExitStatus, Output};
|
||||||
|
use winapi::shared::minwindef::{DWORD, LPVOID};
|
||||||
|
use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
|
||||||
|
use winapi::um::securitybaseapi::GetTokenInformation;
|
||||||
|
use winapi::um::winnt::{TokenElevation, HANDLE, TOKEN_ELEVATION, TOKEN_QUERY};
|
||||||
|
use windows::core::{w, HSTRING, PCWSTR};
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::UI::Shell::ShellExecuteW;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||||
|
|
||||||
|
/// The implementation of state check and elevated executing varies on each platform
|
||||||
|
impl Command {
|
||||||
|
/// Check the state the current program running
|
||||||
|
///
|
||||||
|
/// Return `true` if the program is running as root, otherwise false
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let is_elevated = Command::is_elevated();
|
||||||
|
///
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn is_elevated() -> bool {
|
||||||
|
// Thanks to https://stackoverflow.com/a/8196291
|
||||||
|
unsafe {
|
||||||
|
let mut current_token_ptr: HANDLE = mem::zeroed();
|
||||||
|
let mut token_elevation: TOKEN_ELEVATION = mem::zeroed();
|
||||||
|
let token_elevation_type_ptr: *mut TOKEN_ELEVATION = &mut token_elevation;
|
||||||
|
let mut size: DWORD = 0;
|
||||||
|
|
||||||
|
let result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut current_token_ptr);
|
||||||
|
|
||||||
|
if result != 0 {
|
||||||
|
let result = GetTokenInformation(
|
||||||
|
current_token_ptr,
|
||||||
|
TokenElevation,
|
||||||
|
token_elevation_type_ptr as LPVOID,
|
||||||
|
mem::size_of::<winapi::um::winnt::TOKEN_ELEVATION_TYPE>() as u32,
|
||||||
|
&mut size,
|
||||||
|
);
|
||||||
|
if result != 0 {
|
||||||
|
return token_elevation.TokenIsElevated != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prompting the user with a graphical OS dialog for the root password,
|
||||||
|
/// excuting the command with escalated privileges, and return the output
|
||||||
|
///
|
||||||
|
/// On Windows, according to https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew#return-value,
|
||||||
|
/// Output.status.code() shoudl be greater than 32 if the function succeeds,
|
||||||
|
/// otherwise the value indicates the cause of the failure
|
||||||
|
///
|
||||||
|
/// On Windows, Output.stdout and Output.stderr will always be empty as of now
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use elevated_command::Command;
|
||||||
|
/// use std::process::Command as StdCommand;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let mut cmd = StdCommand::new("path to the application");
|
||||||
|
/// let elevated_cmd = Command::new(cmd);
|
||||||
|
/// let output = elevated_cmd.output().unwrap();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn output(&self) -> Result<Output> {
|
||||||
|
let args = self
|
||||||
|
.cmd
|
||||||
|
.get_args()
|
||||||
|
.map(|c| c.to_str().unwrap().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
let parameters = if args.is_empty() {
|
||||||
|
HSTRING::new()
|
||||||
|
} else {
|
||||||
|
let arg_str = args.join(" ");
|
||||||
|
HSTRING::from(arg_str)
|
||||||
|
};
|
||||||
|
|
||||||
|
// according to https://stackoverflow.com/a/38034535
|
||||||
|
// the cwd always point to %SystemRoot%\System32 and cannot be changed by settting lpdirectory param
|
||||||
|
let r = unsafe {
|
||||||
|
ShellExecuteW(
|
||||||
|
HWND(0),
|
||||||
|
w!("runas"),
|
||||||
|
&HSTRING::from(self.cmd.get_program()),
|
||||||
|
&HSTRING::from(parameters),
|
||||||
|
PCWSTR::null(),
|
||||||
|
SW_HIDE,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Ok(Output {
|
||||||
|
status: ExitStatus::from_raw(r.0 as u32),
|
||||||
|
stdout: Vec::<u8>::new(),
|
||||||
|
stderr: Vec::<u8>::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
mod elevate;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use easytier::{
|
use easytier::{
|
||||||
@@ -128,7 +130,7 @@ fn toggle_window_visibility<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
fn check_sudo() -> bool {
|
fn check_sudo() -> bool {
|
||||||
let is_elevated = elevated_command::Command::is_elevated();
|
let is_elevated = elevate::Command::is_elevated();
|
||||||
if !is_elevated {
|
if !is_elevated {
|
||||||
let exe_path = std::env::var("APPIMAGE")
|
let exe_path = std::env::var("APPIMAGE")
|
||||||
.ok()
|
.ok()
|
||||||
@@ -139,7 +141,7 @@ fn check_sudo() -> bool {
|
|||||||
if args.contains(&AUTOSTART_ARG.to_owned()) {
|
if args.contains(&AUTOSTART_ARG.to_owned()) {
|
||||||
stdcmd.arg(AUTOSTART_ARG);
|
stdcmd.arg(AUTOSTART_ARG);
|
||||||
}
|
}
|
||||||
elevated_command::Command::new(stdcmd)
|
elevate::Command::new(stdcmd)
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to run elevated command");
|
.expect("Failed to run elevated command");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"createUpdaterArtifacts": false
|
"createUpdaterArtifacts": false
|
||||||
},
|
},
|
||||||
"productName": "easytier-gui",
|
"productName": "easytier-gui",
|
||||||
"version": "2.4.0",
|
"version": "2.4.1",
|
||||||
"identifier": "com.kkrainbow.easytier",
|
"identifier": "com.kkrainbow.easytier",
|
||||||
"plugins": {},
|
"plugins": {},
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "easytier-web"
|
name = "easytier-web"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "easytier"
|
|||||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||||
homepage = "https://github.com/EasyTier/EasyTier"
|
homepage = "https://github.com/EasyTier/EasyTier"
|
||||||
repository = "https://github.com/EasyTier/EasyTier"
|
repository = "https://github.com/EasyTier/EasyTier"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["kkrainbow"]
|
authors = ["kkrainbow"]
|
||||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||||
@@ -115,7 +115,7 @@ byteorder = "1.5.0"
|
|||||||
|
|
||||||
# for proxy
|
# for proxy
|
||||||
cidr = { version = "0.2.2", features = ["serde"] }
|
cidr = { version = "0.2.2", features = ["serde"] }
|
||||||
socket2 = "0.5.5"
|
socket2 = { version = "0.5.10", features = ["all"] }
|
||||||
|
|
||||||
# for hole punching
|
# for hole punching
|
||||||
stun_codec = "0.3.4"
|
stun_codec = "0.3.4"
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ use std::{
|
|||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::token_bucket::TokenBucket;
|
use crate::common::{config::ConfigLoader, global_ctx::ArcGlobalCtx, token_bucket::TokenBucket};
|
||||||
use crate::proto::acl::*;
|
use crate::proto::acl::*;
|
||||||
|
use anyhow::Context as _;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
@@ -470,6 +471,7 @@ impl AclProcessor {
|
|||||||
let rules = match chain_type {
|
let rules = match chain_type {
|
||||||
ChainType::Inbound => &self.inbound_rules,
|
ChainType::Inbound => &self.inbound_rules,
|
||||||
ChainType::Outbound => &self.outbound_rules,
|
ChainType::Outbound => &self.outbound_rules,
|
||||||
|
ChainType::Forward => &self.forward_rules,
|
||||||
_ => {
|
_ => {
|
||||||
return AclResult {
|
return AclResult {
|
||||||
action: Action::Drop,
|
action: Action::Drop,
|
||||||
@@ -992,6 +994,146 @@ impl AclStatKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AclRuleBuilder {
|
||||||
|
pub acl: Option<Acl>,
|
||||||
|
pub tcp_whitelist: Vec<String>,
|
||||||
|
pub udp_whitelist: Vec<String>,
|
||||||
|
pub whitelist_priority: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AclRuleBuilder {
|
||||||
|
fn parse_port_list(port_list: &[String]) -> anyhow::Result<Vec<String>> {
|
||||||
|
let mut ports = Vec::new();
|
||||||
|
|
||||||
|
for port_spec in port_list {
|
||||||
|
if port_spec.contains('-') {
|
||||||
|
// Handle port range like "8000-9000"
|
||||||
|
let parts: Vec<&str> = port_spec.split('-').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err(anyhow::anyhow!("Invalid port range format: {}", port_spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
let start: u16 = parts[0]
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid start port in range: {}", port_spec))?;
|
||||||
|
let end: u16 = parts[1]
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid end port in range: {}", port_spec))?;
|
||||||
|
|
||||||
|
if start > end {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Start port must be <= end port in range: {}",
|
||||||
|
port_spec
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// acl can handle port range
|
||||||
|
ports.push(port_spec.clone());
|
||||||
|
} else {
|
||||||
|
// Handle single port
|
||||||
|
let port: u16 = port_spec
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid port number: {}", port_spec))?;
|
||||||
|
ports.push(port.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_acl_from_whitelists(&mut self) -> anyhow::Result<()> {
|
||||||
|
if self.tcp_whitelist.is_empty() && self.udp_whitelist.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create inbound chain for whitelist rules
|
||||||
|
let mut inbound_chain = Chain {
|
||||||
|
name: "inbound_whitelist".to_string(),
|
||||||
|
chain_type: ChainType::Inbound as i32,
|
||||||
|
description: "Auto-generated inbound whitelist from CLI".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
rules: vec![],
|
||||||
|
default_action: Action::Drop as i32, // Default deny
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rule_priority = self.whitelist_priority.unwrap_or(1000u32);
|
||||||
|
|
||||||
|
// Add TCP whitelist rules
|
||||||
|
if !self.tcp_whitelist.is_empty() {
|
||||||
|
let tcp_ports = Self::parse_port_list(&self.tcp_whitelist)?;
|
||||||
|
let tcp_rule = Rule {
|
||||||
|
name: "tcp_whitelist".to_string(),
|
||||||
|
description: "Auto-generated TCP whitelist rule".to_string(),
|
||||||
|
priority: rule_priority,
|
||||||
|
enabled: true,
|
||||||
|
protocol: Protocol::Tcp as i32,
|
||||||
|
ports: tcp_ports,
|
||||||
|
source_ips: vec![],
|
||||||
|
destination_ips: vec![],
|
||||||
|
source_ports: vec![],
|
||||||
|
action: Action::Allow as i32,
|
||||||
|
rate_limit: 0,
|
||||||
|
burst_limit: 0,
|
||||||
|
stateful: true,
|
||||||
|
};
|
||||||
|
inbound_chain.rules.push(tcp_rule);
|
||||||
|
rule_priority -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add UDP whitelist rules
|
||||||
|
if !self.udp_whitelist.is_empty() {
|
||||||
|
let udp_ports = Self::parse_port_list(&self.udp_whitelist)?;
|
||||||
|
let udp_rule = Rule {
|
||||||
|
name: "udp_whitelist".to_string(),
|
||||||
|
description: "Auto-generated UDP whitelist rule".to_string(),
|
||||||
|
priority: rule_priority,
|
||||||
|
enabled: true,
|
||||||
|
protocol: Protocol::Udp as i32,
|
||||||
|
ports: udp_ports,
|
||||||
|
source_ips: vec![],
|
||||||
|
destination_ips: vec![],
|
||||||
|
source_ports: vec![],
|
||||||
|
action: Action::Allow as i32,
|
||||||
|
rate_limit: 0,
|
||||||
|
burst_limit: 0,
|
||||||
|
stateful: false,
|
||||||
|
};
|
||||||
|
inbound_chain.rules.push(udp_rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.acl.is_none() {
|
||||||
|
self.acl = Some(Acl::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let acl = self.acl.as_mut().unwrap();
|
||||||
|
|
||||||
|
if let Some(ref mut acl_v1) = acl.acl_v1 {
|
||||||
|
acl_v1.chains.push(inbound_chain);
|
||||||
|
} else {
|
||||||
|
acl.acl_v1 = Some(AclV1 {
|
||||||
|
chains: vec![inbound_chain],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_build(mut self) -> anyhow::Result<Option<Acl>> {
|
||||||
|
self.generate_acl_from_whitelists()?;
|
||||||
|
Ok(self.acl.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(global_ctx: &ArcGlobalCtx) -> anyhow::Result<Option<Acl>> {
|
||||||
|
let builder = AclRuleBuilder {
|
||||||
|
acl: global_ctx.config.get_acl(),
|
||||||
|
tcp_whitelist: global_ctx.config.get_tcp_whitelist(),
|
||||||
|
udp_whitelist: global_ctx.config.get_udp_whitelist(),
|
||||||
|
whitelist_priority: None,
|
||||||
|
};
|
||||||
|
builder.do_build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum AclStatType {
|
pub enum AclStatType {
|
||||||
Total,
|
Total,
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ pub trait ConfigLoader: Send + Sync {
|
|||||||
fn get_acl(&self) -> Option<Acl>;
|
fn get_acl(&self) -> Option<Acl>;
|
||||||
fn set_acl(&self, acl: Option<Acl>);
|
fn set_acl(&self, acl: Option<Acl>);
|
||||||
|
|
||||||
|
fn get_tcp_whitelist(&self) -> Vec<String>;
|
||||||
|
fn set_tcp_whitelist(&self, whitelist: Vec<String>);
|
||||||
|
|
||||||
|
fn get_udp_whitelist(&self) -> Vec<String>;
|
||||||
|
fn set_udp_whitelist(&self, whitelist: Vec<String>);
|
||||||
|
|
||||||
fn dump(&self) -> String;
|
fn dump(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +236,7 @@ pub struct VpnPortalConfig {
|
|||||||
pub wireguard_listen: SocketAddr,
|
pub wireguard_listen: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||||
pub struct PortForwardConfig {
|
pub struct PortForwardConfig {
|
||||||
pub bind_addr: SocketAddr,
|
pub bind_addr: SocketAddr,
|
||||||
pub dst_addr: SocketAddr,
|
pub dst_addr: SocketAddr,
|
||||||
@@ -299,6 +305,9 @@ struct Config {
|
|||||||
flags_struct: Option<Flags>,
|
flags_struct: Option<Flags>,
|
||||||
|
|
||||||
acl: Option<Acl>,
|
acl: Option<Acl>,
|
||||||
|
|
||||||
|
tcp_whitelist: Option<Vec<String>>,
|
||||||
|
udp_whitelist: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -665,6 +674,32 @@ impl ConfigLoader for TomlConfigLoader {
|
|||||||
self.config.lock().unwrap().acl = acl;
|
self.config.lock().unwrap().acl = acl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_tcp_whitelist(&self) -> Vec<String> {
|
||||||
|
self.config
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.tcp_whitelist
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_tcp_whitelist(&self, whitelist: Vec<String>) {
|
||||||
|
self.config.lock().unwrap().tcp_whitelist = Some(whitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_udp_whitelist(&self) -> Vec<String> {
|
||||||
|
self.config
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.udp_whitelist
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_udp_whitelist(&self, whitelist: Vec<String>) {
|
||||||
|
self.config.lock().unwrap().udp_whitelist = Some(whitelist);
|
||||||
|
}
|
||||||
|
|
||||||
fn dump(&self) -> String {
|
fn dump(&self) -> String {
|
||||||
let default_flags_json = serde_json::to_string(&gen_default_flags()).unwrap();
|
let default_flags_json = serde_json::to_string(&gen_default_flags()).unwrap();
|
||||||
let default_flags_hashmap =
|
let default_flags_hashmap =
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::DashSet;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{
|
sync::{
|
||||||
broadcast::{error::RecvError, Receiver},
|
broadcast::{error::RecvError, Receiver},
|
||||||
mpsc, Mutex,
|
mpsc,
|
||||||
},
|
},
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
time::timeout,
|
time::timeout,
|
||||||
@@ -32,7 +32,6 @@ use crate::{
|
|||||||
global_ctx::{ArcGlobalCtx, GlobalCtxEvent},
|
global_ctx::{ArcGlobalCtx, GlobalCtxEvent},
|
||||||
netns::NetNS,
|
netns::NetNS,
|
||||||
},
|
},
|
||||||
connector::set_bind_addr_for_peer_connector,
|
|
||||||
peers::peer_manager::PeerManager,
|
peers::peer_manager::PeerManager,
|
||||||
proto::cli::{
|
proto::cli::{
|
||||||
Connector, ConnectorManageRpc, ConnectorStatus, ListConnectorRequest,
|
Connector, ConnectorManageRpc, ConnectorStatus, ListConnectorRequest,
|
||||||
@@ -43,8 +42,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::create_connector_by_url;
|
use super::create_connector_by_url;
|
||||||
|
|
||||||
type MutexConnector = Arc<Mutex<Box<dyn TunnelConnector>>>;
|
type ConnectorMap = Arc<DashSet<String>>;
|
||||||
type ConnectorMap = Arc<DashMap<String, MutexConnector>>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct ReconnResult {
|
struct ReconnResult {
|
||||||
@@ -72,7 +70,7 @@ pub struct ManualConnectorManager {
|
|||||||
|
|
||||||
impl ManualConnectorManager {
|
impl ManualConnectorManager {
|
||||||
pub fn new(global_ctx: ArcGlobalCtx, peer_manager: Arc<PeerManager>) -> Self {
|
pub fn new(global_ctx: ArcGlobalCtx, peer_manager: Arc<PeerManager>) -> Self {
|
||||||
let connectors = Arc::new(DashMap::new());
|
let connectors = Arc::new(DashSet::new());
|
||||||
let tasks = JoinSet::new();
|
let tasks = JoinSet::new();
|
||||||
let event_subscriber = global_ctx.subscribe();
|
let event_subscriber = global_ctx.subscribe();
|
||||||
|
|
||||||
@@ -105,14 +103,11 @@ impl ManualConnectorManager {
|
|||||||
T: TunnelConnector + 'static,
|
T: TunnelConnector + 'static,
|
||||||
{
|
{
|
||||||
tracing::info!("add_connector: {}", connector.remote_url());
|
tracing::info!("add_connector: {}", connector.remote_url());
|
||||||
self.data.connectors.insert(
|
self.data.connectors.insert(connector.remote_url().into());
|
||||||
connector.remote_url().into(),
|
|
||||||
Arc::new(Mutex::new(Box::new(connector))),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_connector_by_url(&self, url: &str) -> Result<(), Error> {
|
pub async fn add_connector_by_url(&self, url: &str) -> Result<(), Error> {
|
||||||
self.add_connector(create_connector_by_url(url, &self.global_ctx, IpVersion::Both).await?);
|
self.data.connectors.insert(url.to_owned());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,16 +231,16 @@ impl ManualConnectorManager {
|
|||||||
for dead_url in dead_urls {
|
for dead_url in dead_urls {
|
||||||
let data_clone = data.clone();
|
let data_clone = data.clone();
|
||||||
let sender = reconn_result_send.clone();
|
let sender = reconn_result_send.clone();
|
||||||
let (_, connector) = data.connectors.remove(&dead_url).unwrap();
|
data.connectors.remove(&dead_url).unwrap();
|
||||||
let insert_succ = data.reconnecting.insert(dead_url.clone());
|
let insert_succ = data.reconnecting.insert(dead_url.clone());
|
||||||
assert!(insert_succ);
|
assert!(insert_succ);
|
||||||
|
|
||||||
tasks.lock().unwrap().spawn(async move {
|
tasks.lock().unwrap().spawn(async move {
|
||||||
let reconn_ret = Self::conn_reconnect(data_clone.clone(), dead_url.clone(), connector.clone()).await;
|
let reconn_ret = Self::conn_reconnect(data_clone.clone(), dead_url.clone() ).await;
|
||||||
let _ = sender.send(reconn_ret).await;
|
let _ = sender.send(reconn_ret).await;
|
||||||
|
|
||||||
data_clone.reconnecting.remove(&dead_url).unwrap();
|
data_clone.reconnecting.remove(&dead_url).unwrap();
|
||||||
data_clone.connectors.insert(dead_url.clone(), connector);
|
data_clone.connectors.insert(dead_url.clone());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tracing::info!("reconn_interval tick, done");
|
tracing::info!("reconn_interval tick, done");
|
||||||
@@ -323,25 +318,13 @@ impl ManualConnectorManager {
|
|||||||
async fn conn_reconnect_with_ip_version(
|
async fn conn_reconnect_with_ip_version(
|
||||||
data: Arc<ConnectorManagerData>,
|
data: Arc<ConnectorManagerData>,
|
||||||
dead_url: String,
|
dead_url: String,
|
||||||
connector: MutexConnector,
|
|
||||||
ip_version: IpVersion,
|
ip_version: IpVersion,
|
||||||
) -> Result<ReconnResult, Error> {
|
) -> Result<ReconnResult, Error> {
|
||||||
let ip_collector = data.global_ctx.get_ip_collector();
|
let connector =
|
||||||
|
create_connector_by_url(&dead_url, &data.global_ctx.clone(), ip_version).await?;
|
||||||
|
|
||||||
connector.lock().await.set_ip_version(ip_version);
|
data.global_ctx
|
||||||
|
.issue_event(GlobalCtxEvent::Connecting(connector.remote_url().clone()));
|
||||||
if data.global_ctx.config.get_flags().bind_device {
|
|
||||||
set_bind_addr_for_peer_connector(
|
|
||||||
connector.lock().await.as_mut(),
|
|
||||||
ip_version == IpVersion::V4,
|
|
||||||
&ip_collector,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
|
||||||
connector.lock().await.remote_url().clone(),
|
|
||||||
));
|
|
||||||
tracing::info!("reconnect try connect... conn: {:?}", connector);
|
tracing::info!("reconnect try connect... conn: {:?}", connector);
|
||||||
let Some(pm) = data.peer_manager.upgrade() else {
|
let Some(pm) = data.peer_manager.upgrade() else {
|
||||||
return Err(Error::AnyhowError(anyhow::anyhow!(
|
return Err(Error::AnyhowError(anyhow::anyhow!(
|
||||||
@@ -349,9 +332,7 @@ impl ManualConnectorManager {
|
|||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
let (peer_id, conn_id) = pm
|
let (peer_id, conn_id) = pm.try_direct_connect(connector).await?;
|
||||||
.try_direct_connect(connector.lock().await.as_mut())
|
|
||||||
.await?;
|
|
||||||
tracing::info!("reconnect succ: {} {} {}", peer_id, conn_id, dead_url);
|
tracing::info!("reconnect succ: {} {} {}", peer_id, conn_id, dead_url);
|
||||||
Ok(ReconnResult {
|
Ok(ReconnResult {
|
||||||
dead_url,
|
dead_url,
|
||||||
@@ -363,7 +344,6 @@ impl ManualConnectorManager {
|
|||||||
async fn conn_reconnect(
|
async fn conn_reconnect(
|
||||||
data: Arc<ConnectorManagerData>,
|
data: Arc<ConnectorManagerData>,
|
||||||
dead_url: String,
|
dead_url: String,
|
||||||
connector: MutexConnector,
|
|
||||||
) -> Result<ReconnResult, Error> {
|
) -> Result<ReconnResult, Error> {
|
||||||
tracing::info!("reconnect: {}", dead_url);
|
tracing::info!("reconnect: {}", dead_url);
|
||||||
|
|
||||||
@@ -415,12 +395,7 @@ impl ManualConnectorManager {
|
|||||||
let ret = timeout(
|
let ret = timeout(
|
||||||
// allow http connector to wait longer
|
// allow http connector to wait longer
|
||||||
std::time::Duration::from_secs(if use_long_timeout { 20 } else { 2 }),
|
std::time::Duration::from_secs(if use_long_timeout { 20 } else { 2 }),
|
||||||
Self::conn_reconnect_with_ip_version(
|
Self::conn_reconnect_with_ip_version(data.clone(), dead_url.clone(), ip_version),
|
||||||
data.clone(),
|
|
||||||
dead_url.clone(),
|
|
||||||
connector.clone(),
|
|
||||||
ip_version,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
tracing::info!("reconnect: {} done, ret: {:?}", dead_url, ret);
|
tracing::info!("reconnect: {} done, ret: {:?}", dead_url, ret);
|
||||||
|
|||||||
@@ -314,7 +314,11 @@ impl PunchBothEasySymHoleClient {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
match try_connect_with_socket(socket.socket.clone(), remote_mapped_addr.into())
|
match try_connect_with_socket(
|
||||||
|
global_ctx.clone(),
|
||||||
|
socket.socket.clone(),
|
||||||
|
remote_mapped_addr.into(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tunnel) => {
|
Ok(tunnel) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@@ -582,7 +582,33 @@ pub(crate) async fn send_symmetric_hole_punch_packet(
|
|||||||
Ok(cur_port_idx % ports.len())
|
Ok(cur_port_idx % ports.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn check_udp_socket_local_addr(
|
||||||
|
global_ctx: ArcGlobalCtx,
|
||||||
|
remote_mapped_addr: SocketAddr,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||||
|
socket.connect(remote_mapped_addr).await?;
|
||||||
|
if let Ok(local_addr) = socket.local_addr() {
|
||||||
|
// local_addr should not be equal to virtual ipv4 or virtual ipv6
|
||||||
|
match local_addr.ip() {
|
||||||
|
IpAddr::V4(ip) => {
|
||||||
|
if global_ctx.get_ipv4().map(|ip| ip.address()) == Some(ip) {
|
||||||
|
return Err(anyhow::anyhow!("local address is virtual ipv4").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IpAddr::V6(ip) => {
|
||||||
|
if global_ctx.get_ipv6().map(|ip| ip.address()) == Some(ip) {
|
||||||
|
return Err(anyhow::anyhow!("local address is virtual ipv6").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn try_connect_with_socket(
|
pub(crate) async fn try_connect_with_socket(
|
||||||
|
global_ctx: ArcGlobalCtx,
|
||||||
socket: Arc<UdpSocket>,
|
socket: Arc<UdpSocket>,
|
||||||
remote_mapped_addr: SocketAddr,
|
remote_mapped_addr: SocketAddr,
|
||||||
) -> Result<Box<dyn Tunnel>, Error> {
|
) -> Result<Box<dyn Tunnel>, Error> {
|
||||||
@@ -596,6 +622,9 @@ pub(crate) async fn try_connect_with_socket(
|
|||||||
.parse()
|
.parse()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
check_udp_socket_local_addr(global_ctx, remote_mapped_addr).await?;
|
||||||
|
|
||||||
connector
|
connector
|
||||||
.try_connect_with_socket(socket, remote_mapped_addr)
|
.try_connect_with_socket(socket, remote_mapped_addr)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -223,7 +223,11 @@ impl PunchConeHoleClient {
|
|||||||
tracing::debug!(?socket, ?tid, "punched socket found, try connect with it");
|
tracing::debug!(?socket, ?tid, "punched socket found, try connect with it");
|
||||||
|
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
match try_connect_with_socket(socket.socket.clone(), remote_mapped_addr.into())
|
match try_connect_with_socket(
|
||||||
|
global_ctx.clone(),
|
||||||
|
socket.socket.clone(),
|
||||||
|
remote_mapped_addr.into(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tunnel) => {
|
Ok(tunnel) => {
|
||||||
|
|||||||
@@ -14,11 +14,15 @@ use tokio::{net::UdpSocket, sync::RwLock};
|
|||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{scoped_task::ScopedTask, stun::StunInfoCollectorTrait, PeerId},
|
common::{
|
||||||
connector::udp_hole_punch::common::{
|
global_ctx::ArcGlobalCtx, scoped_task::ScopedTask, stun::StunInfoCollectorTrait, PeerId,
|
||||||
|
},
|
||||||
|
connector::udp_hole_punch::{
|
||||||
|
common::{
|
||||||
send_symmetric_hole_punch_packet, try_connect_with_socket, HOLE_PUNCH_PACKET_BODY_LEN,
|
send_symmetric_hole_punch_packet, try_connect_with_socket, HOLE_PUNCH_PACKET_BODY_LEN,
|
||||||
},
|
},
|
||||||
connector::udp_hole_punch::handle_rpc_result,
|
handle_rpc_result,
|
||||||
|
},
|
||||||
defer,
|
defer,
|
||||||
peers::peer_manager::PeerManager,
|
peers::peer_manager::PeerManager,
|
||||||
proto::{
|
proto::{
|
||||||
@@ -350,6 +354,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn check_hole_punch_result<T>(
|
async fn check_hole_punch_result<T>(
|
||||||
|
global_ctx: ArcGlobalCtx,
|
||||||
udp_array: &Arc<UdpSocketArray>,
|
udp_array: &Arc<UdpSocketArray>,
|
||||||
packet: &[u8],
|
packet: &[u8],
|
||||||
tid: u32,
|
tid: u32,
|
||||||
@@ -376,7 +381,13 @@ impl PunchSymToConeHoleClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// if hole punched but tunnel creation failed, need to retry entire process.
|
// if hole punched but tunnel creation failed, need to retry entire process.
|
||||||
match try_connect_with_socket(socket.socket.clone(), remote_mapped_addr.into()).await {
|
match try_connect_with_socket(
|
||||||
|
global_ctx.clone(),
|
||||||
|
socket.socket.clone(),
|
||||||
|
remote_mapped_addr.into(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(tunnel) => {
|
Ok(tunnel) => {
|
||||||
ret_tunnel.replace(tunnel);
|
ret_tunnel.replace(tunnel);
|
||||||
break;
|
break;
|
||||||
@@ -435,6 +446,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
// try direct connect first
|
// try direct connect first
|
||||||
if self.try_direct_connect.load(Ordering::Relaxed) {
|
if self.try_direct_connect.load(Ordering::Relaxed) {
|
||||||
if let Ok(tunnel) = try_connect_with_socket(
|
if let Ok(tunnel) = try_connect_with_socket(
|
||||||
|
global_ctx.clone(),
|
||||||
Arc::new(UdpSocket::bind("0.0.0.0:0").await?),
|
Arc::new(UdpSocket::bind("0.0.0.0:0").await?),
|
||||||
remote_mapped_addr.into(),
|
remote_mapped_addr.into(),
|
||||||
)
|
)
|
||||||
@@ -478,6 +490,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
let ret_tunnel = Self::check_hole_punch_result(
|
let ret_tunnel = Self::check_hole_punch_result(
|
||||||
|
global_ctx.clone(),
|
||||||
&udp_array,
|
&udp_array,
|
||||||
&packet,
|
&packet,
|
||||||
tid,
|
tid,
|
||||||
@@ -505,6 +518,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
let ret_tunnel = Self::check_hole_punch_result(
|
let ret_tunnel = Self::check_hole_punch_result(
|
||||||
|
global_ctx,
|
||||||
&udp_array,
|
&udp_array,
|
||||||
&packet,
|
&packet,
|
||||||
tid,
|
tid,
|
||||||
|
|||||||
@@ -22,23 +22,25 @@ use tokio::time::timeout;
|
|||||||
|
|
||||||
use easytier::{
|
use easytier::{
|
||||||
common::{
|
common::{
|
||||||
|
config::PortForwardConfig,
|
||||||
constants::EASYTIER_VERSION,
|
constants::EASYTIER_VERSION,
|
||||||
stun::{StunInfoCollector, StunInfoCollectorTrait},
|
stun::{StunInfoCollector, StunInfoCollectorTrait},
|
||||||
},
|
},
|
||||||
proto::{
|
proto::{
|
||||||
cli::{
|
cli::{
|
||||||
list_peer_route_pair, AclManageRpc, AclManageRpcClientFactory, ConnectorManageRpc,
|
list_peer_route_pair, AclManageRpc, AclManageRpcClientFactory, AddPortForwardRequest,
|
||||||
ConnectorManageRpcClientFactory, DumpRouteRequest, GetAclStatsRequest,
|
ConnectorManageRpc, ConnectorManageRpcClientFactory, DumpRouteRequest,
|
||||||
GetVpnPortalInfoRequest, ListConnectorRequest, ListForeignNetworkRequest,
|
GetAclStatsRequest, GetVpnPortalInfoRequest, GetWhitelistRequest, ListConnectorRequest,
|
||||||
ListGlobalForeignNetworkRequest, ListMappedListenerRequest, ListPeerRequest,
|
ListForeignNetworkRequest, ListGlobalForeignNetworkRequest, ListMappedListenerRequest,
|
||||||
ListPeerResponse, ListRouteRequest, ListRouteResponse, ManageMappedListenerRequest,
|
ListPeerRequest, ListPeerResponse, ListPortForwardRequest, ListRouteRequest,
|
||||||
MappedListenerManageAction, MappedListenerManageRpc,
|
ListRouteResponse, ManageMappedListenerRequest, MappedListenerManageAction,
|
||||||
MappedListenerManageRpcClientFactory, NodeInfo, PeerManageRpc,
|
MappedListenerManageRpc, MappedListenerManageRpcClientFactory, NodeInfo, PeerManageRpc,
|
||||||
PeerManageRpcClientFactory, ShowNodeInfoRequest, TcpProxyEntryState,
|
PeerManageRpcClientFactory, PortForwardManageRpc, PortForwardManageRpcClientFactory,
|
||||||
|
RemovePortForwardRequest, SetWhitelistRequest, ShowNodeInfoRequest, TcpProxyEntryState,
|
||||||
TcpProxyEntryTransportType, TcpProxyRpc, TcpProxyRpcClientFactory, VpnPortalRpc,
|
TcpProxyEntryTransportType, TcpProxyRpc, TcpProxyRpcClientFactory, VpnPortalRpc,
|
||||||
VpnPortalRpcClientFactory,
|
VpnPortalRpcClientFactory,
|
||||||
},
|
},
|
||||||
common::NatType,
|
common::{NatType, SocketType},
|
||||||
peer_rpc::{GetGlobalPeerMapRequest, PeerCenterRpc, PeerCenterRpcClientFactory},
|
peer_rpc::{GetGlobalPeerMapRequest, PeerCenterRpc, PeerCenterRpcClientFactory},
|
||||||
rpc_impl::standalone::StandAloneClient,
|
rpc_impl::standalone::StandAloneClient,
|
||||||
rpc_types::controller::BaseController,
|
rpc_types::controller::BaseController,
|
||||||
@@ -96,6 +98,10 @@ enum SubCommand {
|
|||||||
Proxy,
|
Proxy,
|
||||||
#[command(about = "show ACL rules statistics")]
|
#[command(about = "show ACL rules statistics")]
|
||||||
Acl(AclArgs),
|
Acl(AclArgs),
|
||||||
|
#[command(about = "manage port forwarding")]
|
||||||
|
PortForward(PortForwardArgs),
|
||||||
|
#[command(about = "manage TCP/UDP whitelist")]
|
||||||
|
Whitelist(WhitelistArgs),
|
||||||
#[command(about = t!("core_clap.generate_completions").to_string())]
|
#[command(about = t!("core_clap.generate_completions").to_string())]
|
||||||
GenAutocomplete { shell: Shell },
|
GenAutocomplete { shell: Shell },
|
||||||
}
|
}
|
||||||
@@ -193,6 +199,62 @@ enum AclSubCommand {
|
|||||||
Stats,
|
Stats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
struct PortForwardArgs {
|
||||||
|
#[command(subcommand)]
|
||||||
|
sub_command: Option<PortForwardSubCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum PortForwardSubCommand {
|
||||||
|
/// Add port forward rule
|
||||||
|
Add {
|
||||||
|
#[arg(help = "Protocol (tcp/udp)")]
|
||||||
|
protocol: String,
|
||||||
|
#[arg(help = "Local bind address (e.g., 0.0.0.0:8080)")]
|
||||||
|
bind_addr: String,
|
||||||
|
#[arg(help = "Destination address (e.g., 10.1.1.1:80)")]
|
||||||
|
dst_addr: String,
|
||||||
|
},
|
||||||
|
/// Remove port forward rule
|
||||||
|
Remove {
|
||||||
|
#[arg(help = "Protocol (tcp/udp)")]
|
||||||
|
protocol: String,
|
||||||
|
#[arg(help = "Local bind address (e.g., 0.0.0.0:8080)")]
|
||||||
|
bind_addr: String,
|
||||||
|
#[arg(help = "Optional Destination address (e.g., 10.1.1.1:80)")]
|
||||||
|
dst_addr: Option<String>,
|
||||||
|
},
|
||||||
|
/// List port forward rules
|
||||||
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
struct WhitelistArgs {
|
||||||
|
#[command(subcommand)]
|
||||||
|
sub_command: Option<WhitelistSubCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum WhitelistSubCommand {
|
||||||
|
/// Set TCP port whitelist
|
||||||
|
SetTcp {
|
||||||
|
#[arg(help = "TCP ports (e.g., 80,443,8000-9000)")]
|
||||||
|
ports: String,
|
||||||
|
},
|
||||||
|
/// Set UDP port whitelist
|
||||||
|
SetUdp {
|
||||||
|
#[arg(help = "UDP ports (e.g., 53,5000-6000)")]
|
||||||
|
ports: String,
|
||||||
|
},
|
||||||
|
/// Clear TCP whitelist
|
||||||
|
ClearTcp,
|
||||||
|
/// Clear UDP whitelist
|
||||||
|
ClearUdp,
|
||||||
|
/// Show current whitelist configuration
|
||||||
|
Show,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
struct ServiceArgs {
|
struct ServiceArgs {
|
||||||
#[arg(short, long, default_value = env!("CARGO_PKG_NAME"), help = "service name")]
|
#[arg(short, long, default_value = env!("CARGO_PKG_NAME"), help = "service name")]
|
||||||
@@ -340,6 +402,18 @@ impl CommandHandler<'_> {
|
|||||||
.with_context(|| "failed to get vpn portal client")?)
|
.with_context(|| "failed to get vpn portal client")?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_port_forward_manager_client(
|
||||||
|
&self,
|
||||||
|
) -> Result<Box<dyn PortForwardManageRpc<Controller = BaseController>>, Error> {
|
||||||
|
Ok(self
|
||||||
|
.client
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.scoped_client::<PortForwardManageRpcClientFactory<BaseController>>("".to_string())
|
||||||
|
.await
|
||||||
|
.with_context(|| "failed to get port forward manager client")?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_peers(&self) -> Result<ListPeerResponse, Error> {
|
async fn list_peers(&self) -> Result<ListPeerResponse, Error> {
|
||||||
let client = self.get_peer_manager_client().await?;
|
let client = self.get_peer_manager_client().await?;
|
||||||
let request = ListPeerRequest::default();
|
let request = ListPeerRequest::default();
|
||||||
@@ -788,6 +862,265 @@ impl CommandHandler<'_> {
|
|||||||
}
|
}
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_port_forward_add(
|
||||||
|
&self,
|
||||||
|
protocol: &str,
|
||||||
|
bind_addr: &str,
|
||||||
|
dst_addr: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let bind_addr: std::net::SocketAddr = bind_addr
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid bind address: {}", bind_addr))?;
|
||||||
|
let dst_addr: std::net::SocketAddr = dst_addr
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid destination address: {}", dst_addr))?;
|
||||||
|
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
return Err(anyhow::anyhow!("Protocol must be 'tcp' or 'udp'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = self.get_port_forward_manager_client().await?;
|
||||||
|
let request = AddPortForwardRequest {
|
||||||
|
cfg: Some(
|
||||||
|
PortForwardConfig {
|
||||||
|
proto: protocol.to_string(),
|
||||||
|
bind_addr: bind_addr.into(),
|
||||||
|
dst_addr: dst_addr.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.add_port_forward(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
println!(
|
||||||
|
"Port forward rule added: {} {} -> {}",
|
||||||
|
protocol, bind_addr, dst_addr
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_port_forward_remove(
|
||||||
|
&self,
|
||||||
|
protocol: &str,
|
||||||
|
bind_addr: &str,
|
||||||
|
dst_addr: Option<&str>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let bind_addr: std::net::SocketAddr = bind_addr
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid bind address: {}", bind_addr))?;
|
||||||
|
|
||||||
|
if protocol != "tcp" && protocol != "udp" {
|
||||||
|
return Err(anyhow::anyhow!("Protocol must be 'tcp' or 'udp'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = self.get_port_forward_manager_client().await?;
|
||||||
|
let request = RemovePortForwardRequest {
|
||||||
|
cfg: Some(
|
||||||
|
PortForwardConfig {
|
||||||
|
proto: protocol.to_string(),
|
||||||
|
bind_addr: bind_addr.into(),
|
||||||
|
dst_addr: dst_addr
|
||||||
|
.map(|s| s.parse::<SocketAddr>().unwrap())
|
||||||
|
.map(Into::into)
|
||||||
|
.unwrap_or("0.0.0.0:0".parse::<SocketAddr>().unwrap().into()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.remove_port_forward(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
println!("Port forward rule removed: {} {}", protocol, bind_addr);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_port_forward_list(&self) -> Result<(), Error> {
|
||||||
|
let client = self.get_port_forward_manager_client().await?;
|
||||||
|
let request = ListPortForwardRequest::default();
|
||||||
|
let response = client
|
||||||
|
.list_port_forward(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if self.verbose || *self.output_format == OutputFormat::Json {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&response)?);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(tabled::Tabled, serde::Serialize)]
|
||||||
|
struct PortForwardTableItem {
|
||||||
|
protocol: String,
|
||||||
|
bind_addr: String,
|
||||||
|
dst_addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let items: Vec<PortForwardTableItem> = response
|
||||||
|
.cfgs
|
||||||
|
.into_iter()
|
||||||
|
.map(|rule| PortForwardTableItem {
|
||||||
|
protocol: format!(
|
||||||
|
"{:?}",
|
||||||
|
SocketType::try_from(rule.socket_type).unwrap_or(SocketType::Tcp)
|
||||||
|
),
|
||||||
|
bind_addr: rule
|
||||||
|
.bind_addr
|
||||||
|
.map(|addr| addr.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
dst_addr: rule
|
||||||
|
.dst_addr
|
||||||
|
.map(|addr| addr.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
print_output(&items, self.output_format)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_whitelist_set_tcp(&self, ports: &str) -> Result<(), Error> {
|
||||||
|
let tcp_ports = Self::parse_port_list(ports)?;
|
||||||
|
let client = self.get_acl_manager_client().await?;
|
||||||
|
|
||||||
|
// Get current UDP ports to preserve them
|
||||||
|
let current = client
|
||||||
|
.get_whitelist(BaseController::default(), GetWhitelistRequest::default())
|
||||||
|
.await?;
|
||||||
|
let request = SetWhitelistRequest {
|
||||||
|
tcp_ports,
|
||||||
|
udp_ports: current.udp_ports,
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.set_whitelist(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
println!("TCP whitelist updated: {}", ports);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_whitelist_set_udp(&self, ports: &str) -> Result<(), Error> {
|
||||||
|
let udp_ports = Self::parse_port_list(ports)?;
|
||||||
|
let client = self.get_acl_manager_client().await?;
|
||||||
|
|
||||||
|
// Get current TCP ports to preserve them
|
||||||
|
let current = client
|
||||||
|
.get_whitelist(BaseController::default(), GetWhitelistRequest::default())
|
||||||
|
.await?;
|
||||||
|
let request = SetWhitelistRequest {
|
||||||
|
tcp_ports: current.tcp_ports,
|
||||||
|
udp_ports,
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.set_whitelist(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
println!("UDP whitelist updated: {}", ports);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_whitelist_clear_tcp(&self) -> Result<(), Error> {
|
||||||
|
let client = self.get_acl_manager_client().await?;
|
||||||
|
|
||||||
|
// Get current UDP ports to preserve them
|
||||||
|
let current = client
|
||||||
|
.get_whitelist(BaseController::default(), GetWhitelistRequest::default())
|
||||||
|
.await?;
|
||||||
|
let request = SetWhitelistRequest {
|
||||||
|
tcp_ports: vec![],
|
||||||
|
udp_ports: current.udp_ports,
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.set_whitelist(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
println!("TCP whitelist cleared");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_whitelist_clear_udp(&self) -> Result<(), Error> {
|
||||||
|
let client = self.get_acl_manager_client().await?;
|
||||||
|
|
||||||
|
// Get current TCP ports to preserve them
|
||||||
|
let current = client
|
||||||
|
.get_whitelist(BaseController::default(), GetWhitelistRequest::default())
|
||||||
|
.await?;
|
||||||
|
let request = SetWhitelistRequest {
|
||||||
|
tcp_ports: current.tcp_ports,
|
||||||
|
udp_ports: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
.set_whitelist(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
println!("UDP whitelist cleared");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_whitelist_show(&self) -> Result<(), Error> {
|
||||||
|
let client = self.get_acl_manager_client().await?;
|
||||||
|
let request = GetWhitelistRequest::default();
|
||||||
|
let response = client
|
||||||
|
.get_whitelist(BaseController::default(), request)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if self.verbose || *self.output_format == OutputFormat::Json {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&response)?);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"TCP Whitelist: {}",
|
||||||
|
if response.tcp_ports.is_empty() {
|
||||||
|
"None".to_string()
|
||||||
|
} else {
|
||||||
|
response.tcp_ports.join(", ")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"UDP Whitelist: {}",
|
||||||
|
if response.udp_ports.is_empty() {
|
||||||
|
"None".to_string()
|
||||||
|
} else {
|
||||||
|
response.udp_ports.join(", ")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_port_list(ports_str: &str) -> Result<Vec<String>, Error> {
|
||||||
|
let mut ports = Vec::new();
|
||||||
|
for port_spec in ports_str.split(',') {
|
||||||
|
let port_spec = port_spec.trim();
|
||||||
|
if port_spec.contains('-') {
|
||||||
|
// Handle port range
|
||||||
|
let parts: Vec<&str> = port_spec.split('-').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err(anyhow::anyhow!("Invalid port range: {}", port_spec));
|
||||||
|
}
|
||||||
|
let start: u16 = parts[0]
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid start port: {}", parts[0]))?;
|
||||||
|
let end: u16 = parts[1]
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid end port: {}", parts[1]))?;
|
||||||
|
if start > end {
|
||||||
|
return Err(anyhow::anyhow!("Invalid port range: start > end"));
|
||||||
|
}
|
||||||
|
ports.push(format!("{}-{}", start, end));
|
||||||
|
} else {
|
||||||
|
// Handle single port
|
||||||
|
let port: u16 = port_spec
|
||||||
|
.parse()
|
||||||
|
.with_context(|| format!("Invalid port number: {}", port_spec))?;
|
||||||
|
ports.push(port.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1494,6 +1827,46 @@ async fn main() -> Result<(), Error> {
|
|||||||
handler.handle_acl_stats().await?;
|
handler.handle_acl_stats().await?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SubCommand::PortForward(port_forward_args) => match &port_forward_args.sub_command {
|
||||||
|
Some(PortForwardSubCommand::Add {
|
||||||
|
protocol,
|
||||||
|
bind_addr,
|
||||||
|
dst_addr,
|
||||||
|
}) => {
|
||||||
|
handler
|
||||||
|
.handle_port_forward_add(protocol, bind_addr, dst_addr)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Some(PortForwardSubCommand::Remove {
|
||||||
|
protocol,
|
||||||
|
bind_addr,
|
||||||
|
dst_addr,
|
||||||
|
}) => {
|
||||||
|
handler
|
||||||
|
.handle_port_forward_remove(protocol, bind_addr, dst_addr.as_deref())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Some(PortForwardSubCommand::List) | None => {
|
||||||
|
handler.handle_port_forward_list().await?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SubCommand::Whitelist(whitelist_args) => match &whitelist_args.sub_command {
|
||||||
|
Some(WhitelistSubCommand::SetTcp { ports }) => {
|
||||||
|
handler.handle_whitelist_set_tcp(ports).await?;
|
||||||
|
}
|
||||||
|
Some(WhitelistSubCommand::SetUdp { ports }) => {
|
||||||
|
handler.handle_whitelist_set_udp(ports).await?;
|
||||||
|
}
|
||||||
|
Some(WhitelistSubCommand::ClearTcp) => {
|
||||||
|
handler.handle_whitelist_clear_tcp().await?;
|
||||||
|
}
|
||||||
|
Some(WhitelistSubCommand::ClearUdp) => {
|
||||||
|
handler.handle_whitelist_clear_udp().await?;
|
||||||
|
}
|
||||||
|
Some(WhitelistSubCommand::Show) | None => {
|
||||||
|
handler.handle_whitelist_show().await?;
|
||||||
|
}
|
||||||
|
},
|
||||||
SubCommand::GenAutocomplete { shell } => {
|
SubCommand::GenAutocomplete { shell } => {
|
||||||
let mut cmd = Cli::command();
|
let mut cmd = Cli::command();
|
||||||
easytier::print_completions(shell, &mut cmd, "easytier-cli");
|
easytier::print_completions(shell, &mut cmd, "easytier-cli");
|
||||||
|
|||||||
@@ -29,10 +29,7 @@ use easytier::{
|
|||||||
connector::create_connector_by_url,
|
connector::create_connector_by_url,
|
||||||
instance_manager::NetworkInstanceManager,
|
instance_manager::NetworkInstanceManager,
|
||||||
launcher::{add_proxy_network_to_config, ConfigSource},
|
launcher::{add_proxy_network_to_config, ConfigSource},
|
||||||
proto::{
|
proto::common::{CompressionAlgoPb, NatType},
|
||||||
acl::{Acl, AclV1, Action, Chain, ChainType, Protocol, Rule},
|
|
||||||
common::{CompressionAlgoPb, NatType},
|
|
||||||
},
|
|
||||||
tunnel::{IpVersion, PROTO_PORT_OFFSET},
|
tunnel::{IpVersion, PROTO_PORT_OFFSET},
|
||||||
utils::{init_logger, setup_panic_handler},
|
utils::{init_logger, setup_panic_handler},
|
||||||
web_client,
|
web_client,
|
||||||
@@ -622,117 +619,6 @@ impl NetworkOptions {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_port_list(port_list: &[String]) -> anyhow::Result<Vec<String>> {
|
|
||||||
let mut ports = Vec::new();
|
|
||||||
|
|
||||||
for port_spec in port_list {
|
|
||||||
if port_spec.contains('-') {
|
|
||||||
// Handle port range like "8000-9000"
|
|
||||||
let parts: Vec<&str> = port_spec.split('-').collect();
|
|
||||||
if parts.len() != 2 {
|
|
||||||
return Err(anyhow::anyhow!("Invalid port range format: {}", port_spec));
|
|
||||||
}
|
|
||||||
|
|
||||||
let start: u16 = parts[0]
|
|
||||||
.parse()
|
|
||||||
.with_context(|| format!("Invalid start port in range: {}", port_spec))?;
|
|
||||||
let end: u16 = parts[1]
|
|
||||||
.parse()
|
|
||||||
.with_context(|| format!("Invalid end port in range: {}", port_spec))?;
|
|
||||||
|
|
||||||
if start > end {
|
|
||||||
return Err(anyhow::anyhow!(
|
|
||||||
"Start port must be <= end port in range: {}",
|
|
||||||
port_spec
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add individual ports in the range
|
|
||||||
for port in start..=end {
|
|
||||||
ports.push(port.to_string());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle single port
|
|
||||||
let port: u16 = port_spec
|
|
||||||
.parse()
|
|
||||||
.with_context(|| format!("Invalid port number: {}", port_spec))?;
|
|
||||||
ports.push(port.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_acl_from_whitelists(&self) -> anyhow::Result<Option<Acl>> {
|
|
||||||
if self.tcp_whitelist.is_empty() && self.udp_whitelist.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut acl = Acl {
|
|
||||||
acl_v1: Some(AclV1 { chains: vec![] }),
|
|
||||||
};
|
|
||||||
|
|
||||||
let acl_v1 = acl.acl_v1.as_mut().unwrap();
|
|
||||||
|
|
||||||
// Create inbound chain for whitelist rules
|
|
||||||
let mut inbound_chain = Chain {
|
|
||||||
name: "inbound_whitelist".to_string(),
|
|
||||||
chain_type: ChainType::Inbound as i32,
|
|
||||||
description: "Auto-generated inbound whitelist from CLI".to_string(),
|
|
||||||
enabled: true,
|
|
||||||
rules: vec![],
|
|
||||||
default_action: Action::Drop as i32, // Default deny
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut rule_priority = 1000u32;
|
|
||||||
|
|
||||||
// Add TCP whitelist rules
|
|
||||||
if !self.tcp_whitelist.is_empty() {
|
|
||||||
let tcp_ports = Self::parse_port_list(&self.tcp_whitelist)?;
|
|
||||||
let tcp_rule = Rule {
|
|
||||||
name: "tcp_whitelist".to_string(),
|
|
||||||
description: "Auto-generated TCP whitelist rule".to_string(),
|
|
||||||
priority: rule_priority,
|
|
||||||
enabled: true,
|
|
||||||
protocol: Protocol::Tcp as i32,
|
|
||||||
ports: tcp_ports,
|
|
||||||
source_ips: vec![],
|
|
||||||
destination_ips: vec![],
|
|
||||||
source_ports: vec![],
|
|
||||||
action: Action::Allow as i32,
|
|
||||||
rate_limit: 0,
|
|
||||||
burst_limit: 0,
|
|
||||||
stateful: true,
|
|
||||||
};
|
|
||||||
inbound_chain.rules.push(tcp_rule);
|
|
||||||
rule_priority -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add UDP whitelist rules
|
|
||||||
if !self.udp_whitelist.is_empty() {
|
|
||||||
let udp_ports = Self::parse_port_list(&self.udp_whitelist)?;
|
|
||||||
let udp_rule = Rule {
|
|
||||||
name: "udp_whitelist".to_string(),
|
|
||||||
description: "Auto-generated UDP whitelist rule".to_string(),
|
|
||||||
priority: rule_priority,
|
|
||||||
enabled: true,
|
|
||||||
protocol: Protocol::Udp as i32,
|
|
||||||
ports: udp_ports,
|
|
||||||
source_ips: vec![],
|
|
||||||
destination_ips: vec![],
|
|
||||||
source_ports: vec![],
|
|
||||||
action: Action::Allow as i32,
|
|
||||||
rate_limit: 0,
|
|
||||||
burst_limit: 0,
|
|
||||||
stateful: false,
|
|
||||||
};
|
|
||||||
inbound_chain.rules.push(udp_rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
acl_v1.chains.push(inbound_chain);
|
|
||||||
Ok(Some(acl))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merge_into(&self, cfg: &mut TomlConfigLoader) -> anyhow::Result<()> {
|
fn merge_into(&self, cfg: &mut TomlConfigLoader) -> anyhow::Result<()> {
|
||||||
if self.hostname.is_some() {
|
if self.hostname.is_some() {
|
||||||
cfg.set_hostname(self.hostname.clone());
|
cfg.set_hostname(self.hostname.clone());
|
||||||
@@ -990,10 +876,13 @@ impl NetworkOptions {
|
|||||||
cfg.set_exit_nodes(self.exit_nodes.clone());
|
cfg.set_exit_nodes(self.exit_nodes.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle port whitelists by generating ACL configuration
|
let mut old_tcp_whitelist = cfg.get_tcp_whitelist();
|
||||||
if let Some(acl) = self.generate_acl_from_whitelists()? {
|
old_tcp_whitelist.extend(self.tcp_whitelist.clone());
|
||||||
cfg.set_acl(Some(acl));
|
cfg.set_tcp_whitelist(old_tcp_whitelist);
|
||||||
}
|
|
||||||
|
let mut old_udp_whitelist = cfg.get_udp_whitelist();
|
||||||
|
old_udp_whitelist.extend(self.udp_whitelist.clone());
|
||||||
|
cfg.set_udp_whitelist(old_udp_whitelist);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1222,6 +1111,14 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = manager.wait() => {
|
_ = manager.wait() => {
|
||||||
|
let infos = manager.collect_network_infos()?;
|
||||||
|
let errs = infos
|
||||||
|
.into_values()
|
||||||
|
.filter_map(|info| info.error_msg)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if errs.len() > 0 {
|
||||||
|
return Err(anyhow::anyhow!("some instances stopped with errors"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ = tokio::signal::ctrl_c() => {
|
_ = tokio::signal::ctrl_c() => {
|
||||||
println!("ctrl-c received, exiting...");
|
println!("ctrl-c received, exiting...");
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ use pnet::packet::{
|
|||||||
Packet as _,
|
Packet as _,
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use tokio::{io::copy_bidirectional, select, task::JoinSet};
|
use tokio::{
|
||||||
|
io::{copy_bidirectional, AsyncRead, AsyncWrite},
|
||||||
|
select,
|
||||||
|
task::JoinSet,
|
||||||
|
};
|
||||||
|
use tokio_util::io::InspectReader;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
tcp_proxy::{NatDstConnector, NatDstTcpConnector, TcpProxy},
|
tcp_proxy::{NatDstConnector, NatDstTcpConnector, TcpProxy},
|
||||||
@@ -28,11 +33,13 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
|
acl_processor::PacketInfo,
|
||||||
error::Result,
|
error::Result,
|
||||||
global_ctx::{ArcGlobalCtx, GlobalCtx},
|
global_ctx::{ArcGlobalCtx, GlobalCtx},
|
||||||
},
|
},
|
||||||
peers::{peer_manager::PeerManager, NicPacketFilter, PeerPacketFilter},
|
peers::{acl_filter::AclFilter, peer_manager::PeerManager, NicPacketFilter, PeerPacketFilter},
|
||||||
proto::{
|
proto::{
|
||||||
|
acl::{Action, ChainType, Protocol},
|
||||||
cli::{
|
cli::{
|
||||||
ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState,
|
ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState,
|
||||||
TcpProxyEntryTransportType, TcpProxyRpc,
|
TcpProxyEntryTransportType, TcpProxyRpc,
|
||||||
@@ -372,6 +379,50 @@ pub struct KcpProxyDst {
|
|||||||
tasks: JoinSet<()>,
|
tasks: JoinSet<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ProxyAclHandler {
|
||||||
|
pub acl_filter: Arc<AclFilter>,
|
||||||
|
pub packet_info: PacketInfo,
|
||||||
|
pub chain_type: ChainType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyAclHandler {
|
||||||
|
pub fn handle_packet(&self, buf: &[u8]) -> Result<()> {
|
||||||
|
let mut packet_info = self.packet_info.clone();
|
||||||
|
packet_info.packet_size = buf.len();
|
||||||
|
let ret = self
|
||||||
|
.acl_filter
|
||||||
|
.get_processor()
|
||||||
|
.process_packet(&packet_info, self.chain_type);
|
||||||
|
self.acl_filter.handle_acl_result(
|
||||||
|
&ret,
|
||||||
|
&packet_info,
|
||||||
|
self.chain_type,
|
||||||
|
&self.acl_filter.get_processor(),
|
||||||
|
);
|
||||||
|
if !matches!(ret.action, Action::Allow) {
|
||||||
|
return Err(anyhow::anyhow!("acl denied").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn copy_bidirection_with_acl(
|
||||||
|
&self,
|
||||||
|
src: impl AsyncRead + AsyncWrite + Unpin,
|
||||||
|
mut dst: impl AsyncRead + AsyncWrite + Unpin,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (src_reader, src_writer) = tokio::io::split(src);
|
||||||
|
let src_reader = InspectReader::new(src_reader, |buf| {
|
||||||
|
let _ = self.handle_packet(buf);
|
||||||
|
});
|
||||||
|
let mut src = tokio::io::join(src_reader, src_writer);
|
||||||
|
|
||||||
|
copy_bidirectional(&mut src, &mut dst).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl KcpProxyDst {
|
impl KcpProxyDst {
|
||||||
pub async fn new(peer_manager: Arc<PeerManager>) -> Self {
|
pub async fn new(peer_manager: Arc<PeerManager>) -> Self {
|
||||||
let mut kcp_endpoint = create_kcp_endpoint();
|
let mut kcp_endpoint = create_kcp_endpoint();
|
||||||
@@ -396,7 +447,7 @@ impl KcpProxyDst {
|
|||||||
|
|
||||||
#[tracing::instrument(ret)]
|
#[tracing::instrument(ret)]
|
||||||
async fn handle_one_in_stream(
|
async fn handle_one_in_stream(
|
||||||
mut kcp_stream: KcpStream,
|
kcp_stream: KcpStream,
|
||||||
global_ctx: ArcGlobalCtx,
|
global_ctx: ArcGlobalCtx,
|
||||||
proxy_entries: Arc<DashMap<ConnId, TcpProxyEntry>>,
|
proxy_entries: Arc<DashMap<ConnId, TcpProxyEntry>>,
|
||||||
cidr_set: Arc<CidrSet>,
|
cidr_set: Arc<CidrSet>,
|
||||||
@@ -411,6 +462,7 @@ impl KcpProxyDst {
|
|||||||
parsed_conn_data
|
parsed_conn_data
|
||||||
))?
|
))?
|
||||||
.into();
|
.into();
|
||||||
|
let src_socket: SocketAddr = parsed_conn_data.src.unwrap_or_default().into();
|
||||||
|
|
||||||
match dst_socket.ip() {
|
match dst_socket.ip() {
|
||||||
IpAddr::V4(dst_v4_ip) => {
|
IpAddr::V4(dst_v4_ip) => {
|
||||||
@@ -437,17 +489,36 @@ impl KcpProxyDst {
|
|||||||
proxy_entries.remove(&conn_id);
|
proxy_entries.remove(&conn_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address()))
|
let send_to_self =
|
||||||
&& global_ctx.no_tun()
|
Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address()));
|
||||||
{
|
|
||||||
|
if send_to_self && global_ctx.no_tun() {
|
||||||
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
|
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let acl_handler = ProxyAclHandler {
|
||||||
|
acl_filter: global_ctx.get_acl_filter().clone(),
|
||||||
|
packet_info: PacketInfo {
|
||||||
|
src_ip: src_socket.ip(),
|
||||||
|
dst_ip: dst_socket.ip(),
|
||||||
|
src_port: Some(src_socket.port()),
|
||||||
|
dst_port: Some(dst_socket.port()),
|
||||||
|
protocol: Protocol::Tcp,
|
||||||
|
packet_size: conn_data.len(),
|
||||||
|
},
|
||||||
|
chain_type: if send_to_self {
|
||||||
|
ChainType::Inbound
|
||||||
|
} else {
|
||||||
|
ChainType::Forward
|
||||||
|
},
|
||||||
|
};
|
||||||
|
acl_handler.handle_packet(&conn_data)?;
|
||||||
|
|
||||||
tracing::debug!("kcp connect to dst socket: {:?}", dst_socket);
|
tracing::debug!("kcp connect to dst socket: {:?}", dst_socket);
|
||||||
|
|
||||||
let _g = global_ctx.net_ns.guard();
|
let _g = global_ctx.net_ns.guard();
|
||||||
let connector = NatDstTcpConnector {};
|
let connector = NatDstTcpConnector {};
|
||||||
let mut ret = connector
|
let ret = connector
|
||||||
.connect("0.0.0.0:0".parse().unwrap(), dst_socket)
|
.connect("0.0.0.0:0".parse().unwrap(), dst_socket)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -455,7 +526,10 @@ impl KcpProxyDst {
|
|||||||
e.state = TcpProxyEntryState::Connected.into();
|
e.state = TcpProxyEntryState::Connected.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_bidirectional(&mut ret, &mut kcp_stream).await?;
|
acl_handler
|
||||||
|
.copy_bidirection_with_acl(kcp_stream, ret)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,19 +7,21 @@ use dashmap::DashMap;
|
|||||||
use pnet::packet::ipv4::Ipv4Packet;
|
use pnet::packet::ipv4::Ipv4Packet;
|
||||||
use prost::Message as _;
|
use prost::Message as _;
|
||||||
use quinn::{Endpoint, Incoming};
|
use quinn::{Endpoint, Incoming};
|
||||||
use tokio::io::{copy_bidirectional, AsyncRead, AsyncReadExt, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
use crate::common::acl_processor::PacketInfo;
|
||||||
use crate::common::error::Result;
|
use crate::common::error::Result;
|
||||||
use crate::common::global_ctx::{ArcGlobalCtx, GlobalCtx};
|
use crate::common::global_ctx::{ArcGlobalCtx, GlobalCtx};
|
||||||
use crate::common::join_joinset_background;
|
use crate::common::join_joinset_background;
|
||||||
use crate::defer;
|
use crate::defer;
|
||||||
use crate::gateway::kcp_proxy::TcpProxyForKcpSrcTrait;
|
use crate::gateway::kcp_proxy::{ProxyAclHandler, TcpProxyForKcpSrcTrait};
|
||||||
use crate::gateway::tcp_proxy::{NatDstConnector, NatDstTcpConnector, TcpProxy};
|
use crate::gateway::tcp_proxy::{NatDstConnector, NatDstTcpConnector, TcpProxy};
|
||||||
use crate::gateway::CidrSet;
|
use crate::gateway::CidrSet;
|
||||||
use crate::peers::peer_manager::PeerManager;
|
use crate::peers::peer_manager::PeerManager;
|
||||||
|
use crate::proto::acl::{ChainType, Protocol};
|
||||||
use crate::proto::cli::{
|
use crate::proto::cli::{
|
||||||
ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState,
|
ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState,
|
||||||
TcpProxyEntryTransportType, TcpProxyRpc,
|
TcpProxyEntryTransportType, TcpProxyRpc,
|
||||||
@@ -322,12 +324,13 @@ impl QUICProxyDst {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
match ret {
|
match ret {
|
||||||
Ok(Ok((mut quic_stream, mut tcp_stream))) => {
|
Ok(Ok((quic_stream, tcp_stream, acl))) => {
|
||||||
let ret = copy_bidirectional(&mut quic_stream, &mut tcp_stream).await;
|
let remote_addr = quic_stream.connection.as_ref().map(|c| c.remote_address());
|
||||||
|
let ret = acl.copy_bidirection_with_acl(quic_stream, tcp_stream).await;
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"QUIC connection handled, result: {:?}, remote addr: {:?}",
|
"QUIC connection handled, result: {:?}, remote addr: {:?}",
|
||||||
ret,
|
ret,
|
||||||
quic_stream.connection.as_ref().map(|c| c.remote_address())
|
remote_addr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
@@ -345,7 +348,7 @@ impl QUICProxyDst {
|
|||||||
cidr_set: Arc<CidrSet>,
|
cidr_set: Arc<CidrSet>,
|
||||||
proxy_entry_key: SocketAddr,
|
proxy_entry_key: SocketAddr,
|
||||||
proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>,
|
proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>,
|
||||||
) -> Result<(QUICStream, TcpStream)> {
|
) -> Result<(QUICStream, TcpStream, ProxyAclHandler)> {
|
||||||
let conn = incoming.await.with_context(|| "accept failed")?;
|
let conn = incoming.await.with_context(|| "accept failed")?;
|
||||||
let addr = conn.remote_address();
|
let addr = conn.remote_address();
|
||||||
tracing::info!("Accepted QUIC connection from {}", addr);
|
tracing::info!("Accepted QUIC connection from {}", addr);
|
||||||
@@ -376,7 +379,8 @@ impl QUICProxyDst {
|
|||||||
dst_socket.set_ip(real_ip);
|
dst_socket.set_ip(real_ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if Some(*dst_socket.ip()) == ctx.get_ipv4().map(|ip| ip.address()) && ctx.no_tun() {
|
let send_to_self = Some(*dst_socket.ip()) == ctx.get_ipv4().map(|ip| ip.address());
|
||||||
|
if send_to_self && ctx.no_tun() {
|
||||||
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
|
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +395,24 @@ impl QUICProxyDst {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let acl_handler = ProxyAclHandler {
|
||||||
|
acl_filter: ctx.get_acl_filter().clone(),
|
||||||
|
packet_info: PacketInfo {
|
||||||
|
src_ip: addr.ip(),
|
||||||
|
dst_ip: (*dst_socket.ip()).into(),
|
||||||
|
src_port: Some(addr.port()),
|
||||||
|
dst_port: Some(dst_socket.port()),
|
||||||
|
protocol: Protocol::Tcp,
|
||||||
|
packet_size: len as usize,
|
||||||
|
},
|
||||||
|
chain_type: if send_to_self {
|
||||||
|
ChainType::Inbound
|
||||||
|
} else {
|
||||||
|
ChainType::Forward
|
||||||
|
},
|
||||||
|
};
|
||||||
|
acl_handler.handle_packet(&buf)?;
|
||||||
|
|
||||||
let connector = NatDstTcpConnector {};
|
let connector = NatDstTcpConnector {};
|
||||||
|
|
||||||
let dst_stream = {
|
let dst_stream = {
|
||||||
@@ -411,7 +433,7 @@ impl QUICProxyDst {
|
|||||||
receiver: r,
|
receiver: r,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((quic_stream, dst_stream))
|
Ok((quic_stream, dst_stream, acl_handler))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use kcp_sys::{endpoint::KcpEndpoint, stream::KcpStream};
|
use kcp_sys::{endpoint::KcpEndpoint, stream::KcpStream};
|
||||||
|
use tokio_util::sync::{CancellationToken, DropGuard};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
@@ -432,6 +433,8 @@ pub struct Socks5Server {
|
|||||||
udp_forward_task: Arc<DashMap<UdpClientKey, ScopedTask<()>>>,
|
udp_forward_task: Arc<DashMap<UdpClientKey, ScopedTask<()>>>,
|
||||||
|
|
||||||
kcp_endpoint: Mutex<Option<Weak<KcpEndpoint>>>,
|
kcp_endpoint: Mutex<Option<Weak<KcpEndpoint>>>,
|
||||||
|
|
||||||
|
cancel_tokens: DashMap<PortForwardConfig, DropGuard>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -531,6 +534,8 @@ impl Socks5Server {
|
|||||||
udp_forward_task: Arc::new(DashMap::new()),
|
udp_forward_task: Arc::new(DashMap::new()),
|
||||||
|
|
||||||
kcp_endpoint: Mutex::new(None),
|
kcp_endpoint: Mutex::new(None),
|
||||||
|
|
||||||
|
cancel_tokens: DashMap::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,10 +619,9 @@ impl Socks5Server {
|
|||||||
need_start = true;
|
need_start = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
for port_forward in self.global_ctx.config.get_port_forwards() {
|
let cfgs = self.global_ctx.config.get_port_forwards();
|
||||||
self.add_port_forward(port_forward).await?;
|
self.reload_port_forwards(&cfgs).await?;
|
||||||
need_start = true;
|
need_start = need_start || cfgs.len() > 0;
|
||||||
}
|
|
||||||
|
|
||||||
if need_start {
|
if need_start {
|
||||||
self.peer_manager
|
self.peer_manager
|
||||||
@@ -630,6 +634,26 @@ impl Socks5Server {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn reload_port_forwards(&self, cfgs: &Vec<PortForwardConfig>) -> Result<(), Error> {
|
||||||
|
// remove entries not in new cfg
|
||||||
|
self.cancel_tokens.retain(|k, _| {
|
||||||
|
cfgs.iter().any(|cfg| {
|
||||||
|
if cfg.dst_addr.ip().is_unspecified() {
|
||||||
|
k.bind_addr == cfg.bind_addr && k.proto == cfg.proto
|
||||||
|
} else {
|
||||||
|
k == cfg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// add new ones
|
||||||
|
for cfg in cfgs {
|
||||||
|
if !self.cancel_tokens.contains_key(cfg) {
|
||||||
|
self.add_port_forward(cfg.clone()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_port_forward_connection(
|
async fn handle_port_forward_connection(
|
||||||
mut incoming_socket: tokio::net::TcpStream,
|
mut incoming_socket: tokio::net::TcpStream,
|
||||||
connector: Box<dyn AsyncTcpConnector<S = SocksTcpStream> + Send>,
|
connector: Box<dyn AsyncTcpConnector<S = SocksTcpStream> + Send>,
|
||||||
@@ -660,12 +684,10 @@ impl Socks5Server {
|
|||||||
pub async fn add_port_forward(&self, cfg: PortForwardConfig) -> Result<(), Error> {
|
pub async fn add_port_forward(&self, cfg: PortForwardConfig) -> Result<(), Error> {
|
||||||
match cfg.proto.to_lowercase().as_str() {
|
match cfg.proto.to_lowercase().as_str() {
|
||||||
"tcp" => {
|
"tcp" => {
|
||||||
self.add_tcp_port_forward(cfg.bind_addr, cfg.dst_addr)
|
self.add_tcp_port_forward(&cfg).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
"udp" => {
|
"udp" => {
|
||||||
self.add_udp_port_forward(cfg.bind_addr, cfg.dst_addr)
|
self.add_udp_port_forward(&cfg).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
@@ -680,11 +702,12 @@ impl Socks5Server {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_tcp_port_forward(
|
pub fn remove_port_forward(&self, cfg: PortForwardConfig) {
|
||||||
&self,
|
let _ = self.cancel_tokens.remove(&cfg);
|
||||||
bind_addr: SocketAddr,
|
}
|
||||||
dst_addr: SocketAddr,
|
|
||||||
) -> Result<(), Error> {
|
pub async fn add_tcp_port_forward(&self, cfg: &PortForwardConfig) -> Result<(), Error> {
|
||||||
|
let (bind_addr, dst_addr) = (cfg.bind_addr, cfg.dst_addr);
|
||||||
let listener = bind_tcp_socket(bind_addr, self.global_ctx.net_ns.clone())?;
|
let listener = bind_tcp_socket(bind_addr, self.global_ctx.net_ns.clone())?;
|
||||||
|
|
||||||
let net = self.net.clone();
|
let net = self.net.clone();
|
||||||
@@ -693,15 +716,27 @@ impl Socks5Server {
|
|||||||
let forward_tasks = tasks.clone();
|
let forward_tasks = tasks.clone();
|
||||||
let kcp_endpoint = self.kcp_endpoint.lock().await.clone();
|
let kcp_endpoint = self.kcp_endpoint.lock().await.clone();
|
||||||
let peer_mgr = Arc::downgrade(&self.peer_manager.clone());
|
let peer_mgr = Arc::downgrade(&self.peer_manager.clone());
|
||||||
|
let cancel_token = CancellationToken::new();
|
||||||
|
self.cancel_tokens
|
||||||
|
.insert(cfg.clone(), cancel_token.clone().drop_guard());
|
||||||
|
|
||||||
self.tasks.lock().unwrap().spawn(async move {
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let (incoming_socket, addr) = match listener.accept().await {
|
let (incoming_socket, addr) = select! {
|
||||||
|
biased;
|
||||||
|
_ = cancel_token.cancelled() => {
|
||||||
|
tracing::info!("port forward for {:?} cancelled", bind_addr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res = listener.accept() => {
|
||||||
|
match res {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("port forward accept error = {:?}", err);
|
tracing::error!("port forward accept error = {:?}", err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -747,11 +782,8 @@ impl Socks5Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "add_udp_port_forward", skip(self))]
|
#[tracing::instrument(name = "add_udp_port_forward", skip(self))]
|
||||||
pub async fn add_udp_port_forward(
|
pub async fn add_udp_port_forward(&self, cfg: &PortForwardConfig) -> Result<(), Error> {
|
||||||
&self,
|
let (bind_addr, dst_addr) = (cfg.bind_addr, cfg.dst_addr);
|
||||||
bind_addr: SocketAddr,
|
|
||||||
dst_addr: SocketAddr,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let socket = Arc::new(bind_udp_socket(bind_addr, self.global_ctx.net_ns.clone())?);
|
let socket = Arc::new(bind_udp_socket(bind_addr, self.global_ctx.net_ns.clone())?);
|
||||||
|
|
||||||
let entries = self.entries.clone();
|
let entries = self.entries.clone();
|
||||||
@@ -759,17 +791,29 @@ impl Socks5Server {
|
|||||||
let net = self.net.clone();
|
let net = self.net.clone();
|
||||||
let udp_client_map = self.udp_client_map.clone();
|
let udp_client_map = self.udp_client_map.clone();
|
||||||
let udp_forward_task = self.udp_forward_task.clone();
|
let udp_forward_task = self.udp_forward_task.clone();
|
||||||
|
let cancel_token = CancellationToken::new();
|
||||||
|
self.cancel_tokens
|
||||||
|
.insert(cfg.clone(), cancel_token.clone().drop_guard());
|
||||||
|
|
||||||
self.tasks.lock().unwrap().spawn(async move {
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
// we set the max buffer size of smoltcp to 8192, so we need to use a buffer size that is less than 8192 here.
|
// we set the max buffer size of smoltcp to 8192, so we need to use a buffer size that is less than 8192 here.
|
||||||
let mut buf = vec![0u8; 8192];
|
let mut buf = vec![0u8; 8192];
|
||||||
let (len, addr) = match socket.recv_from(&mut buf).await {
|
let (len, addr) = select! {
|
||||||
|
biased;
|
||||||
|
_ = cancel_token.cancelled() => {
|
||||||
|
tracing::info!("udp port forward for {:?} cancelled", bind_addr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res = socket.recv_from(&mut buf) => {
|
||||||
|
match res {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("udp port forward recv error = {:?}", err);
|
tracing::error!("udp port forward recv error = {:?}", err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
|
|||||||
@@ -63,7 +63,13 @@ pub struct NatDstTcpConnector;
|
|||||||
impl NatDstConnector for NatDstTcpConnector {
|
impl NatDstConnector for NatDstTcpConnector {
|
||||||
type DstStream = TcpStream;
|
type DstStream = TcpStream;
|
||||||
async fn connect(&self, _src: SocketAddr, nat_dst: SocketAddr) -> Result<Self::DstStream> {
|
async fn connect(&self, _src: SocketAddr, nat_dst: SocketAddr) -> Result<Self::DstStream> {
|
||||||
let socket = TcpSocket::new_v4().unwrap();
|
let socket = match TcpSocket::new_v4() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("create v4 socket failed: {:?}", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
if let Err(e) = socket.set_nodelay(true) {
|
if let Err(e) = socket.set_nodelay(true) {
|
||||||
tracing::warn!("set_nodelay failed, ignore it: {:?}", e);
|
tracing::warn!("set_nodelay failed, ignore it: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use cidr::{IpCidr, Ipv4Inet};
|
|||||||
use tokio::{sync::Mutex, task::JoinSet};
|
use tokio::{sync::Mutex, task::JoinSet};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use crate::common::acl_processor::AclRuleBuilder;
|
||||||
use crate::common::config::ConfigLoader;
|
use crate::common::config::ConfigLoader;
|
||||||
use crate::common::error::Error;
|
use crate::common::error::Error;
|
||||||
use crate::common::global_ctx::{ArcGlobalCtx, GlobalCtx, GlobalCtxEvent};
|
use crate::common::global_ctx::{ArcGlobalCtx, GlobalCtx, GlobalCtxEvent};
|
||||||
@@ -29,13 +30,15 @@ use crate::peers::peer_manager::{PeerManager, RouteAlgoType};
|
|||||||
use crate::peers::rpc_service::PeerManagerRpcService;
|
use crate::peers::rpc_service::PeerManagerRpcService;
|
||||||
use crate::peers::{create_packet_recv_chan, recv_packet_from_chan, PacketRecvChanReceiver};
|
use crate::peers::{create_packet_recv_chan, recv_packet_from_chan, PacketRecvChanReceiver};
|
||||||
use crate::proto::cli::VpnPortalRpc;
|
use crate::proto::cli::VpnPortalRpc;
|
||||||
use crate::proto::cli::{GetVpnPortalInfoRequest, GetVpnPortalInfoResponse, VpnPortalInfo};
|
|
||||||
use crate::proto::cli::{
|
use crate::proto::cli::{
|
||||||
ListMappedListenerRequest, ListMappedListenerResponse, ManageMappedListenerRequest,
|
AddPortForwardRequest, AddPortForwardResponse, ListMappedListenerRequest,
|
||||||
ManageMappedListenerResponse, MappedListener, MappedListenerManageAction,
|
ListMappedListenerResponse, ListPortForwardRequest, ListPortForwardResponse,
|
||||||
MappedListenerManageRpc,
|
ManageMappedListenerRequest, ManageMappedListenerResponse, MappedListener,
|
||||||
|
MappedListenerManageAction, MappedListenerManageRpc, PortForwardManageRpc,
|
||||||
|
RemovePortForwardRequest, RemovePortForwardResponse,
|
||||||
};
|
};
|
||||||
use crate::proto::common::TunnelInfo;
|
use crate::proto::cli::{GetVpnPortalInfoRequest, GetVpnPortalInfoResponse, VpnPortalInfo};
|
||||||
|
use crate::proto::common::{PortForwardConfigPb, TunnelInfo};
|
||||||
use crate::proto::peer_rpc::PeerCenterRpcServer;
|
use crate::proto::peer_rpc::PeerCenterRpcServer;
|
||||||
use crate::proto::rpc_impl::standalone::{RpcServerHook, StandAloneServer};
|
use crate::proto::rpc_impl::standalone::{RpcServerHook, StandAloneServer};
|
||||||
use crate::proto::rpc_types;
|
use crate::proto::rpc_types;
|
||||||
@@ -609,9 +612,9 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(acl) = self.global_ctx.config.get_acl() {
|
self.global_ctx
|
||||||
self.global_ctx.get_acl_filter().reload_rules(Some(&acl));
|
.get_acl_filter()
|
||||||
}
|
.reload_rules(AclRuleBuilder::build(&self.global_ctx)?.as_ref());
|
||||||
|
|
||||||
// run after tun device created, so listener can bind to tun device, which may be required by win 10
|
// run after tun device created, so listener can bind to tun device, which may be required by win 10
|
||||||
self.ip_proxy = Some(IpProxy::new(
|
self.ip_proxy = Some(IpProxy::new(
|
||||||
@@ -790,6 +793,85 @@ impl Instance {
|
|||||||
MappedListenerManagerRpcService(self.global_ctx.clone())
|
MappedListenerManagerRpcService(self.global_ctx.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_port_forward_manager_rpc_service(
|
||||||
|
&self,
|
||||||
|
) -> impl PortForwardManageRpc<Controller = BaseController> + Clone {
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PortForwardManagerRpcService {
|
||||||
|
global_ctx: ArcGlobalCtx,
|
||||||
|
socks5_server: Weak<Socks5Server>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl PortForwardManageRpc for PortForwardManagerRpcService {
|
||||||
|
type Controller = BaseController;
|
||||||
|
|
||||||
|
async fn add_port_forward(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
request: AddPortForwardRequest,
|
||||||
|
) -> Result<AddPortForwardResponse, rpc_types::error::Error> {
|
||||||
|
let Some(socks5_server) = self.socks5_server.upgrade() else {
|
||||||
|
return Err(anyhow::anyhow!("socks5 server not available").into());
|
||||||
|
};
|
||||||
|
if let Some(cfg) = request.cfg {
|
||||||
|
tracing::info!("Port forward rule added: {:?}", cfg);
|
||||||
|
let mut current_forwards = self.global_ctx.config.get_port_forwards();
|
||||||
|
current_forwards.push(cfg.into());
|
||||||
|
self.global_ctx
|
||||||
|
.config
|
||||||
|
.set_port_forwards(current_forwards.clone());
|
||||||
|
socks5_server
|
||||||
|
.reload_port_forwards(¤t_forwards)
|
||||||
|
.await
|
||||||
|
.with_context(|| "Failed to reload port forwards")?;
|
||||||
|
}
|
||||||
|
Ok(AddPortForwardResponse {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_port_forward(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
request: RemovePortForwardRequest,
|
||||||
|
) -> Result<RemovePortForwardResponse, rpc_types::error::Error> {
|
||||||
|
let Some(socks5_server) = self.socks5_server.upgrade() else {
|
||||||
|
return Err(anyhow::anyhow!("socks5 server not available").into());
|
||||||
|
};
|
||||||
|
let Some(cfg) = request.cfg else {
|
||||||
|
return Err(anyhow::anyhow!("port forward config is empty").into());
|
||||||
|
};
|
||||||
|
let cfg = cfg.into();
|
||||||
|
let mut current_forwards = self.global_ctx.config.get_port_forwards();
|
||||||
|
current_forwards.retain(|e| *e != cfg);
|
||||||
|
self.global_ctx
|
||||||
|
.config
|
||||||
|
.set_port_forwards(current_forwards.clone());
|
||||||
|
socks5_server
|
||||||
|
.reload_port_forwards(¤t_forwards)
|
||||||
|
.await
|
||||||
|
.with_context(|| "Failed to reload port forwards")?;
|
||||||
|
|
||||||
|
tracing::info!("Port forward rule removed: {:?}", cfg);
|
||||||
|
Ok(RemovePortForwardResponse {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_port_forward(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
_request: ListPortForwardRequest,
|
||||||
|
) -> Result<ListPortForwardResponse, rpc_types::error::Error> {
|
||||||
|
let forwards = self.global_ctx.config.get_port_forwards();
|
||||||
|
let cfgs: Vec<PortForwardConfigPb> = forwards.into_iter().map(Into::into).collect();
|
||||||
|
Ok(ListPortForwardResponse { cfgs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PortForwardManagerRpcService {
|
||||||
|
global_ctx: self.global_ctx.clone(),
|
||||||
|
socks5_server: Arc::downgrade(&self.socks5_server),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_rpc_server(&mut self) -> Result<(), Error> {
|
async fn run_rpc_server(&mut self) -> Result<(), Error> {
|
||||||
let Some(_) = self.global_ctx.config.get_rpc_portal() else {
|
let Some(_) = self.global_ctx.config.get_rpc_portal() else {
|
||||||
tracing::info!("rpc server not enabled, because rpc_portal is not set.");
|
tracing::info!("rpc server not enabled, because rpc_portal is not set.");
|
||||||
@@ -803,6 +885,7 @@ impl Instance {
|
|||||||
let peer_center = self.peer_center.clone();
|
let peer_center = self.peer_center.clone();
|
||||||
let vpn_portal_rpc = self.get_vpn_portal_rpc_service();
|
let vpn_portal_rpc = self.get_vpn_portal_rpc_service();
|
||||||
let mapped_listener_manager_rpc = self.get_mapped_listener_manager_rpc_service();
|
let mapped_listener_manager_rpc = self.get_mapped_listener_manager_rpc_service();
|
||||||
|
let port_forward_manager_rpc = self.get_port_forward_manager_rpc_service();
|
||||||
|
|
||||||
let s = self.rpc_server.as_mut().unwrap();
|
let s = self.rpc_server.as_mut().unwrap();
|
||||||
let peer_mgr_rpc_service = PeerManagerRpcService::new(peer_mgr.clone());
|
let peer_mgr_rpc_service = PeerManagerRpcService::new(peer_mgr.clone());
|
||||||
@@ -823,6 +906,10 @@ impl Instance {
|
|||||||
MappedListenerManageRpcServer::new(mapped_listener_manager_rpc),
|
MappedListenerManageRpcServer::new(mapped_listener_manager_rpc),
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
s.registry().register(
|
||||||
|
PortForwardManageRpcServer::new(port_forward_manager_rpc),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(ip_proxy) = self.ip_proxy.as_ref() {
|
if let Some(ip_proxy) = self.ip_proxy.as_ref() {
|
||||||
s.registry().register(
|
s.registry().register(
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ impl NetworkInstanceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let instance_stop_notifier = instance.get_stop_notifier();
|
let instance_stop_notifier = instance.get_stop_notifier();
|
||||||
let instance_config_source = instance.get_config_source();
|
|
||||||
let instance_event_receiver = match instance.get_config_source() {
|
let instance_event_receiver = match instance.get_config_source() {
|
||||||
ConfigSource::Cli | ConfigSource::File | ConfigSource::Web => {
|
ConfigSource::Cli | ConfigSource::File | ConfigSource::Web => {
|
||||||
Some(instance.subscribe_event())
|
Some(instance.subscribe_event())
|
||||||
@@ -78,14 +77,8 @@ impl NetworkInstanceManager {
|
|||||||
eprintln!("instance {} stopped with error: {}", instance_id, e);
|
eprintln!("instance {} stopped with error: {}", instance_id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match instance_config_source {
|
stop_check_notifier.notify_one();
|
||||||
ConfigSource::Cli | ConfigSource::File => {
|
|
||||||
instance_map.remove(&instance_id);
|
|
||||||
}
|
|
||||||
ConfigSource::Web | ConfigSource::GUI | ConfigSource::FFI => {}
|
|
||||||
}
|
|
||||||
instance_stop_tasks.remove(&instance_id);
|
instance_stop_tasks.remove(&instance_id);
|
||||||
stop_check_notifier.notify_waiters();
|
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -160,7 +153,11 @@ impl NetworkInstanceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait(&self) {
|
pub async fn wait(&self) {
|
||||||
while self.instance_map.len() > 0 {
|
while self
|
||||||
|
.instance_map
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.value().is_easytier_running())
|
||||||
|
{
|
||||||
self.stop_check_notifier.notified().await;
|
self.stop_check_notifier.notified().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,6 +335,7 @@ mod tests {
|
|||||||
use crate::common::config::*;
|
use crate::common::config::*;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
async fn it_works() {
|
async fn it_works() {
|
||||||
let manager = NetworkInstanceManager::new();
|
let manager = NetworkInstanceManager::new();
|
||||||
let cfg_str = r#"
|
let cfg_str = r#"
|
||||||
@@ -404,6 +402,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial_test::serial]
|
||||||
fn test_no_tokio_runtime() {
|
fn test_no_tokio_runtime() {
|
||||||
let manager = NetworkInstanceManager::new();
|
let manager = NetworkInstanceManager::new();
|
||||||
let cfg_str = r#"
|
let cfg_str = r#"
|
||||||
@@ -466,6 +465,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
async fn test_single_instance_failed() {
|
async fn test_single_instance_failed() {
|
||||||
let free_tcp_port =
|
let free_tcp_port =
|
||||||
crate::utils::find_free_tcp_port(10012..65534).expect("no free tcp port found");
|
crate::utils::find_free_tcp_port(10012..65534).expect("no free tcp port found");
|
||||||
@@ -491,7 +491,7 @@ mod tests {
|
|||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = manager.wait() => {
|
_ = manager.wait() => {
|
||||||
assert_eq!(manager.list_network_instance_ids().len(), 0);
|
assert_eq!(manager.list_network_instance_ids().len(), 1);
|
||||||
}
|
}
|
||||||
_ = tokio::time::sleep(std::time::Duration::from_secs(5)) => {
|
_ = tokio::time::sleep(std::time::Duration::from_secs(5)) => {
|
||||||
panic!("instance manager with single failed instance({:?}) should not running", config_source);
|
panic!("instance manager with single failed instance({:?}) should not running", config_source);
|
||||||
@@ -522,6 +522,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
async fn test_multiple_instances_one_failed() {
|
async fn test_multiple_instances_one_failed() {
|
||||||
let free_tcp_port =
|
let free_tcp_port =
|
||||||
crate::utils::find_free_tcp_port(10012..65534).expect("no free tcp port found");
|
crate::utils::find_free_tcp_port(10012..65534).expect("no free tcp port found");
|
||||||
@@ -557,7 +558,7 @@ mod tests {
|
|||||||
panic!("instance manager with multiple instances one failed should still running");
|
panic!("instance manager with multiple instances one failed should still running");
|
||||||
}
|
}
|
||||||
_ = tokio::time::sleep(std::time::Duration::from_secs(2)) => {
|
_ = tokio::time::sleep(std::time::Duration::from_secs(2)) => {
|
||||||
assert_eq!(manager.list_network_instance_ids().len(), 1);
|
assert_eq!(manager.list_network_instance_ids().len(), 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ impl AclFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get current processor for processing packets
|
/// Get current processor for processing packets
|
||||||
fn get_processor(&self) -> Arc<AclProcessor> {
|
pub fn get_processor(&self) -> Arc<AclProcessor> {
|
||||||
self.acl_processor.load_full()
|
self.acl_processor.load_full()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ impl AclFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process ACL result and log if needed
|
/// Process ACL result and log if needed
|
||||||
fn handle_acl_result(
|
pub fn handle_acl_result(
|
||||||
&self,
|
&self,
|
||||||
result: &AclResult,
|
result: &AclResult,
|
||||||
packet_info: &PacketInfo,
|
packet_info: &PacketInfo,
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ impl PeerConn {
|
|||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_handshake(&mut self) -> Result<(), Error> {
|
async fn send_handshake(&mut self, send_secret_digest: bool) -> Result<(), Error> {
|
||||||
let network = self.global_ctx.get_network_identity();
|
let network = self.global_ctx.get_network_identity();
|
||||||
let mut req = HandshakeRequest {
|
let mut req = HandshakeRequest {
|
||||||
magic: MAGIC,
|
magic: MAGIC,
|
||||||
@@ -257,8 +257,16 @@ impl PeerConn {
|
|||||||
network_name: network.network_name.clone(),
|
network_name: network.network_name.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// only send network secret digest if the network is the same
|
||||||
|
if send_secret_digest {
|
||||||
req.network_secret_digrest
|
req.network_secret_digrest
|
||||||
.extend_from_slice(&network.network_secret_digest.unwrap_or_default());
|
.extend_from_slice(&network.network_secret_digest.unwrap_or_default());
|
||||||
|
} else {
|
||||||
|
// fill zero
|
||||||
|
req.network_secret_digrest
|
||||||
|
.extend_from_slice(&[0u8; std::mem::size_of::<NetworkSecretDigest>()]);
|
||||||
|
}
|
||||||
|
|
||||||
let hs_req = req.encode_to_vec();
|
let hs_req = req.encode_to_vec();
|
||||||
let mut zc_packet = ZCPacket::new_with_payload(hs_req.as_bytes());
|
let mut zc_packet = ZCPacket::new_with_payload(hs_req.as_bytes());
|
||||||
@@ -295,7 +303,8 @@ impl PeerConn {
|
|||||||
self.info = Some(rsp);
|
self.info = Some(rsp);
|
||||||
self.is_client = Some(false);
|
self.is_client = Some(false);
|
||||||
|
|
||||||
self.send_handshake().await?;
|
let send_digest = self.get_network_identity() == self.global_ctx.get_network_identity();
|
||||||
|
self.send_handshake(send_digest).await?;
|
||||||
|
|
||||||
if self.get_peer_id() == self.my_peer_id {
|
if self.get_peer_id() == self.my_peer_id {
|
||||||
Err(Error::WaitRespError("peer id conflict".to_owned()))
|
Err(Error::WaitRespError("peer id conflict".to_owned()))
|
||||||
@@ -310,10 +319,14 @@ impl PeerConn {
|
|||||||
tracing::info!("handshake request: {:?}", rsp);
|
tracing::info!("handshake request: {:?}", rsp);
|
||||||
self.info = Some(rsp);
|
self.info = Some(rsp);
|
||||||
self.is_client = Some(false);
|
self.is_client = Some(false);
|
||||||
self.send_handshake().await?;
|
|
||||||
|
let send_digest = self.get_network_identity() == self.global_ctx.get_network_identity();
|
||||||
|
self.send_handshake(send_digest).await?;
|
||||||
|
|
||||||
if self.get_peer_id() == self.my_peer_id {
|
if self.get_peer_id() == self.my_peer_id {
|
||||||
Err(Error::WaitRespError("peer id conflict".to_owned()))
|
Err(Error::WaitRespError(
|
||||||
|
"peer id conflict, are you connecting to yourself?".to_owned(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -321,7 +334,7 @@ impl PeerConn {
|
|||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn do_handshake_as_client(&mut self) -> Result<(), Error> {
|
pub async fn do_handshake_as_client(&mut self) -> Result<(), Error> {
|
||||||
self.send_handshake().await?;
|
self.send_handshake(true).await?;
|
||||||
tracing::info!("waiting for handshake request from server");
|
tracing::info!("waiting for handshake request from server");
|
||||||
let rsp = self.wait_handshake_loop().await?;
|
let rsp = self.wait_handshake_loop().await?;
|
||||||
tracing::info!("handshake response: {:?}", rsp);
|
tracing::info!("handshake response: {:?}", rsp);
|
||||||
@@ -329,7 +342,9 @@ impl PeerConn {
|
|||||||
self.is_client = Some(true);
|
self.is_client = Some(true);
|
||||||
|
|
||||||
if self.get_peer_id() == self.my_peer_id {
|
if self.get_peer_id() == self.my_peer_id {
|
||||||
Err(Error::WaitRespError("peer id conflict".to_owned()))
|
Err(Error::WaitRespError(
|
||||||
|
"peer id conflict, are you connecting to yourself?".to_owned(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ impl PeerRpcManagerTransport for RpcTransport {
|
|||||||
.get_route_peer_info(dst_peer_id)
|
.get_route_peer_info(dst_peer_id)
|
||||||
.await
|
.await
|
||||||
.and_then(|x| x.feature_flag.map(|x| x.is_public_server))
|
.and_then(|x| x.feature_flag.map(|x| x.is_public_server))
|
||||||
.unwrap_or(true);
|
// if dst is directly connected, it's must not public server
|
||||||
|
.unwrap_or(!peers.has_peer(dst_peer_id));
|
||||||
if !is_dst_peer_public_server {
|
if !is_dst_peer_public_server {
|
||||||
self.encryptor
|
self.encryptor
|
||||||
.encrypt(&mut msg)
|
.encrypt(&mut msg)
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::{
|
||||||
|
common::acl_processor::AclRuleBuilder,
|
||||||
|
proto::{
|
||||||
cli::{
|
cli::{
|
||||||
AclManageRpc, DumpRouteRequest, DumpRouteResponse, GetAclStatsRequest, GetAclStatsResponse,
|
AclManageRpc, DumpRouteRequest, DumpRouteResponse, GetAclStatsRequest,
|
||||||
|
GetAclStatsResponse, GetWhitelistRequest, GetWhitelistResponse,
|
||||||
ListForeignNetworkRequest, ListForeignNetworkResponse, ListGlobalForeignNetworkRequest,
|
ListForeignNetworkRequest, ListForeignNetworkResponse, ListGlobalForeignNetworkRequest,
|
||||||
ListGlobalForeignNetworkResponse, ListPeerRequest, ListPeerResponse, ListRouteRequest,
|
ListGlobalForeignNetworkResponse, ListPeerRequest, ListPeerResponse, ListRouteRequest,
|
||||||
ListRouteResponse, PeerInfo, PeerManageRpc, ShowNodeInfoRequest, ShowNodeInfoResponse,
|
ListRouteResponse, PeerInfo, PeerManageRpc, SetWhitelistRequest, SetWhitelistResponse,
|
||||||
|
ShowNodeInfoRequest, ShowNodeInfoResponse,
|
||||||
},
|
},
|
||||||
rpc_types::{self, controller::BaseController},
|
rpc_types::{self, controller::BaseController},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::peer_manager::PeerManager;
|
use super::peer_manager::PeerManager;
|
||||||
@@ -153,4 +158,45 @@ impl AclManageRpc for PeerManagerRpcService {
|
|||||||
acl_stats: Some(acl_stats),
|
acl_stats: Some(acl_stats),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_whitelist(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
request: SetWhitelistRequest,
|
||||||
|
) -> Result<SetWhitelistResponse, rpc_types::error::Error> {
|
||||||
|
tracing::info!(
|
||||||
|
"Setting whitelist - TCP: {:?}, UDP: {:?}",
|
||||||
|
request.tcp_ports,
|
||||||
|
request.udp_ports
|
||||||
|
);
|
||||||
|
|
||||||
|
let global_ctx = self.peer_manager.get_global_ctx();
|
||||||
|
|
||||||
|
global_ctx.config.set_tcp_whitelist(request.tcp_ports);
|
||||||
|
global_ctx.config.set_udp_whitelist(request.udp_ports);
|
||||||
|
global_ctx
|
||||||
|
.get_acl_filter()
|
||||||
|
.reload_rules(AclRuleBuilder::build(&global_ctx)?.as_ref());
|
||||||
|
|
||||||
|
Ok(SetWhitelistResponse {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_whitelist(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
_request: GetWhitelistRequest,
|
||||||
|
) -> Result<GetWhitelistResponse, rpc_types::error::Error> {
|
||||||
|
let global_ctx = self.peer_manager.get_global_ctx();
|
||||||
|
let tcp_ports = global_ctx.config.get_tcp_whitelist();
|
||||||
|
let udp_ports = global_ctx.config.get_udp_whitelist();
|
||||||
|
tracing::info!(
|
||||||
|
"Getting whitelist - TCP: {:?}, UDP: {:?}",
|
||||||
|
tcp_ports,
|
||||||
|
udp_ports
|
||||||
|
);
|
||||||
|
Ok(GetWhitelistResponse {
|
||||||
|
tcp_ports,
|
||||||
|
udp_ports,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,4 +261,44 @@ message GetAclStatsResponse {
|
|||||||
|
|
||||||
service AclManageRpc {
|
service AclManageRpc {
|
||||||
rpc GetAclStats(GetAclStatsRequest) returns (GetAclStatsResponse);
|
rpc GetAclStats(GetAclStatsRequest) returns (GetAclStatsResponse);
|
||||||
|
rpc SetWhitelist(SetWhitelistRequest) returns (SetWhitelistResponse);
|
||||||
|
rpc GetWhitelist(GetWhitelistRequest) returns (GetWhitelistResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetWhitelistRequest {
|
||||||
|
repeated string tcp_ports = 1;
|
||||||
|
repeated string udp_ports = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetWhitelistResponse {}
|
||||||
|
|
||||||
|
message GetWhitelistRequest {}
|
||||||
|
|
||||||
|
message GetWhitelistResponse {
|
||||||
|
repeated string tcp_ports = 1;
|
||||||
|
repeated string udp_ports = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddPortForwardRequest {
|
||||||
|
common.PortForwardConfigPb cfg = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddPortForwardResponse {}
|
||||||
|
|
||||||
|
message RemovePortForwardRequest {
|
||||||
|
common.PortForwardConfigPb cfg = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemovePortForwardResponse {}
|
||||||
|
|
||||||
|
message ListPortForwardRequest {}
|
||||||
|
|
||||||
|
message ListPortForwardResponse {
|
||||||
|
repeated common.PortForwardConfigPb cfgs = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service PortForwardManageRpc {
|
||||||
|
rpc AddPortForward(AddPortForwardRequest) returns (AddPortForwardResponse);
|
||||||
|
rpc RemovePortForward(RemovePortForwardRequest) returns (RemovePortForwardResponse);
|
||||||
|
rpc ListPortForward(ListPortForwardRequest) returns (ListPortForwardResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1329,16 +1329,33 @@ async fn avoid_tunnel_loop_back_to_virtual_network() {
|
|||||||
drop_insts(insts).await;
|
drop_insts(insts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest::rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
pub async fn acl_rule_test_inbound() {
|
pub async fn acl_rule_test_inbound(
|
||||||
|
#[values(true, false)] enable_kcp_proxy: bool,
|
||||||
|
#[values(true, false)] enable_quic_proxy: bool,
|
||||||
|
) {
|
||||||
use crate::tunnel::{
|
use crate::tunnel::{
|
||||||
common::tests::_tunnel_pingpong_netns,
|
common::tests::_tunnel_pingpong_netns,
|
||||||
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
||||||
udp::{UdpTunnelConnector, UdpTunnelListener},
|
udp::{UdpTunnelConnector, UdpTunnelListener},
|
||||||
};
|
};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
let insts = init_three_node("udp").await;
|
let insts = init_three_node_ex(
|
||||||
|
"udp",
|
||||||
|
|cfg| {
|
||||||
|
if cfg.get_inst_name() == "inst1" {
|
||||||
|
let mut flags = cfg.get_flags();
|
||||||
|
flags.enable_kcp_proxy = enable_kcp_proxy;
|
||||||
|
flags.enable_quic_proxy = enable_quic_proxy;
|
||||||
|
cfg.set_flags(flags);
|
||||||
|
}
|
||||||
|
cfg
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
// 构造 ACL 配置
|
// 构造 ACL 配置
|
||||||
use crate::proto::acl::*;
|
use crate::proto::acl::*;
|
||||||
@@ -1422,7 +1439,7 @@ pub async fn acl_rule_test_inbound() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// 6. 8080 应该连接失败(被 ACL 拦截)
|
// 6. 8080 应该连接失败(被 ACL 拦截)
|
||||||
let result = tokio::time::timeout(
|
let result = tokio::spawn(tokio::time::timeout(
|
||||||
std::time::Duration::from_millis(200),
|
std::time::Duration::from_millis(200),
|
||||||
_tunnel_pingpong_netns(
|
_tunnel_pingpong_netns(
|
||||||
listener_8080,
|
listener_8080,
|
||||||
@@ -1431,10 +1448,13 @@ pub async fn acl_rule_test_inbound() {
|
|||||||
NetNS::new(Some("net_a".into())),
|
NetNS::new(Some("net_a".into())),
|
||||||
buf.clone(),
|
buf.clone(),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_err(), "TCP 连接 8080 应被 ACL 拦截,不能成功");
|
assert!(
|
||||||
|
result.is_err() || result.unwrap().is_err(),
|
||||||
|
"TCP 连接 8080 应被 ACL 拦截,不能成功"
|
||||||
|
);
|
||||||
|
|
||||||
// 7. 从 10.144.144.2 连接 8082 应该连接失败(被 ACL 拦截)
|
// 7. 从 10.144.144.2 连接 8082 应该连接失败(被 ACL 拦截)
|
||||||
let result = tokio::time::timeout(
|
let result = tokio::time::timeout(
|
||||||
@@ -1508,3 +1528,236 @@ pub async fn acl_rule_test_inbound() {
|
|||||||
|
|
||||||
drop_insts(insts).await;
|
drop_insts(insts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest::rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
pub async fn acl_rule_test_subnet_proxy(
|
||||||
|
#[values(true, false)] enable_kcp_proxy: bool,
|
||||||
|
#[values(true, false)] enable_quic_proxy: bool,
|
||||||
|
) {
|
||||||
|
use crate::tunnel::{
|
||||||
|
common::tests::_tunnel_pingpong_netns,
|
||||||
|
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
||||||
|
udp::{UdpTunnelConnector, UdpTunnelListener},
|
||||||
|
};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
let insts = init_three_node_ex(
|
||||||
|
"udp",
|
||||||
|
|cfg| {
|
||||||
|
if cfg.get_inst_name() == "inst1" {
|
||||||
|
let mut flags = cfg.get_flags();
|
||||||
|
flags.enable_kcp_proxy = enable_kcp_proxy;
|
||||||
|
flags.enable_quic_proxy = enable_quic_proxy;
|
||||||
|
cfg.set_flags(flags);
|
||||||
|
} else if cfg.get_inst_name() == "inst3" {
|
||||||
|
// 添加子网代理配置
|
||||||
|
cfg.add_proxy_cidr("10.1.2.0/24".parse().unwrap(), None)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
cfg
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// 等待代理路由出现
|
||||||
|
wait_proxy_route_appear(
|
||||||
|
&insts[0].get_peer_manager(),
|
||||||
|
"10.144.144.3/24",
|
||||||
|
insts[2].peer_id(),
|
||||||
|
"10.1.2.0/24",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Test IPv4 connectivity
|
||||||
|
wait_for_condition(
|
||||||
|
|| async { ping_test("net_a", "10.1.2.4", None).await },
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// 构造 ACL 配置 - 针对子网代理流量
|
||||||
|
use crate::proto::acl::*;
|
||||||
|
let mut acl = Acl::default();
|
||||||
|
let mut acl_v1 = AclV1::default();
|
||||||
|
|
||||||
|
let mut chain = Chain::default();
|
||||||
|
chain.name = "test_subnet_proxy_inbound".to_string();
|
||||||
|
chain.chain_type = ChainType::Forward as i32;
|
||||||
|
chain.enabled = true;
|
||||||
|
|
||||||
|
// 禁止访问子网代理中的 8080 端口
|
||||||
|
let mut deny_rule = Rule::default();
|
||||||
|
deny_rule.name = "deny_subnet_8080".to_string();
|
||||||
|
deny_rule.priority = 200;
|
||||||
|
deny_rule.enabled = true;
|
||||||
|
deny_rule.action = Action::Drop as i32;
|
||||||
|
deny_rule.protocol = Protocol::Any as i32;
|
||||||
|
deny_rule.ports = vec!["8080".to_string()];
|
||||||
|
deny_rule.destination_ips = vec!["10.1.2.0/24".to_string()];
|
||||||
|
chain.rules.push(deny_rule);
|
||||||
|
|
||||||
|
// 禁止来自 inst1 (10.144.144.1) 访问子网代理中的 8081 端口
|
||||||
|
let mut deny_src_rule = Rule::default();
|
||||||
|
deny_src_rule.name = "deny_inst1_to_subnet_8081".to_string();
|
||||||
|
deny_src_rule.priority = 200;
|
||||||
|
deny_src_rule.enabled = true;
|
||||||
|
deny_src_rule.action = Action::Drop as i32;
|
||||||
|
deny_src_rule.protocol = Protocol::Any as i32;
|
||||||
|
deny_src_rule.ports = vec!["8081".to_string()];
|
||||||
|
deny_src_rule.source_ips = vec!["10.144.144.1/32".to_string()];
|
||||||
|
deny_src_rule.destination_ips = vec!["10.1.2.0/24".to_string()];
|
||||||
|
chain.rules.push(deny_src_rule);
|
||||||
|
|
||||||
|
// 允许其他流量
|
||||||
|
let mut allow_rule = Rule::default();
|
||||||
|
allow_rule.name = "allow_all".to_string();
|
||||||
|
allow_rule.priority = 100;
|
||||||
|
allow_rule.enabled = true;
|
||||||
|
allow_rule.action = Action::Allow as i32;
|
||||||
|
allow_rule.protocol = Protocol::Any as i32;
|
||||||
|
allow_rule.stateful = true;
|
||||||
|
chain.rules.push(allow_rule);
|
||||||
|
|
||||||
|
acl_v1.chains.push(chain);
|
||||||
|
acl.acl_v1 = Some(acl_v1);
|
||||||
|
|
||||||
|
// 在 inst3 上应用 ACL 规则
|
||||||
|
insts[2]
|
||||||
|
.get_global_ctx()
|
||||||
|
.get_acl_filter()
|
||||||
|
.reload_rules(Some(&acl));
|
||||||
|
|
||||||
|
// TCP 测试部分 - 测试子网代理的 ACL 规则
|
||||||
|
{
|
||||||
|
// 在 net_d (10.1.2.4) 上监听多个端口
|
||||||
|
let listener_8080 = TcpTunnelListener::new("tcp://0.0.0.0:8080".parse().unwrap());
|
||||||
|
let listener_8081 = TcpTunnelListener::new("tcp://0.0.0.0:8081".parse().unwrap());
|
||||||
|
let listener_8082 = TcpTunnelListener::new("tcp://0.0.0.0:8082".parse().unwrap());
|
||||||
|
|
||||||
|
// 从 inst1 (net_a) 连接到子网代理
|
||||||
|
let connector_8080 = TcpTunnelConnector::new("tcp://10.1.2.4:8080".parse().unwrap());
|
||||||
|
let connector_8081 = TcpTunnelConnector::new("tcp://10.1.2.4:8081".parse().unwrap());
|
||||||
|
let connector_8082 = TcpTunnelConnector::new("tcp://10.1.2.4:8082".parse().unwrap());
|
||||||
|
|
||||||
|
let mut buf = vec![0; 32];
|
||||||
|
rand::thread_rng().fill(&mut buf[..]);
|
||||||
|
|
||||||
|
// 8082 应该可以连接成功(不被 ACL 拦截)
|
||||||
|
_tunnel_pingpong_netns(
|
||||||
|
listener_8082,
|
||||||
|
connector_8082,
|
||||||
|
NetNS::new(Some("net_d".into())),
|
||||||
|
NetNS::new(Some("net_a".into())),
|
||||||
|
buf.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// 8080 应该连接失败(被 ACL 拦截 - 禁止访问子网代理的 8080)
|
||||||
|
let result = tokio::spawn(tokio::time::timeout(
|
||||||
|
std::time::Duration::from_millis(200),
|
||||||
|
_tunnel_pingpong_netns(
|
||||||
|
listener_8080,
|
||||||
|
connector_8080,
|
||||||
|
NetNS::new(Some("net_d".into())),
|
||||||
|
NetNS::new(Some("net_a".into())),
|
||||||
|
buf.clone(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err() || result.unwrap().is_err(),
|
||||||
|
"TCP 连接子网代理 8080 应被 ACL 拦截,不能成功"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 8081 应该连接失败(被 ACL 拦截 - 禁止 inst1 访问子网代理的 8081)
|
||||||
|
let result = tokio::spawn(tokio::time::timeout(
|
||||||
|
std::time::Duration::from_millis(200),
|
||||||
|
_tunnel_pingpong_netns(
|
||||||
|
listener_8081,
|
||||||
|
connector_8081,
|
||||||
|
NetNS::new(Some("net_d".into())),
|
||||||
|
NetNS::new(Some("net_a".into())),
|
||||||
|
buf.clone(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err() || result.unwrap().is_err(),
|
||||||
|
"TCP 连接子网代理 8081 应被 ACL 拦截,不能成功"
|
||||||
|
);
|
||||||
|
|
||||||
|
let stats = insts[2].get_global_ctx().get_acl_filter().get_stats();
|
||||||
|
println!("ACL stats after TCP tests: {:?}", stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDP 测试部分 - 测试子网代理的 ACL 规则
|
||||||
|
{
|
||||||
|
let listener_8080 = UdpTunnelListener::new("udp://0.0.0.0:8080".parse().unwrap());
|
||||||
|
let listener_8082 = UdpTunnelListener::new("udp://0.0.0.0:8082".parse().unwrap());
|
||||||
|
|
||||||
|
let connector_8080 = UdpTunnelConnector::new("udp://10.1.2.4:8080".parse().unwrap());
|
||||||
|
let connector_8082 = UdpTunnelConnector::new("udp://10.1.2.4:8082".parse().unwrap());
|
||||||
|
|
||||||
|
let mut buf = vec![0; 32];
|
||||||
|
rand::thread_rng().fill(&mut buf[..]);
|
||||||
|
|
||||||
|
// 8082 应该可以连接成功
|
||||||
|
_tunnel_pingpong_netns(
|
||||||
|
listener_8082,
|
||||||
|
connector_8082,
|
||||||
|
NetNS::new(Some("net_d".into())),
|
||||||
|
NetNS::new(Some("net_a".into())),
|
||||||
|
buf.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// 8080 应该连接失败(被 ACL 拦截)
|
||||||
|
let result = tokio::time::timeout(
|
||||||
|
std::time::Duration::from_millis(200),
|
||||||
|
_tunnel_pingpong_netns(
|
||||||
|
listener_8080,
|
||||||
|
connector_8080,
|
||||||
|
NetNS::new(Some("net_d".into())),
|
||||||
|
NetNS::new(Some("net_a".into())),
|
||||||
|
buf.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let stats = insts[2].get_global_ctx().get_acl_filter().get_stats();
|
||||||
|
println!("ACL stats after UDP tests: {}", stats);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"UDP 连接子网代理 8080 应被 ACL 拦截,不能成功"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试 ICMP 到子网代理(应该被拒绝,因为 Any 协议被拒绝)
|
||||||
|
tokio::spawn(wait_for_condition(
|
||||||
|
|| async { ping_test("net_a", "10.1.2.4", None).await },
|
||||||
|
Duration::from_secs(1),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
// 移除 ACL 规则
|
||||||
|
insts[2]
|
||||||
|
.get_global_ctx()
|
||||||
|
.get_acl_filter()
|
||||||
|
.reload_rules(None);
|
||||||
|
|
||||||
|
// 验证移除 ACL 后,ICMP 可以正常工作
|
||||||
|
wait_for_condition(
|
||||||
|
|| async { ping_test("net_a", "10.1.2.4", None).await },
|
||||||
|
Duration::from_secs(5),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
drop_insts(insts).await;
|
||||||
|
}
|
||||||
|
|||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750741721,
|
"lastModified": 1753429684,
|
||||||
"narHash": "sha256-Z0djmTa1YmnGMfE9jEe05oO4zggjDmxOGKwt844bUhE=",
|
"narHash": "sha256-9h7+4/53cSfQ/uA3pSvCaBepmZaz/dLlLVJnbQ+SJjk=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4b1164c3215f018c4442463a27689d973cffd750",
|
"rev": "7fd36ee82c0275fb545775cc5e4d30542899511d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -48,11 +48,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750905536,
|
"lastModified": 1753671061,
|
||||||
"narHash": "sha256-Mo7yXM5IvMGNvJPiNkFsVT2UERmnvjsKgnY6UyDdySQ=",
|
"narHash": "sha256-IU4eBWfe9h2QejJYST+EAlhg8a1H6mh9gbcmWgZ2/mQ=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "2fa7c0aabd15fa0ccc1dc7e675a4fcf0272ad9a1",
|
"rev": "40065d17ee4dbec3ded8ca61236132aede843fab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
31
flake.nix
31
flake.nix
@@ -10,35 +10,48 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
|
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
overlays = [ (import rust-overlay) ];
|
overlays = [ (import rust-overlay) ];
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system overlays;
|
inherit system overlays;
|
||||||
};
|
};
|
||||||
|
rustVersion = "1.87.0";
|
||||||
lib = nixpkgs.lib;
|
rust = pkgs.rust-bin.stable.${rustVersion}.default.override{
|
||||||
|
|
||||||
rust = pkgs.rust-bin.stable.latest.default.override {
|
|
||||||
extensions = [ "rust-src" "rust-analyzer" ];
|
extensions = [ "rust-src" "rust-analyzer" ];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell rec {
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
rust
|
rust
|
||||||
pkg-config
|
|
||||||
protobuf
|
protobuf
|
||||||
];
|
clang
|
||||||
|
pkg-config
|
||||||
|
|
||||||
|
# web
|
||||||
|
nodejs_22
|
||||||
|
pnpm
|
||||||
|
];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
zstd
|
zstd
|
||||||
|
openssl
|
||||||
|
libclang
|
||||||
|
llvmPackages.libclang
|
||||||
|
|
||||||
|
# gui
|
||||||
|
webkitgtk_4_1
|
||||||
|
libsoup_3
|
||||||
];
|
];
|
||||||
|
|
||||||
|
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||||
|
BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.clang}/resource-root/include";
|
||||||
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (buildInputs ++ nativeBuildInputs);
|
||||||
ZSTD_SYS_USE_PKG_CONFIG = true;
|
ZSTD_SYS_USE_PKG_CONFIG = true;
|
||||||
KCP_SYS_EXTRA_HEADER_PATH = "${pkgs.libclang.lib}/lib/clang/19/include:${pkgs.glibc.dev}/include";
|
KCP_SYS_EXTRA_HEADER_PATH = "${pkgs.libclang.lib}/lib/clang/19/include:${pkgs.glibc.dev}/include";
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user