mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 18:24:36 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40c6de8e31 | |||
| 2db655bd6d | |||
| c49c56612b | |||
| 6ca074abae | |||
| 84430055ab | |||
| 432fcb3fc3 | |||
| fae32361f2 | |||
| bcb2e512d4 | |||
| 82ca04a8a7 | |||
| 2ef3b72224 | |||
| 6d319cba1d | |||
| 3687519ef3 | |||
| 3a4ac59467 | |||
| 1cfc135df3 | |||
| 5b35c51da9 | |||
| ec7ddd3bad | |||
| 6f3e708679 | |||
| 869e1b89f5 | |||
| 9e0a3b6936 | |||
| c6cb1a77d0 | |||
| 83010861ba | |||
| daa53e5168 | |||
| 51befdbf87 | |||
| 8311b11713 | |||
| 19c80c7b9c | |||
| a879dd1b14 | |||
| a8feb9ac2b | |||
| c5fbd29c0e | |||
| 26b1794723 | |||
| 371b4b70a3 | |||
| b2cc38ee63 |
+35
-54
@@ -1,29 +1,40 @@
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
linker = "rust-lld"
|
||||
rustflags = ["-C", "linker-flavor=ld.lld"]
|
||||
# region Native
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-unknown-linux-ohos]
|
||||
ar = "/usr/local/ohos-sdk/linux/native/llvm/bin/llvm-ar"
|
||||
linker = "/home/runner/sdk/native/llvm/aarch64-unknown-linux-ohos-clang.sh"
|
||||
[target.'cfg(all(windows, target_env = "msvc"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.aarch64-unknown-linux-ohos.env]
|
||||
PKG_CONFIG_PATH = "/usr/local/ohos-sdk/linux/native/sysroot/usr/lib/pkgconfig:/usr/local/ohos-sdk/linux/native/sysroot/usr/local/lib/pkgconfig"
|
||||
PKG_CONFIG_LIBDIR = "/usr/local/ohos-sdk/linux/native/sysroot/usr/lib:/usr/local/ohos-sdk/linux/native/sysroot/usr/local/lib"
|
||||
PKG_CONFIG_SYSROOT_DIR = "/usr/local/ohos-sdk/linux/native/sysroot"
|
||||
SYSROOT = "/usr/local/ohos-sdk/linux/native/sysroot"
|
||||
# region
|
||||
|
||||
# region CI
|
||||
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
linker = "aarch64-unknown-linux-musl-gcc"
|
||||
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.armv7-unknown-linux-musleabihf]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.armv7-unknown-linux-musleabi]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.arm-unknown-linux-musleabihf]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.arm-unknown-linux-musleabi]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.loongarch64-unknown-linux-musl]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.mipsel-unknown-linux-musl]
|
||||
@@ -64,44 +75,14 @@ rustflags = [
|
||||
"gcc",
|
||||
]
|
||||
|
||||
[target.armv7-unknown-linux-musleabihf]
|
||||
linker = "armv7-unknown-linux-musleabihf-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.aarch64-unknown-linux-ohos]
|
||||
ar = "/usr/local/ohos-sdk/linux/native/llvm/bin/llvm-ar"
|
||||
linker = "/home/runner/sdk/native/llvm/aarch64-unknown-linux-ohos-clang.sh"
|
||||
|
||||
[target.armv7-unknown-linux-musleabi]
|
||||
linker = "armv7-unknown-linux-musleabi-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.aarch64-unknown-linux-ohos.env]
|
||||
PKG_CONFIG_PATH = "/usr/local/ohos-sdk/linux/native/sysroot/usr/lib/pkgconfig:/usr/local/ohos-sdk/linux/native/sysroot/usr/local/lib/pkgconfig"
|
||||
PKG_CONFIG_LIBDIR = "/usr/local/ohos-sdk/linux/native/sysroot/usr/lib:/usr/local/ohos-sdk/linux/native/sysroot/usr/local/lib"
|
||||
PKG_CONFIG_SYSROOT_DIR = "/usr/local/ohos-sdk/linux/native/sysroot"
|
||||
SYSROOT = "/usr/local/ohos-sdk/linux/native/sysroot"
|
||||
|
||||
[target.loongarch64-unknown-linux-musl]
|
||||
linker = "loongarch64-unknown-linux-musl-gcc"
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[target.arm-unknown-linux-musleabihf]
|
||||
linker = "arm-unknown-linux-musleabihf-gcc"
|
||||
rustflags = [
|
||||
"-C",
|
||||
"target-feature=+crt-static",
|
||||
"-L",
|
||||
"./musl_gcc/arm-unknown-linux-musleabihf/arm-unknown-linux-musleabihf/lib",
|
||||
"-L",
|
||||
"./musl_gcc/arm-unknown-linux-musleabihf/lib/gcc/arm-unknown-linux-musleabihf/15.1.0",
|
||||
"-l",
|
||||
"atomic",
|
||||
"-l",
|
||||
"gcc",
|
||||
]
|
||||
|
||||
[target.arm-unknown-linux-musleabi]
|
||||
linker = "arm-unknown-linux-musleabi-gcc"
|
||||
rustflags = [
|
||||
"-C",
|
||||
"target-feature=+crt-static",
|
||||
"-L",
|
||||
"./musl_gcc/arm-unknown-linux-musleabi/arm-unknown-linux-musleabi/lib",
|
||||
"-L",
|
||||
"./musl_gcc/arm-unknown-linux-musleabi/lib/gcc/arm-unknown-linux-musleabi/15.1.0",
|
||||
"-l",
|
||||
"atomic",
|
||||
"-l",
|
||||
"gcc",
|
||||
]
|
||||
# endregion
|
||||
|
||||
@@ -2,10 +2,17 @@ name: prepare-build
|
||||
author: Luna
|
||||
description: Prepare build environment
|
||||
inputs:
|
||||
web:
|
||||
description: 'Whether to prepare the web build environment'
|
||||
target:
|
||||
description: 'The target to build for'
|
||||
required: false
|
||||
pnpm:
|
||||
description: 'Whether to run pnpm build'
|
||||
required: true
|
||||
default: 'true'
|
||||
pnpm-build-filter:
|
||||
description: 'The filter argument for pnpm build (e.g. ./easytier-web/*)'
|
||||
required: false
|
||||
default: './easytier-web/*'
|
||||
gui:
|
||||
description: 'Whether to prepare the GUI build environment'
|
||||
required: true
|
||||
@@ -19,21 +26,61 @@ runs:
|
||||
- run: mkdir -p easytier-gui/dist
|
||||
shell: bash
|
||||
|
||||
- name: Setup Frontend Environment
|
||||
if: ${{ inputs.web == 'true' }}
|
||||
uses: ./.github/actions/prepare-pnpm
|
||||
with:
|
||||
build-filter: './easytier-web/*'
|
||||
|
||||
- name: Install GUI dependencies (Used by clippy)
|
||||
if: ${{ inputs.gui == 'true' }}
|
||||
- name: Install dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
bash ./.github/workflows/install_gui_dep.sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install -qqy build-essential mold musl-tools
|
||||
shell: bash
|
||||
|
||||
- name: Install Rust
|
||||
- name: Setup Frontend Environment
|
||||
if: ${{ inputs.pnpm == 'true' }}
|
||||
uses: ./.github/actions/prepare-pnpm
|
||||
with:
|
||||
build-filter: ${{ inputs.pnpm-build-filter }}
|
||||
|
||||
- name: Install GUI dependencies (Linux)
|
||||
if: ${{ inputs.gui == 'true' && runner.os == 'Linux' }}
|
||||
run: |
|
||||
bash ./.github/workflows/install_rust.sh
|
||||
sudo apt-get install -qq xdg-utils \
|
||||
libappindicator3-dev \
|
||||
libgtk-3-dev \
|
||||
librsvg2-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libxdo-dev
|
||||
shell: bash
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: 1.95
|
||||
target: ${{ !contains(inputs.target, 'mips') && inputs.target || '' }}
|
||||
components: ${{ contains(inputs.target, 'mips') && 'rust-src' || '' }}
|
||||
cache: false
|
||||
rustflags: ''
|
||||
|
||||
- name: Install Rust (MIPS)
|
||||
if: ${{ contains(inputs.target, 'mips') }}
|
||||
run: |
|
||||
MUSL_TARGET=${{ inputs.target }}sf
|
||||
mkdir -p ./musl_gcc
|
||||
wget --inet4-only -c https://github.com/cross-tools/musl-cross/releases/download/20250520/${MUSL_TARGET}.tar.xz -P ./musl_gcc/
|
||||
tar xf ./musl_gcc/${MUSL_TARGET}.tar.xz -C ./musl_gcc/
|
||||
sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/bin/*gcc /usr/bin/
|
||||
sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/include/ /usr/include/musl-cross
|
||||
sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/${MUSL_TARGET}/sysroot/ ./musl_gcc/sysroot
|
||||
sudo chmod -R a+rwx ./musl_gcc
|
||||
|
||||
if [[ -d "./musl_gcc/sysroot" ]]; then
|
||||
echo "BINDGEN_EXTRA_CLANG_ARGS=--sysroot=$(readlink -f ./musl_gcc/sysroot)" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
cd "$PWD/musl_gcc/${MUSL_TARGET}/lib/gcc/${MUSL_TARGET}/15.1.0" || exit 255
|
||||
# for panic-abort
|
||||
cp libgcc_eh.a libunwind.a
|
||||
|
||||
# for mimalloc
|
||||
ar x libgcc.a _ctzsi2.o _clz.o _bswapsi2.o
|
||||
ar rcs libctz.a _ctzsi2.o _clz.o _bswapsi2.o
|
||||
shell: bash
|
||||
|
||||
- name: Setup protoc
|
||||
|
||||
+123
-149
@@ -2,9 +2,14 @@ name: EasyTier Core
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["develop", "main", "releases/**"]
|
||||
branches: [ "develop", "main", "releases/**" ]
|
||||
pull_request:
|
||||
branches: ["develop", "main"]
|
||||
branches: [ "develop", "main" ]
|
||||
types: [ opened, synchronize, reopened, ready_for_review ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -18,6 +23,7 @@ jobs:
|
||||
pre_job:
|
||||
# continue-on-error: true # Uncomment once integration is finished
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
# Map a step output to a job output
|
||||
outputs:
|
||||
# do not skip push on branch starts with releases/
|
||||
@@ -30,7 +36,7 @@ jobs:
|
||||
concurrent_skipping: 'same_content_newer'
|
||||
skip_after_successful_duplicate: 'true'
|
||||
cancel_others: 'true'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/core.yml", ".github/workflows/install_rust.sh", "easytier-web/**"]'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/core.yml", ".github/actions/**", "easytier-web/**"]'
|
||||
build_web:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_job
|
||||
@@ -51,41 +57,48 @@ jobs:
|
||||
easytier-web/frontend/dist/*
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- TARGET: aarch64-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-aarch64
|
||||
- TARGET: x86_64-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-x86_64
|
||||
- TARGET: riscv64gc-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-riscv64
|
||||
- TARGET: mips-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-mips
|
||||
- TARGET: mipsel-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-mipsel
|
||||
- TARGET: armv7-unknown-linux-musleabihf # raspberry pi 2-3-4, not tested
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-armv7hf
|
||||
- TARGET: armv7-unknown-linux-musleabi # raspberry pi 2-3-4, not tested
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-armv7
|
||||
- TARGET: arm-unknown-linux-musleabihf # raspberry pi 0-1, not tested
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-armhf
|
||||
- TARGET: arm-unknown-linux-musleabi # raspberry pi 0-1, not tested
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: linux-arm
|
||||
- TARGET: aarch64-unknown-linux-musl
|
||||
OS: ubuntu-24.04-arm
|
||||
ARTIFACT_NAME: linux-aarch64
|
||||
|
||||
- TARGET: riscv64gc-unknown-linux-musl
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-riscv64
|
||||
- TARGET: loongarch64-unknown-linux-musl
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-loongarch64
|
||||
|
||||
- TARGET: armv7-unknown-linux-musleabihf # raspberry pi 2-3-4, not tested
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-armv7hf
|
||||
- TARGET: armv7-unknown-linux-musleabi # raspberry pi 2-3-4, not tested
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-armv7
|
||||
- TARGET: arm-unknown-linux-musleabihf # raspberry pi 0-1, not tested
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-armhf
|
||||
- TARGET: arm-unknown-linux-musleabi # raspberry pi 0-1, not tested
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-arm
|
||||
|
||||
- TARGET: mips-unknown-linux-musl
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-mips
|
||||
- TARGET: mipsel-unknown-linux-musl
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: linux-mipsel
|
||||
|
||||
- TARGET: x86_64-unknown-freebsd
|
||||
OS: ubuntu-24.04
|
||||
ARTIFACT_NAME: freebsd-13.2-x86_64
|
||||
BSD_VERSION: 13.2
|
||||
|
||||
- TARGET: x86_64-apple-darwin
|
||||
OS: macos-latest
|
||||
ARTIFACT_NAME: macos-x86_64
|
||||
@@ -96,17 +109,12 @@ jobs:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
ARTIFACT_NAME: windows-x86_64
|
||||
- TARGET: aarch64-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
ARTIFACT_NAME: windows-arm64
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
ARTIFACT_NAME: windows-i686
|
||||
|
||||
- TARGET: x86_64-unknown-freebsd
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: freebsd-13.2-x86_64
|
||||
BSD_VERSION: 13.2
|
||||
- TARGET: aarch64-pc-windows-msvc
|
||||
OS: windows-11-arm
|
||||
ARTIFACT_NAME: windows-arm64
|
||||
|
||||
runs-on: ${{ matrix.OS }}
|
||||
env:
|
||||
@@ -131,8 +139,15 @@ jobs:
|
||||
name: easytier-web-dashboard
|
||||
path: easytier-web/frontend/dist/
|
||||
|
||||
- name: Prepare build environment
|
||||
uses: ./.github/actions/prepare-build
|
||||
with:
|
||||
target: ${{ matrix.TARGET }}
|
||||
gui: true
|
||||
pnpm: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
|
||||
with:
|
||||
# The prefix cache key, this can be changed to start a new cache manually.
|
||||
# default: "v0-rust"
|
||||
@@ -140,96 +155,51 @@ jobs:
|
||||
shared-key: "core-registry"
|
||||
cache-targets: "false"
|
||||
|
||||
- name: Setup protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
- uses: mlugg/setup-zig@v2
|
||||
if: ${{ contains(matrix.OS, 'ubuntu') }}
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
if: ${{ contains(matrix.OS, 'ubuntu') }}
|
||||
with:
|
||||
# GitHub repo token to use to avoid rate limiter
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tool: cargo-zigbuild
|
||||
|
||||
- name: Build Core & Cli
|
||||
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
|
||||
run: |
|
||||
bash ./.github/workflows/install_rust.sh
|
||||
|
||||
# loongarch need llvm-18
|
||||
if [[ $TARGET =~ ^loongarch.*$ ]]; then
|
||||
sudo apt-get install -qq llvm-18 clang-18
|
||||
export LLVM_CONFIG_PATH=/usr/lib/llvm-18/bin/llvm-config
|
||||
fi
|
||||
# we set the sysroot when sysroot is a dir
|
||||
# this dir is a soft link generated by install_rust.sh
|
||||
# kcp-sys need this to gen ffi bindings. without this clang may fail to find some libc headers such as bits/libc-header-start.h
|
||||
if [[ -d "./musl_gcc/sysroot" ]]; then
|
||||
export BINDGEN_EXTRA_CLANG_ARGS=--sysroot=$(readlink -f ./musl_gcc/sysroot)
|
||||
fi
|
||||
|
||||
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
|
||||
cargo +nightly-2026-02-02 build -r --target $TARGET -Z build-std=std,panic_abort --package=easytier --features=jemalloc
|
||||
- name: Build
|
||||
if: ${{ !contains(matrix.TARGET, 'mips') }}
|
||||
run: |
|
||||
if [[ "$TARGET" == *windows* ]]; then
|
||||
SUFFIX=.exe
|
||||
else
|
||||
if [[ $OS =~ ^windows.*$ ]]; then
|
||||
SUFFIX=.exe
|
||||
CORE_FEATURES="--features=mimalloc"
|
||||
elif [[ $TARGET =~ ^riscv64.*$ || $TARGET =~ ^loongarch64.*$ || $TARGET =~ ^aarch64.*$ ]]; then
|
||||
CORE_FEATURES="--features=mimalloc"
|
||||
else
|
||||
CORE_FEATURES="--features=jemalloc"
|
||||
fi
|
||||
cargo build --release --target $TARGET --package=easytier-web --features=embed
|
||||
mv ./target/$TARGET/release/easytier-web"$SUFFIX" ./target/$TARGET/release/easytier-web-embed"$SUFFIX"
|
||||
cargo build --release --target $TARGET $CORE_FEATURES
|
||||
SUFFIX=""
|
||||
fi
|
||||
|
||||
# Copied and slightly modified from @lmq8267 (https://github.com/lmq8267)
|
||||
- name: Build Core & Cli (X86_64 FreeBSD)
|
||||
uses: vmactions/freebsd-vm@670398e4236735b8b65805c3da44b7a511fb8b27
|
||||
if: ${{ endsWith(matrix.TARGET, 'freebsd') }}
|
||||
if [[ "$TARGET" =~ (x86_64-unknown-linux-musl|aarch64-unknown-linux-musl|windows|darwin) ]]; then
|
||||
BUILD=build
|
||||
else
|
||||
BUILD=zigbuild
|
||||
fi
|
||||
|
||||
if [[ "$TARGET" =~ ^(riscv64|loongarch64|aarch64).*$ || "$TARGET" =~ windows ]]; then
|
||||
FEATURES="mimalloc"
|
||||
else
|
||||
FEATURES="jemalloc"
|
||||
fi
|
||||
|
||||
cargo $BUILD --release --target $TARGET --package=easytier-web --features=embed
|
||||
mv ./target/$TARGET/release/easytier-web"$SUFFIX" ./target/$TARGET/release/easytier-web-embed"$SUFFIX"
|
||||
|
||||
cargo $BUILD --release --target $TARGET --features=$FEATURES
|
||||
|
||||
- name: Build (MIPS)
|
||||
if: ${{ contains(matrix.TARGET, 'mips') }}
|
||||
env:
|
||||
TARGET: ${{ matrix.TARGET }}
|
||||
with:
|
||||
envs: TARGET
|
||||
release: ${{ matrix.BSD_VERSION }}
|
||||
arch: x86_64
|
||||
usesh: true
|
||||
mem: 6144
|
||||
cpu: 4
|
||||
run: |
|
||||
uname -a
|
||||
echo $SHELL
|
||||
pwd
|
||||
ls -lah
|
||||
whoami
|
||||
env | sort
|
||||
|
||||
pkg install -y git protobuf llvm-devel sudo curl
|
||||
curl --proto 'https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
. $HOME/.cargo/env
|
||||
|
||||
rustup set auto-self-update disable
|
||||
|
||||
rustup install 1.93
|
||||
rustup default 1.93
|
||||
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
export CARGO_TERM_COLOR=always
|
||||
|
||||
cargo build --release --verbose --target $TARGET --package=easytier-web --features=embed
|
||||
mv ./target/$TARGET/release/easytier-web ./target/$TARGET/release/easytier-web-embed
|
||||
cargo build --release --verbose --target $TARGET --features=mimalloc
|
||||
|
||||
mkdir -p built-bins/$TARGET/release/
|
||||
mv ./target/$TARGET/release/easytier-web-embed ./built-bins/$TARGET/release/easytier-web-embed
|
||||
mv ./target/$TARGET/release/easytier-web ./built-bins/$TARGET/release/easytier-web
|
||||
mv ./target/$TARGET/release/easytier-core ./built-bins/$TARGET/release/easytier-core
|
||||
mv ./target/$TARGET/release/easytier-cli ./built-bins/$TARGET/release/easytier-cli
|
||||
|
||||
# remove dirs to avoid copy many files back
|
||||
rm -rf ./target ~/.cargo
|
||||
mv ./built-bins ./target
|
||||
RUSTC_BOOTSTRAP: 1
|
||||
run: |
|
||||
cargo build -r --target $TARGET -Z build-std=std,panic_abort --package=easytier --features=jemalloc
|
||||
|
||||
- name: Compress
|
||||
run: |
|
||||
mkdir -p ./artifacts/objects/
|
||||
|
||||
# windows is the only OS using a different convention for executable file name
|
||||
if [[ $OS =~ ^windows.*$ ]]; then
|
||||
SUFFIX=.exe
|
||||
@@ -242,26 +212,37 @@ jobs:
|
||||
find "easytier/third_party/${ARCH_DIR}" -maxdepth 1 -type f \( -name "*.dll" -o -name "*.sys" \) -exec cp {} ./artifacts/objects/ \;
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $GITHUB_REF_TYPE =~ ^tag$ ]]; then
|
||||
TAG=$GITHUB_REF_NAME
|
||||
else
|
||||
TAG=$GITHUB_SHA
|
||||
fi
|
||||
|
||||
if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ (loongarch|freebsd) ]]; then
|
||||
HOST_ARCH=$(uname -m)
|
||||
case $HOST_ARCH in
|
||||
x86_64) UPX_ARCH="amd64" ;;
|
||||
aarch64) UPX_ARCH="arm64" ;;
|
||||
*) UPX_ARCH="amd64" ;;
|
||||
esac
|
||||
|
||||
if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^.*freebsd$ && ! $TARGET =~ ^loongarch.*$ && ! $TARGET =~ ^riscv64.*$ ]]; then
|
||||
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 -
|
||||
cp upx-${UPX_VERSION}-amd64_linux/upx .
|
||||
./upx --lzma --best ./target/$TARGET/release/easytier-core"$SUFFIX"
|
||||
./upx --lzma --best ./target/$TARGET/release/easytier-cli"$SUFFIX"
|
||||
UPX_VERSION=5.1.1
|
||||
UPX_PKG="upx-${UPX_VERSION}-${UPX_ARCH}_linux"
|
||||
curl -L "https://github.com/upx/upx/releases/download/v${UPX_VERSION}/${UPX_PKG}.tar.xz" -s | tar xJvf -
|
||||
cp "${UPX_PKG}/upx" .
|
||||
UPX_BIN=./upx
|
||||
fi
|
||||
|
||||
mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/
|
||||
mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/
|
||||
if [[ ! $TARGET =~ ^mips.*$ ]]; then
|
||||
mv ./target/$TARGET/release/easytier-web"$SUFFIX" ./artifacts/objects/
|
||||
mv ./target/$TARGET/release/easytier-web-embed"$SUFFIX" ./artifacts/objects/
|
||||
fi
|
||||
for BIN in ./target/$TARGET/release/easytier-{core,cli,web,web-embed}"$SUFFIX"; do
|
||||
if [[ -f "$BIN" ]]; then
|
||||
if [[ -n "$UPX_BIN" ]]; then
|
||||
$UPX_BIN --lzma --best "$BIN" || true
|
||||
fi
|
||||
|
||||
mv "$BIN" ./artifacts/objects/
|
||||
fi
|
||||
done
|
||||
|
||||
mv ./artifacts/objects/* ./artifacts/
|
||||
rm -rf ./artifacts/objects/
|
||||
@@ -273,25 +254,10 @@ jobs:
|
||||
path: |
|
||||
./artifacts/*
|
||||
|
||||
core-result:
|
||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- pre_job
|
||||
- build_web
|
||||
- build
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
if: needs.build.result != 'success'
|
||||
run: exit 1
|
||||
|
||||
magisk_build:
|
||||
needs:
|
||||
- pre_job
|
||||
- build_web
|
||||
- build
|
||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||
build_magisk:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ pre_job, build_web, build ]
|
||||
if: needs.pre_job.result == 'success' && needs.pre_job.outputs.should_skip != 'true' && !cancelled()
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5 # 必须先检出代码才能获取模块配置
|
||||
@@ -311,7 +277,6 @@ jobs:
|
||||
cp ./downloaded-binaries/easytier-cli ./easytier-contrib/easytier-magisk/
|
||||
cp ./downloaded-binaries/easytier-web ./easytier-contrib/easytier-magisk/
|
||||
|
||||
|
||||
# 上传生成的模块
|
||||
- name: Upload Magisk Module
|
||||
uses: actions/upload-artifact@v5
|
||||
@@ -322,3 +287,12 @@ jobs:
|
||||
!./easytier-contrib/easytier-magisk/build.sh
|
||||
!./easytier-contrib/easytier-magisk/magisk_update.json
|
||||
if-no-files-found: error
|
||||
|
||||
core-result:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ pre_job, build_web, build, build_magisk ]
|
||||
if: needs.pre_job.result == 'success' && needs.pre_job.outputs.should_skip != 'true' && !cancelled()
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
|
||||
+39
-86
@@ -5,7 +5,12 @@ on:
|
||||
branches: ["develop", "main", "releases/**"]
|
||||
pull_request:
|
||||
branches: ["develop", "main"]
|
||||
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -18,6 +23,7 @@ jobs:
|
||||
pre_job:
|
||||
# continue-on-error: true # Uncomment once integration is finished
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
# Map a step output to a job output
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip == 'true' && !startsWith(github.ref_name, 'releases/') }}
|
||||
@@ -29,20 +35,20 @@ jobs:
|
||||
concurrent_skipping: 'same_content_newer'
|
||||
skip_after_successful_duplicate: 'true'
|
||||
cancel_others: 'true'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", ".github/workflows/gui.yml", ".github/workflows/install_rust.sh", ".github/workflows/install_gui_dep.sh", "easytier-web/frontend-lib/**"]'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", ".github/workflows/gui.yml", ".github/actions/**", "easytier-web/frontend-lib/**"]'
|
||||
build-gui:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- TARGET: aarch64-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
GUI_TARGET: aarch64-unknown-linux-gnu
|
||||
ARTIFACT_NAME: linux-aarch64
|
||||
- TARGET: x86_64-unknown-linux-musl
|
||||
OS: ubuntu-22.04
|
||||
OS: ubuntu-24.04
|
||||
GUI_TARGET: x86_64-unknown-linux-gnu
|
||||
ARTIFACT_NAME: linux-x86_64
|
||||
- TARGET: aarch64-unknown-linux-musl
|
||||
OS: ubuntu-24.04-arm
|
||||
GUI_TARGET: aarch64-unknown-linux-gnu
|
||||
ARTIFACT_NAME: linux-aarch64
|
||||
|
||||
- TARGET: x86_64-apple-darwin
|
||||
OS: macos-latest
|
||||
@@ -57,16 +63,14 @@ jobs:
|
||||
OS: windows-latest
|
||||
GUI_TARGET: x86_64-pc-windows-msvc
|
||||
ARTIFACT_NAME: windows-x86_64
|
||||
|
||||
- TARGET: aarch64-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
GUI_TARGET: aarch64-pc-windows-msvc
|
||||
ARTIFACT_NAME: windows-arm64
|
||||
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
OS: windows-latest
|
||||
GUI_TARGET: i686-pc-windows-msvc
|
||||
ARTIFACT_NAME: windows-i686
|
||||
- TARGET: aarch64-pc-windows-msvc
|
||||
OS: windows-11-arm
|
||||
GUI_TARGET: aarch64-pc-windows-msvc
|
||||
ARTIFACT_NAME: windows-arm64
|
||||
|
||||
runs-on: ${{ matrix.OS }}
|
||||
env:
|
||||
@@ -80,75 +84,29 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install GUI dependencies (x86 only)
|
||||
if: ${{ matrix.TARGET == 'x86_64-unknown-linux-musl' }}
|
||||
run: bash ./.github/workflows/install_gui_dep.sh
|
||||
|
||||
- name: Install GUI cross compile (aarch64 only)
|
||||
if: ${{ matrix.TARGET == 'aarch64-unknown-linux-musl' }}
|
||||
run: |
|
||||
# see https://tauri.app/v1/guides/building/linux/
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy main restricted" | sudo tee /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu/ jammy-security main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu/ jammy-security universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu/ jammy-security multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security universe" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security multiverse" | sudo tee -a /etc/apt/sources.list
|
||||
|
||||
sudo dpkg --add-architecture arm64
|
||||
sudo apt update
|
||||
sudo apt install aptitude
|
||||
sudo aptitude install -y libgstreamer1.0-0:arm64 gstreamer1.0-plugins-base:arm64 gstreamer1.0-plugins-good:arm64 \
|
||||
libgstreamer-gl1.0-0:arm64 libgstreamer-plugins-base1.0-0:arm64 libgstreamer-plugins-good1.0-0:arm64 libwebkit2gtk-4.1-0:arm64 \
|
||||
libwebkit2gtk-4.1-dev:arm64 libssl-dev:arm64 gcc-aarch64-linux-gnu libsoup-3.0-dev:arm64 libjavascriptcoregtk-4.1-dev:arm64
|
||||
echo "PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/" >> "$GITHUB_ENV"
|
||||
echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install rpm package (Linux target only)
|
||||
if: ${{ contains(matrix.TARGET, '-linux-') }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y rpm
|
||||
|
||||
- name: Set current ref as env variable
|
||||
run: |
|
||||
echo "GIT_DESC=$(git log -1 --format=%cd.%h --date=format:%Y-%m-%d_%H:%M:%S)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Frontend Environment
|
||||
uses: ./.github/actions/prepare-pnpm
|
||||
- name: Prepare build environment
|
||||
uses: ./.github/actions/prepare-build
|
||||
with:
|
||||
target: ${{ matrix.TARGET }}
|
||||
gui: true
|
||||
pnpm: true
|
||||
pnpm-build-filter: ''
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# The prefix cache key, this can be changed to start a new cache manually.
|
||||
# default: "v0-rust"
|
||||
prefix-key: ""
|
||||
|
||||
- name: Install rust target
|
||||
run: bash ./.github/workflows/install_rust.sh
|
||||
|
||||
- name: Setup protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
# GitHub repo token to use to avoid rate limiter
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
shared-key: "gui-registry"
|
||||
cache-targets: "false"
|
||||
|
||||
- name: copy correct DLLs
|
||||
if: ${{ matrix.OS == 'windows-latest' }}
|
||||
if: ${{ contains(matrix.GUI_TARGET, 'windows') }}
|
||||
run: |
|
||||
case $TARGET in
|
||||
x86_64*) ARCH_DIR=x86_64 ;;
|
||||
@@ -164,10 +122,9 @@ jobs:
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
with:
|
||||
projectPath: ./easytier-gui
|
||||
# https://tauri.app/v1/guides/building/linux/#cross-compiling-tauri-applications-for-arm-based-devices
|
||||
args: --verbose --target ${{ matrix.GUI_TARGET }} ${{ contains(matrix.TARGET, '-linux-') && contains(matrix.TARGET, 'aarch64') && '--bundles deb,rpm' || '' }}
|
||||
args: --verbose --target ${{ matrix.GUI_TARGET }}
|
||||
|
||||
- name: Compress
|
||||
- name: Collect artifact
|
||||
run: |
|
||||
mkdir -p ./artifacts/objects/
|
||||
|
||||
@@ -176,18 +133,16 @@ jobs:
|
||||
else
|
||||
TAG=$GITHUB_SHA
|
||||
fi
|
||||
|
||||
# copy gui bundle, gui is built without specific target
|
||||
if [[ $OS =~ ^windows.*$ ]]; then
|
||||
if [[ $GUI_TARGET =~ windows ]]; then
|
||||
mv ./target/$GUI_TARGET/release/bundle/nsis/*.exe ./artifacts/objects/
|
||||
elif [[ $OS =~ ^macos.*$ ]]; then
|
||||
elif [[ $GUI_TARGET =~ darwin ]]; then
|
||||
mv ./target/$GUI_TARGET/release/bundle/dmg/*.dmg ./artifacts/objects/
|
||||
elif [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^mips.*$ ]]; then
|
||||
elif [[ $GUI_TARGET =~ linux ]]; then
|
||||
mv ./target/$GUI_TARGET/release/bundle/deb/*.deb ./artifacts/objects/
|
||||
mv ./target/$GUI_TARGET/release/bundle/rpm/*.rpm ./artifacts/objects/
|
||||
if [[ $GUI_TARGET =~ ^x86_64.*$ ]]; then
|
||||
# currently only x86 appimage is supported
|
||||
mv ./target/$GUI_TARGET/release/bundle/appimage/*.AppImage ./artifacts/objects/
|
||||
fi
|
||||
mv ./target/$GUI_TARGET/release/bundle/appimage/*.AppImage ./artifacts/objects/
|
||||
fi
|
||||
|
||||
mv ./artifacts/objects/* ./artifacts/
|
||||
@@ -201,12 +156,10 @@ jobs:
|
||||
./artifacts/*
|
||||
|
||||
gui-result:
|
||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- pre_job
|
||||
- build-gui
|
||||
needs: [ pre_job, build-gui ]
|
||||
if: needs.pre_job.result == 'success' && needs.pre_job.outputs.should_skip != 'true' && !cancelled()
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
if: needs.build-gui.result != 'success'
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
sudo apt update
|
||||
sudo apt install -qq libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libgtk-3-dev \
|
||||
librsvg2-dev \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
patchelf
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# env needed:
|
||||
# - TARGET
|
||||
# - GUI_TARGET
|
||||
# - OS
|
||||
|
||||
# dependencies are only needed on ubuntu as that's the only place where
|
||||
# we make cross-compilation
|
||||
if [[ $OS =~ ^ubuntu.*$ ]]; then
|
||||
sudo apt-get update && sudo apt-get install -qq musl-tools libappindicator3-dev llvm clang
|
||||
# https://github.com/cross-tools/musl-cross/releases
|
||||
# if "musl" is a substring of TARGET, we assume that we are using musl
|
||||
MUSL_TARGET=$TARGET
|
||||
# if target is mips or mipsel, we should use soft-float version of musl
|
||||
if [[ $TARGET =~ ^mips.*$ || $TARGET =~ ^mipsel.*$ ]]; then
|
||||
MUSL_TARGET=${TARGET}sf
|
||||
elif [[ $TARGET =~ ^riscv64gc-.*$ ]]; then
|
||||
MUSL_TARGET=${TARGET/#riscv64gc-/riscv64-}
|
||||
fi
|
||||
if [[ $MUSL_TARGET =~ musl ]]; then
|
||||
mkdir -p ./musl_gcc
|
||||
wget --inet4-only -c https://github.com/cross-tools/musl-cross/releases/download/20250520/${MUSL_TARGET}.tar.xz -P ./musl_gcc/
|
||||
tar xf ./musl_gcc/${MUSL_TARGET}.tar.xz -C ./musl_gcc/
|
||||
sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/bin/*gcc /usr/bin/
|
||||
sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/include/ /usr/include/musl-cross
|
||||
sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/${MUSL_TARGET}/sysroot/ ./musl_gcc/sysroot
|
||||
sudo chmod -R a+rwx ./musl_gcc
|
||||
fi
|
||||
fi
|
||||
|
||||
# see https://github.com/rust-lang/rustup/issues/3709
|
||||
rustup set auto-self-update disable
|
||||
rustup install 1.93
|
||||
rustup default 1.93
|
||||
|
||||
# mips/mipsel cannot add target from rustup, need compile by ourselves
|
||||
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
|
||||
cd "$PWD/musl_gcc/${MUSL_TARGET}/lib/gcc/${MUSL_TARGET}/15.1.0" || exit 255
|
||||
# for panic-abort
|
||||
cp libgcc_eh.a libunwind.a
|
||||
|
||||
# for mimalloc
|
||||
ar x libgcc.a _ctzsi2.o _clz.o _bswapsi2.o
|
||||
ar rcs libctz.a _ctzsi2.o _clz.o _bswapsi2.o
|
||||
|
||||
rustup toolchain install nightly-2026-02-02-x86_64-unknown-linux-gnu
|
||||
rustup component add rust-src --toolchain nightly-2026-02-02-x86_64-unknown-linux-gnu
|
||||
|
||||
# https://github.com/rust-lang/rust/issues/128808
|
||||
# remove it after Cargo or rustc fix this.
|
||||
RUST_LIB_SRC=$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/
|
||||
if [[ -f $RUST_LIB_SRC/library/Cargo.lock && ! -f $RUST_LIB_SRC/Cargo.lock ]]; then
|
||||
cp -f $RUST_LIB_SRC/library/Cargo.lock $RUST_LIB_SRC/Cargo.lock
|
||||
fi
|
||||
else
|
||||
rustup target add $TARGET
|
||||
if [[ $GUI_TARGET != '' ]]; then
|
||||
rustup target add $GUI_TARGET
|
||||
fi
|
||||
fi
|
||||
@@ -5,7 +5,12 @@ on:
|
||||
branches: ["develop", "main", "releases/**"]
|
||||
pull_request:
|
||||
branches: ["develop", "main"]
|
||||
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -18,6 +23,7 @@ jobs:
|
||||
pre_job:
|
||||
# continue-on-error: true # Uncomment once integration is finished
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
# Map a step output to a job output
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip == 'true' && !startsWith(github.ref_name, 'releases/') }}
|
||||
@@ -29,20 +35,25 @@ jobs:
|
||||
concurrent_skipping: 'same_content_newer'
|
||||
skip_after_successful_duplicate: 'true'
|
||||
cancel_others: 'true'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", "tauri-plugin-vpnservice/**", ".github/workflows/mobile.yml", ".github/workflows/install_rust.sh"]'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", "tauri-plugin-vpnservice/**", ".github/workflows/mobile.yml", ".github/actions/**"]'
|
||||
build-mobile:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- TARGET: android
|
||||
OS: ubuntu-22.04
|
||||
ARTIFACT_NAME: android
|
||||
runs-on: ${{ matrix.OS }}
|
||||
- TARGET: aarch64-linux-android
|
||||
ARCH: aarch64
|
||||
- TARGET: armv7-linux-androideabi
|
||||
ARCH: armv7
|
||||
- TARGET: i686-linux-android
|
||||
ARCH: i686
|
||||
- TARGET: x86_64-linux-android
|
||||
ARCH: x86_64
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NAME: easytier
|
||||
TARGET: ${{ matrix.TARGET }}
|
||||
OS: ${{ matrix.OS }}
|
||||
ARCH: ${{ matrix.ARCH }}
|
||||
OSS_BUCKET: ${{ secrets.ALIYUN_OSS_BUCKET }}
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
@@ -61,47 +72,41 @@ jobs:
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
cmdline-tools-version: 11076708
|
||||
packages: 'build-tools;34.0.0 ndk;26.0.10792818 tools platform-tools platforms;android-34 '
|
||||
cmdline-tools-version: 12.0
|
||||
packages: 'build-tools;34.0.0 ndk;26.0.10792818 platform-tools platforms;android-34 '
|
||||
|
||||
- name: Setup Android Environment
|
||||
run: |
|
||||
echo "$ANDROID_HOME/platform-tools" >> $GITHUB_PATH
|
||||
echo "$ANDROID_HOME/ndk/26.0.10792818/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH
|
||||
echo "NDK_HOME=$ANDROID_HOME/ndk/26.0.10792818/" > $GITHUB_ENV
|
||||
echo "NDK_HOME=$ANDROID_HOME/ndk/26.0.10792818/" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Frontend Environment
|
||||
uses: ./.github/actions/prepare-pnpm
|
||||
- name: Prepare build environment
|
||||
uses: ./.github/actions/prepare-build
|
||||
with:
|
||||
target: ${{ matrix.TARGET }}
|
||||
gui: false
|
||||
pnpm: true
|
||||
pnpm-build-filter: ''
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# The prefix cache key, this can be changed to start a new cache manually.
|
||||
# default: "v0-rust"
|
||||
prefix-key: ""
|
||||
shared-key: "gui-registry"
|
||||
cache-targets: "false"
|
||||
|
||||
- name: Install rust target
|
||||
run: |
|
||||
bash ./.github/workflows/install_rust.sh
|
||||
rustup target add aarch64-linux-android
|
||||
rustup target add armv7-linux-androideabi
|
||||
rustup target add i686-linux-android
|
||||
rustup target add x86_64-linux-android
|
||||
|
||||
- name: Setup protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
# GitHub repo token to use to avoid rate limiter
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Android
|
||||
- name: Build
|
||||
run: |
|
||||
cd easytier-gui
|
||||
pnpm tauri android build
|
||||
pnpm tauri android build --apk --target "$ARCH" --split-per-abi
|
||||
|
||||
- name: Compress
|
||||
- name: Collect artifact
|
||||
run: |
|
||||
mkdir -p ./artifacts/objects/
|
||||
mv easytier-gui/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk ./artifacts/objects/
|
||||
mv easytier-gui/src-tauri/gen/android/app/build/outputs/apk/*/release/*.apk ./artifacts/objects/
|
||||
|
||||
if [[ $GITHUB_REF_TYPE =~ ^tag$ ]]; then
|
||||
TAG=$GITHUB_REF_NAME
|
||||
@@ -109,23 +114,21 @@ jobs:
|
||||
TAG=$GITHUB_SHA
|
||||
fi
|
||||
|
||||
mv ./artifacts/objects/* ./artifacts
|
||||
mv ./artifacts/objects/* ./artifacts/
|
||||
rm -rf ./artifacts/objects/
|
||||
|
||||
- name: Archive artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: easytier-gui-${{ matrix.ARTIFACT_NAME }}
|
||||
name: easytier-mobile-android-${{ matrix.ARCH }}
|
||||
path: |
|
||||
./artifacts/*
|
||||
|
||||
mobile-result:
|
||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- pre_job
|
||||
- build-mobile
|
||||
needs: [ pre_job, build-mobile ]
|
||||
if: needs.pre_job.result == 'success' && needs.pre_job.outputs.should_skip != 'true' && !cancelled()
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
if: needs.build-mobile.result != 'success'
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
|
||||
@@ -6,14 +6,22 @@ on:
|
||||
paths:
|
||||
- "**/*.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
pull_request:
|
||||
branches: ["main", "develop"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- "**/*.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-full-shell:
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
@@ -26,5 +34,11 @@ jobs:
|
||||
- name: Magic Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@v6
|
||||
|
||||
- name: Check full devShell
|
||||
- name: Warm up full devShell
|
||||
run: nix develop .#full --command true
|
||||
|
||||
- name: Cargo check in flake environment
|
||||
run: nix develop .#full --command cargo check
|
||||
|
||||
- name: Cargo build in flake environment
|
||||
run: nix develop .#full --command cargo build
|
||||
|
||||
+33
-12
@@ -8,8 +8,13 @@ on:
|
||||
- '!*-pre'
|
||||
pull_request:
|
||||
branches: ["develop", "main"]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -20,18 +25,29 @@ defaults:
|
||||
|
||||
jobs:
|
||||
cargo_fmt_check:
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: fmt check
|
||||
|
||||
- name: Prepare build environment
|
||||
uses: ./.github/actions/prepare-build
|
||||
with:
|
||||
gui: false
|
||||
pnpm: false
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: Check formatting
|
||||
working-directory: ./easytier-contrib/easytier-ohrs
|
||||
run: |
|
||||
bash ../../.github/workflows/install_rust.sh
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
pre_job:
|
||||
# continue-on-error: true # Uncomment once integration is finished
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
# Map a step output to a job output
|
||||
outputs:
|
||||
# do not skip push on branch starts with releases/
|
||||
@@ -44,7 +60,8 @@ jobs:
|
||||
concurrent_skipping: "same_content_newer"
|
||||
skip_after_successful_duplicate: "true"
|
||||
cancel_others: "true"
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-contrib/easytier-ohrs/**", ".github/workflows/ohos.yml", ".github/workflows/install_rust.sh"]'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-contrib/easytier-ohrs/**", ".github/workflows/ohos.yml", ".github/actions/**"]'
|
||||
|
||||
build-ohos:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_job
|
||||
@@ -56,13 +73,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
sudo apt-get install -qq \
|
||||
build-essential \
|
||||
wget \
|
||||
unzip \
|
||||
git \
|
||||
pkg-config curl libgl1-mesa-dev expect
|
||||
sudo apt-get clean
|
||||
|
||||
- name: Resolve easytier version
|
||||
run: |
|
||||
@@ -134,6 +150,15 @@ jobs:
|
||||
run: |
|
||||
echo "TARGET_ARCH=aarch64-linux-ohos" >> $GITHUB_ENV
|
||||
|
||||
rustup install stable
|
||||
rustup default stable
|
||||
|
||||
rustup target add aarch64-unknown-linux-ohos
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: ohrs
|
||||
|
||||
- name: Create clang wrapper script
|
||||
run: |
|
||||
sudo mkdir -p $OHOS_NDK_HOME/native/llvm
|
||||
@@ -152,11 +177,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get install -y llvm clang lldb lld
|
||||
sudo apt-get install -y protobuf-compiler
|
||||
bash ../../.github/workflows/install_rust.sh
|
||||
source env.sh
|
||||
cargo install ohrs
|
||||
rustup target add aarch64-unknown-linux-ohos
|
||||
cargo update easytier
|
||||
ohrs doctor
|
||||
ohrs build --release --arch aarch
|
||||
ohrs artifact
|
||||
|
||||
+14
-12
@@ -6,6 +6,10 @@ on:
|
||||
pull_request:
|
||||
branches: [ "develop", "main" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
# RUSTC_WRAPPER: "sccache"
|
||||
@@ -30,7 +34,7 @@ jobs:
|
||||
# All of these options are optional, so you can remove them if you are happy with the defaults
|
||||
concurrent_skipping: 'never'
|
||||
skip_after_successful_duplicate: 'true'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/test.yml", ".github/workflows/install_gui_dep.sh", ".github/workflows/install_rust.sh"]'
|
||||
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/test.yml", ".github/actions/**"]'
|
||||
|
||||
check:
|
||||
name: Run linters & check
|
||||
@@ -44,15 +48,13 @@ jobs:
|
||||
uses: ./.github/actions/prepare-build
|
||||
with:
|
||||
gui: true
|
||||
web: true
|
||||
pnpm: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install rustfmt and clippy
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
rustup component add clippy
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
components: rustfmt,clippy
|
||||
rustflags: ''
|
||||
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
@@ -85,7 +87,7 @@ jobs:
|
||||
uses: ./.github/actions/prepare-build
|
||||
with:
|
||||
gui: true
|
||||
web: true
|
||||
pnpm: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -146,9 +148,9 @@ jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ pre_job, test_matrix ]
|
||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||
needs: [ pre_job, check, test_matrix ]
|
||||
if: needs.pre_job.result == 'success' && needs.pre_job.outputs.should_skip != 'true' && !cancelled()
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
if: needs.test_matrix.result != 'success'
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ Thank you for your interest in contributing to EasyTier! This document provides
|
||||
#### Required Tools
|
||||
- Node.js v21 or higher
|
||||
- pnpm v9 or higher
|
||||
- Rust toolchain (version 1.93)
|
||||
- Rust toolchain (version 1.95)
|
||||
- LLVM and Clang
|
||||
- Protoc (Protocol Buffers compiler)
|
||||
|
||||
@@ -79,8 +79,8 @@ sudo apt install -y bridge-utils
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
# Install Rust toolchain
|
||||
rustup install 1.93
|
||||
rustup default 1.93
|
||||
rustup install 1.95
|
||||
rustup default 1.95
|
||||
|
||||
# Install project dependencies
|
||||
pnpm -r install
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@
|
||||
#### 必需工具
|
||||
- Node.js v21 或更高版本
|
||||
- pnpm v9 或更高版本
|
||||
- Rust 工具链(版本 1.93)
|
||||
- Rust 工具链(版本 1.95)
|
||||
- LLVM 和 Clang
|
||||
- Protoc(Protocol Buffers 编译器)
|
||||
|
||||
@@ -87,8 +87,8 @@ sudo apt install -y bridge-utils
|
||||
2. 安装依赖:
|
||||
```bash
|
||||
# 安装 Rust 工具链
|
||||
rustup install 1.93
|
||||
rustup default 1.93
|
||||
rustup install 1.95
|
||||
rustup default 1.95
|
||||
|
||||
# 安装项目依赖
|
||||
pnpm -r install
|
||||
|
||||
Generated
+1139
-968
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,10 @@ exclude = [
|
||||
"easytier-contrib/easytier-ohrs", # it needs ohrs sdk
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
|
||||
[profile.dev]
|
||||
panic = "unwind"
|
||||
debug = 2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "easytier-android-jni"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use easytier::proto::api::manage::{NetworkInstanceRunningInfo, NetworkInstanceRunningInfoMap};
|
||||
use jni::JNIEnv;
|
||||
use jni::objects::{JClass, JObjectArray, JString};
|
||||
use jni::sys::{jint, jstring};
|
||||
use jni::JNIEnv;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ptr;
|
||||
@@ -15,7 +15,7 @@ pub struct KeyValuePair {
|
||||
}
|
||||
|
||||
// 声明外部 C 函数
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
fn set_tun_fd(inst_name: *const std::ffi::c_char, fd: std::ffi::c_int) -> std::ffi::c_int;
|
||||
fn get_error_msg(out: *mut *const std::ffi::c_char);
|
||||
fn free_string(s: *const std::ffi::c_char);
|
||||
@@ -68,7 +68,7 @@ fn throw_exception(env: &mut JNIEnv, message: &str) {
|
||||
}
|
||||
|
||||
/// 设置 TUN 文件描述符
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_setTunFd(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -87,17 +87,17 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_setTunFd(
|
||||
|
||||
unsafe {
|
||||
let result = set_tun_fd(inst_name_cstr.as_ptr(), fd);
|
||||
if result != 0 {
|
||||
if let Some(error) = get_last_error() {
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
if result != 0
|
||||
&& let Some(error) = get_last_error()
|
||||
{
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析配置
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_parseConfig(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -115,17 +115,17 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_parseConfig(
|
||||
|
||||
unsafe {
|
||||
let result = parse_config(config_cstr.as_ptr());
|
||||
if result != 0 {
|
||||
if let Some(error) = get_last_error() {
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
if result != 0
|
||||
&& let Some(error) = get_last_error()
|
||||
{
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 运行网络实例
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_runNetworkInstance(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -143,17 +143,17 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_runNetworkInstance(
|
||||
|
||||
unsafe {
|
||||
let result = run_network_instance(config_cstr.as_ptr());
|
||||
if result != 0 {
|
||||
if let Some(error) = get_last_error() {
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
if result != 0
|
||||
&& let Some(error) = get_last_error()
|
||||
{
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 保持网络实例
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -165,10 +165,10 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
|
||||
if instance_names.is_null() {
|
||||
unsafe {
|
||||
let result = retain_network_instance(ptr::null(), 0);
|
||||
if result != 0 {
|
||||
if let Some(error) = get_last_error() {
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
if result != 0
|
||||
&& let Some(error) = get_last_error()
|
||||
{
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -187,10 +187,10 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
|
||||
if array_length == 0 {
|
||||
unsafe {
|
||||
let result = retain_network_instance(ptr::null(), 0);
|
||||
if result != 0 {
|
||||
if let Some(error) = get_last_error() {
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
if result != 0
|
||||
&& let Some(error) = get_last_error()
|
||||
{
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -234,17 +234,17 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
|
||||
|
||||
unsafe {
|
||||
let result = retain_network_instance(c_string_ptrs.as_ptr(), c_string_ptrs.len());
|
||||
if result != 0 {
|
||||
if let Some(error) = get_last_error() {
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
if result != 0
|
||||
&& let Some(error) = get_last_error()
|
||||
{
|
||||
throw_exception(&mut env, &error);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 收集网络信息
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_collectNetworkInfos(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -304,7 +304,7 @@ pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_collectNetworkInfos(
|
||||
}
|
||||
|
||||
/// 获取最后的错误信息
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_getLastError(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "easytier-ffi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -30,7 +30,7 @@ fn set_error_msg(msg: &str) {
|
||||
|
||||
/// # Safety
|
||||
/// Set the tun fd
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn set_tun_fd(
|
||||
inst_name: *const std::ffi::c_char,
|
||||
fd: std::ffi::c_int,
|
||||
@@ -59,7 +59,7 @@ pub unsafe extern "C" fn set_tun_fd(
|
||||
|
||||
/// # Safety
|
||||
/// Get the last error message
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn get_error_msg(out: *mut *const std::ffi::c_char) {
|
||||
let msg_buf = ERROR_MSG.lock().unwrap();
|
||||
if msg_buf.is_empty() {
|
||||
@@ -74,7 +74,7 @@ pub unsafe extern "C" fn get_error_msg(out: *mut *const std::ffi::c_char) {
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn free_string(s: *const std::ffi::c_char) {
|
||||
if s.is_null() {
|
||||
return;
|
||||
@@ -86,7 +86,7 @@ pub extern "C" fn free_string(s: *const std::ffi::c_char) {
|
||||
|
||||
/// # Safety
|
||||
/// Parse the config
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn parse_config(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int {
|
||||
let cfg_str = unsafe {
|
||||
assert!(!cfg_str.is_null());
|
||||
@@ -105,7 +105,7 @@ pub unsafe extern "C" fn parse_config(cfg_str: *const std::ffi::c_char) -> std::
|
||||
|
||||
/// # Safety
|
||||
/// Run the network instance
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn run_network_instance(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int {
|
||||
let cfg_str = unsafe {
|
||||
assert!(!cfg_str.is_null());
|
||||
@@ -144,7 +144,7 @@ pub unsafe extern "C" fn run_network_instance(cfg_str: *const std::ffi::c_char)
|
||||
|
||||
/// # Safety
|
||||
/// Retain the network instance
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn retain_network_instance(
|
||||
inst_names: *const *const std::ffi::c_char,
|
||||
length: usize,
|
||||
@@ -188,7 +188,7 @@ pub unsafe extern "C" fn retain_network_instance(
|
||||
|
||||
/// # Safety
|
||||
/// Collect the network infos
|
||||
#[no_mangle]
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn collect_network_infos(
|
||||
infos: *mut KeyValuePair,
|
||||
max_length: usize,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "easytier-uptime"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ops::{Div, Mul};
|
||||
|
||||
use axum::extract::{Path, State};
|
||||
use axum::Json;
|
||||
use axum::extract::{Path, State};
|
||||
use sea_orm::{
|
||||
ColumnTrait, Condition, EntityTrait, IntoActiveModel, ModelTrait, Order, PaginatorTrait,
|
||||
QueryFilter, QueryOrder, QuerySelect, Set, TryIntoModel,
|
||||
@@ -14,7 +14,7 @@ use crate::api::{
|
||||
models::*,
|
||||
};
|
||||
use crate::db::entity::{self, health_records, shared_nodes};
|
||||
use crate::db::{operations::*, Db};
|
||||
use crate::db::{Db, operations::*};
|
||||
use crate::health_checker_manager::HealthCheckerManager;
|
||||
use axum_extra::extract::Query;
|
||||
use std::sync::Arc;
|
||||
@@ -273,7 +273,7 @@ pub struct InstanceFilterParams {
|
||||
use crate::config::AppConfig;
|
||||
use axum::http::{HeaderMap, StatusCode};
|
||||
use chrono::{Duration, Utc};
|
||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -370,19 +370,19 @@ pub async fn admin_get_nodes(
|
||||
let ids = NodeOperations::filter_node_ids_by_tag(&app_state.db, &tag).await?;
|
||||
filtered_ids = Some(ids);
|
||||
}
|
||||
if let Some(tags) = filters.tags {
|
||||
if !tags.is_empty() {
|
||||
let ids_any = NodeOperations::filter_node_ids_by_tags_any(&app_state.db, &tags).await?;
|
||||
filtered_ids = match filtered_ids {
|
||||
Some(mut existing) => {
|
||||
existing.extend(ids_any);
|
||||
existing.sort();
|
||||
existing.dedup();
|
||||
Some(existing)
|
||||
}
|
||||
None => Some(ids_any),
|
||||
};
|
||||
}
|
||||
if let Some(tags) = filters.tags
|
||||
&& !tags.is_empty()
|
||||
{
|
||||
let ids_any = NodeOperations::filter_node_ids_by_tags_any(&app_state.db, &tags).await?;
|
||||
filtered_ids = match filtered_ids {
|
||||
Some(mut existing) => {
|
||||
existing.extend(ids_any);
|
||||
existing.sort();
|
||||
existing.dedup();
|
||||
Some(existing)
|
||||
}
|
||||
None => Some(ids_any),
|
||||
};
|
||||
}
|
||||
if let Some(ids) = filtered_ids {
|
||||
if ids.is_empty() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::routing::{delete, get, post, put};
|
||||
use axum::Router;
|
||||
use axum::routing::{delete, get, post, put};
|
||||
use tower_http::compression::CompressionLayer;
|
||||
use tower_http::cors::CorsLayer;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::db::entity::*;
|
||||
use crate::db::Db;
|
||||
use crate::db::entity::*;
|
||||
use sea_orm::*;
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tokio::time::{Duration, sleep};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
/// 数据清理策略配置
|
||||
|
||||
@@ -5,12 +5,12 @@ pub mod operations;
|
||||
use std::fmt;
|
||||
|
||||
use sea_orm::{
|
||||
prelude::*, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
|
||||
QueryFilter as _, Set, SqlxSqliteConnector, Statement, TransactionTrait as _,
|
||||
ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait, QueryFilter as _, Set,
|
||||
SqlxSqliteConnector, Statement, TransactionTrait as _, prelude::*, sea_query::OnConflict,
|
||||
};
|
||||
use sea_orm_migration::MigratorTrait as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{migrate::MigrateDatabase as _, Sqlite, SqlitePool};
|
||||
use sqlx::{Sqlite, SqlitePool, migrate::MigrateDatabase as _};
|
||||
|
||||
use crate::migrator;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::api::CreateNodeRequest;
|
||||
use crate::db::entity::*;
|
||||
use crate::db::Db;
|
||||
use crate::db::HealthStats;
|
||||
use crate::db::HealthStatus;
|
||||
use crate::db::entity::*;
|
||||
use sea_orm::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ use sqlx::any;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
|
||||
use crate::db::{
|
||||
Db, HealthStatus,
|
||||
entity::shared_nodes,
|
||||
operations::{HealthOperations, NodeOperations},
|
||||
Db, HealthStatus,
|
||||
};
|
||||
|
||||
pub struct HealthCheckOneNode {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use tokio::time::{interval, Interval};
|
||||
use tokio::time::{Interval, interval};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{
|
||||
db::{entity::shared_nodes, operations::NodeOperations, Db},
|
||||
db::{Db, entity::shared_nodes, operations::NodeOperations},
|
||||
health_checker::HealthChecker,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ mod migrator;
|
||||
use api::routes::create_routes;
|
||||
use clap::Parser;
|
||||
use config::AppConfig;
|
||||
use db::{operations::NodeOperations, Db};
|
||||
use db::{Db, operations::NodeOperations};
|
||||
use easytier::common::log;
|
||||
use health_checker::HealthChecker;
|
||||
use health_checker_manager::HealthCheckerManager;
|
||||
@@ -49,7 +49,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
// 如果提供了管理员密码,设置环境变量
|
||||
if let Some(password) = args.admin_password {
|
||||
env::set_var("ADMIN_PASSWORD", password);
|
||||
unsafe {
|
||||
env::set_var("ADMIN_PASSWORD", password);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "easytier-gui"
|
||||
version = "2.6.0"
|
||||
description = "EasyTier GUI"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -11,15 +11,6 @@ edition = "2021"
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||
|
||||
# enable thunk-rs when compiling for x86_64 or i686 windows
|
||||
[target.x86_64-pc-windows-msvc.build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = ["win7"] }
|
||||
|
||||
[target.i686-pc-windows-msvc.build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = ["win7"] }
|
||||
|
||||
[dependencies]
|
||||
# wry 0.47 may crash on android, see https://github.com/EasyTier/EasyTier/issues/527
|
||||
@@ -66,6 +57,14 @@ libc = "0.2"
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
security-framework-sys = "2.9.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = [
|
||||
"win7",
|
||||
] }
|
||||
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
fn main() {
|
||||
// enable thunk-rs when target os is windows and arch is x86_64 or i686
|
||||
#[cfg(target_os = "windows")]
|
||||
if !std::env::var("TARGET")
|
||||
.unwrap_or_default()
|
||||
.contains("aarch64")
|
||||
{
|
||||
thunk::thunk();
|
||||
}
|
||||
|
||||
tauri_build::build();
|
||||
}
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
// enable thunk-rs when target os is windows and arch is x86_64 or i686
|
||||
if target_os == "windows" && (target_arch == "x86" || target_arch == "x86_64") {
|
||||
thunk::thunk();
|
||||
}
|
||||
|
||||
tauri_build::build();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use super::Command;
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::process::{Command as StdCommand, Output};
|
||||
|
||||
@@ -30,10 +30,10 @@ use std::os::unix::process::ExitStatusExt;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
use libc::{fileno, wait, EINTR, SHUT_WR};
|
||||
use libc::{EINTR, SHUT_WR, fileno, wait};
|
||||
use security_framework_sys::authorization::{
|
||||
errAuthorizationSuccess, kAuthorizationFlagDefaults, kAuthorizationFlagDestroyRights,
|
||||
AuthorizationCreate, AuthorizationExecuteWithPrivileges, AuthorizationFree, AuthorizationRef,
|
||||
errAuthorizationSuccess, kAuthorizationFlagDefaults, kAuthorizationFlagDestroyRights,
|
||||
};
|
||||
|
||||
const ENV_PATH: &str = "PATH";
|
||||
|
||||
@@ -11,11 +11,11 @@ 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 winapi::um::winnt::{HANDLE, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation};
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::Shell::ShellExecuteW;
|
||||
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
|
||||
use windows::core::{HSTRING, PCWSTR, w};
|
||||
|
||||
/// The implementation of state check and elevated executing varies on each platform
|
||||
impl Command {
|
||||
|
||||
@@ -21,10 +21,10 @@ use easytier::{
|
||||
instance_manager::NetworkInstanceManager,
|
||||
launcher::NetworkConfig,
|
||||
rpc_service::ApiRpcServer,
|
||||
tunnel::TunnelListener,
|
||||
tunnel::ring::RingTunnelListener,
|
||||
tunnel::tcp::TcpTunnelListener,
|
||||
tunnel::TunnelListener,
|
||||
utils::{self},
|
||||
utils::panic::setup_panic_handler,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -559,10 +559,10 @@ fn toggle_window_visibility(app: &tauri::AppHandle) {
|
||||
}
|
||||
|
||||
fn get_exe_path() -> String {
|
||||
if let Ok(appimage_path) = std::env::var("APPIMAGE") {
|
||||
if !appimage_path.is_empty() {
|
||||
return appimage_path;
|
||||
}
|
||||
if let Ok(appimage_path) = std::env::var("APPIMAGE")
|
||||
&& !appimage_path.is_empty()
|
||||
{
|
||||
return appimage_path;
|
||||
}
|
||||
std::env::current_exe()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
@@ -596,8 +596,8 @@ mod manager {
|
||||
use easytier::proto::rpc_types::controller::BaseController;
|
||||
use easytier::rpc_service::logger::LoggerRpcService;
|
||||
use easytier::rpc_service::remote_client::PersistentConfig;
|
||||
use easytier::tunnel::ring::RingTunnelConnector;
|
||||
use easytier::tunnel::TunnelConnector;
|
||||
use easytier::tunnel::ring::RingTunnelConnector;
|
||||
use easytier::web_client::WebClientHooks;
|
||||
|
||||
pub(super) struct GuiHooks {
|
||||
@@ -979,34 +979,34 @@ mod manager {
|
||||
.get_rpc_client(app.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("RPC client not found"))?;
|
||||
for id in enabled_networks {
|
||||
if let Ok(uuid) = id.parse() {
|
||||
if !self.storage.enabled_networks.contains(&uuid) {
|
||||
let config = self
|
||||
.storage
|
||||
.network_configs
|
||||
.get(&uuid)
|
||||
.map(|i| i.value().1.clone());
|
||||
let Some(config) = config else {
|
||||
continue;
|
||||
};
|
||||
let toml_config = config.gen_config()?;
|
||||
self.pre_run_network_instance_hook(&app, &toml_config)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
client
|
||||
.run_network_instance(
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config: Some(config),
|
||||
overwrite: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.post_run_network_instance_hook(&app, &uuid)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
}
|
||||
if let Ok(uuid) = id.parse()
|
||||
&& !self.storage.enabled_networks.contains(&uuid)
|
||||
{
|
||||
let config = self
|
||||
.storage
|
||||
.network_configs
|
||||
.get(&uuid)
|
||||
.map(|i| i.value().1.clone());
|
||||
let Some(config) = config else {
|
||||
continue;
|
||||
};
|
||||
let toml_config = config.gen_config()?;
|
||||
self.pre_run_network_instance_hook(&app, &toml_config)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
client
|
||||
.run_network_instance(
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config: Some(config),
|
||||
overwrite: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.post_run_network_instance_hook(&app, &uuid)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -1120,7 +1120,7 @@ pub fn run_gui() -> std::process::ExitCode {
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
utils::setup_panic_handler();
|
||||
setup_panic_handler();
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
name = "easytier-rpc-build"
|
||||
description = "Protobuf RPC Service Generator for EasyTier"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
categories = ["network-programming", "command-line-utilities"]
|
||||
rust-version = "1.93.0"
|
||||
license-file = "LICENSE"
|
||||
readme = "README.md"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "easytier-web"
|
||||
version = "2.6.0"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||
|
||||
[dependencies]
|
||||
@@ -69,13 +69,11 @@ subtle = "2.6"
|
||||
|
||||
mimalloc = { version = "*" }
|
||||
|
||||
[build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = [
|
||||
"win7",
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
embed = ["dep:axum-embed"]
|
||||
|
||||
# enable thunk-rs when compiling for x86_64 or i686 windows
|
||||
[target.x86_64-pc-windows-msvc.build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = ["win7"] }
|
||||
|
||||
[target.i686-pc-windows-msvc.build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = ["win7"] }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
// enable thunk-rs when target os is windows and arch is x86_64 or i686
|
||||
#[cfg(target_os = "windows")]
|
||||
if !std::env::var("TARGET")
|
||||
.unwrap_or_default()
|
||||
.contains("aarch64")
|
||||
{
|
||||
if target_os == "windows" && (target_arch == "x86" || target_arch == "x86_64") {
|
||||
thunk::thunk();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { AutoComplete, Button, Checkbox, Dialog, Divider, InputNumber, InputText, Panel, Password, SelectButton, ToggleButton } from 'primevue'
|
||||
import InputGroup from 'primevue/inputgroup'
|
||||
import InputGroupAddon from 'primevue/inputgroupaddon'
|
||||
import { Checkbox, InputText, InputNumber, AutoComplete, Panel, Divider, ToggleButton, Button, Password, Dialog } from 'primevue'
|
||||
import {
|
||||
addRow,
|
||||
DEFAULT_NETWORK_CONFIG,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../types/network'
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import AclManager from './acl/AclManager.vue'
|
||||
import UrlListInput from './UrlListInput.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -488,6 +489,18 @@ watch(() => curNetwork.value, syncNormalizedNetwork, { immediate: true, deep: fa
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Panel :header="t('acl.title')" toggleable collapsed>
|
||||
<div v-if="curNetwork.acl" class="flex flex-col gap-y-2">
|
||||
<AclManager v-model="curNetwork.acl" />
|
||||
</div>
|
||||
<div v-else class="flex justify-center p-4">
|
||||
<Button :label="t('acl.enabled')"
|
||||
@click="curNetwork.acl = { acl_v1: { chains: [], group: { declares: [], members: [] } } }" />
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<div class="flex pt-6 justify-center">
|
||||
<Button :label="t('run_network')" icon="pi pi-arrow-right" icon-pos="right" :disabled="configInvalid"
|
||||
@click="$emit('runNetwork', curNetwork)" />
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
<script setup lang="ts">
|
||||
import { Button, Column, DataTable, Divider, InputText, Select, SelectButton, ToggleButton } from 'primevue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { AclAction, AclChain, AclChainType, AclProtocol, AclRule } from '../../types/network'
|
||||
import AclRuleDialog from './AclRuleDialog.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
groupNames?: string[]
|
||||
}>()
|
||||
|
||||
const chain = defineModel<AclChain>({ required: true })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
watch(() => chain.value.rules, (newRules) => {
|
||||
if (!newRules) return
|
||||
const isSorted = newRules.every((rule, i) => i === 0 || (rule.priority || 0) <= (newRules[i - 1].priority || 0))
|
||||
if (!isSorted) {
|
||||
chain.value.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0))
|
||||
}
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const actionOptions = [
|
||||
{ label: () => t('acl.allow'), value: AclAction.Allow },
|
||||
{ label: () => t('acl.drop'), value: AclAction.Drop },
|
||||
]
|
||||
|
||||
const chainTypeOptions = [
|
||||
{ label: () => t('acl.inbound'), value: AclChainType.Inbound },
|
||||
{ label: () => t('acl.outbound'), value: AclChainType.Outbound },
|
||||
{ label: () => t('acl.forward'), value: AclChainType.Forward },
|
||||
]
|
||||
|
||||
const editingRule = ref<AclRule | null>(null)
|
||||
const editingRuleIndex = ref(-1)
|
||||
const showRuleDialog = ref(false)
|
||||
|
||||
function getProtocolLabel(proto: AclProtocol) {
|
||||
switch (proto) {
|
||||
case AclProtocol.Any: return t('acl.any')
|
||||
case AclProtocol.TCP: return 'TCP'
|
||||
case AclProtocol.UDP: return 'UDP'
|
||||
case AclProtocol.ICMP: return 'ICMP'
|
||||
case AclProtocol.ICMPv6: return 'ICMPv6'
|
||||
default: return t('event.Unknown')
|
||||
}
|
||||
}
|
||||
|
||||
function getActionLabel(action: AclAction) {
|
||||
switch (action) {
|
||||
case AclAction.Allow: return t('acl.allow')
|
||||
case AclAction.Drop: return t('acl.drop')
|
||||
default: return t('event.Unknown')
|
||||
}
|
||||
}
|
||||
|
||||
function addRule() {
|
||||
editingRuleIndex.value = -1
|
||||
editingRule.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
priority: chain.value.rules.length,
|
||||
enabled: true,
|
||||
protocol: AclProtocol.Any,
|
||||
ports: [],
|
||||
source_ips: [],
|
||||
destination_ips: [],
|
||||
source_ports: [],
|
||||
action: AclAction.Allow,
|
||||
rate_limit: 0,
|
||||
burst_limit: 0,
|
||||
stateful: false,
|
||||
source_groups: [],
|
||||
destination_groups: [],
|
||||
}
|
||||
showRuleDialog.value = true
|
||||
}
|
||||
|
||||
function editRule(index: number) {
|
||||
editingRuleIndex.value = index
|
||||
editingRule.value = JSON.parse(JSON.stringify(chain.value.rules[index]))
|
||||
showRuleDialog.value = true
|
||||
}
|
||||
|
||||
function deleteRule(index: number) {
|
||||
chain.value.rules.splice(index, 1)
|
||||
}
|
||||
|
||||
function saveRule(rule: AclRule) {
|
||||
if (editingRuleIndex.value === -1) {
|
||||
chain.value.rules.push(rule)
|
||||
} else {
|
||||
chain.value.rules[editingRuleIndex.value] = rule
|
||||
}
|
||||
chain.value.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0))
|
||||
}
|
||||
|
||||
function onRowReorder(event: any) {
|
||||
chain.value.rules = event.value
|
||||
// Update priorities based on new order (higher priority at top)
|
||||
chain.value.rules.forEach((rule, index) => {
|
||||
rule.priority = chain.value.rules.length - index - 1
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<!-- Chain Metadata Section -->
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-gray-50 rounded-lg border border-gray-200 dark:bg-gray-900 dark:border-gray-700">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold text-sm">{{ t('acl.chain.name') }}</label>
|
||||
<InputText v-model="chain.name" size="small" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold text-sm">{{ t('acl.rule.description') }}</label>
|
||||
<InputText v-model="chain.description" size="small" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6 col-span-full border-t pt-2 mt-2 dark:border-gray-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="font-bold text-sm">{{ t('acl.rule.enabled') }}</label>
|
||||
<ToggleButton v-model="chain.enabled" on-icon="pi pi-check" off-icon="pi pi-times"
|
||||
:on-label="t('web.common.enable')" :off-label="t('web.common.disable')" class="w-24" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="font-bold text-sm">{{ t('acl.chain.type') }}</label>
|
||||
<Select v-model="chain.chain_type" :options="chainTypeOptions" :option-label="opt => opt.label()"
|
||||
option-value="value" size="small" class="w-40" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-auto">
|
||||
<label class="font-bold text-sm">{{ t('acl.default_action') }}</label>
|
||||
<SelectButton v-model="chain.default_action" :options="actionOptions" :option-label="opt => opt.label()"
|
||||
option-value="value" :allow-empty="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center gap-4 justify-between">
|
||||
<h4 class="text-md font-bold">{{ t('acl.rules') }}</h4>
|
||||
<Button icon="pi pi-plus" :label="t('acl.add_rule')" severity="success" size="small" @click="addRule" />
|
||||
</div>
|
||||
|
||||
<DataTable :value="chain.rules" @row-reorder="onRowReorder" responsiveLayout="scroll">
|
||||
<Column rowReorder headerStyle="width: 3rem" />
|
||||
<Column field="enabled" :header="t('acl.rule.enabled')">
|
||||
<template #body="{ data }">
|
||||
<i class="pi" :class="data.enabled ? 'pi-check-circle text-green-500' : 'pi-times-circle text-red-500'"></i>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="name" :header="t('acl.rule.name')" />
|
||||
<Column :header="t('acl.match')">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col gap-2 py-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="px-2 py-0.5 bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 rounded-md text-[10px] font-bold uppercase tracking-wider">
|
||||
{{ getProtocolLabel(data.protocol) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3">
|
||||
<div class="flex items-center gap-1.5 min-w-0">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase w-7">Src</span>
|
||||
<div class="flex flex-wrap gap-1 items-center overflow-hidden">
|
||||
<span v-for="ip in data.source_ips" :key="ip"
|
||||
class="font-mono text-xs bg-surface-100 dark:bg-surface-800 px-1.5 py-0.5 rounded">{{ ip }}</span>
|
||||
<span v-for="grp in data.source_groups" :key="grp"
|
||||
class="text-xs font-bold text-purple-600 dark:text-purple-400">@{{ grp }}</span>
|
||||
<span v-if="data.source_ports.length" class="text-xs text-blue-600 dark:text-blue-400 font-mono">:{{
|
||||
data.source_ports.join(',') }}</span>
|
||||
<span v-if="!data.source_ips.length && !data.source_groups.length" class="text-gray-400">*</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<i class="pi pi-arrow-right hidden sm:block text-gray-300 text-xs"></i>
|
||||
<Divider layout="horizontal" class="sm:hidden my-1" />
|
||||
|
||||
<div class="flex items-center gap-1.5 min-w-0">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase w-7">Dst</span>
|
||||
<div class="flex flex-wrap gap-1 items-center overflow-hidden">
|
||||
<span v-for="ip in data.destination_ips" :key="ip"
|
||||
class="font-mono text-xs bg-surface-100 dark:bg-surface-800 px-1.5 py-0.5 rounded">{{ ip }}</span>
|
||||
<span v-for="grp in data.destination_groups" :key="grp"
|
||||
class="text-xs font-bold text-purple-600 dark:text-purple-400">@{{ grp }}</span>
|
||||
<span v-if="data.ports.length" class="text-xs text-blue-600 dark:text-blue-400 font-mono">:{{
|
||||
data.ports.join(',') }}</span>
|
||||
<span v-if="!data.destination_ips.length && !data.destination_groups.length"
|
||||
class="text-gray-400">*</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="action" :header="t('acl.rule.action')">
|
||||
<template #body="{ data }">
|
||||
<span :class="data.action === AclAction.Allow ? 'text-green-600' : 'text-red-600 font-bold'">
|
||||
{{ getActionLabel(data.action) }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column :header="t('web.common.edit')">
|
||||
<template #body="{ index }">
|
||||
<div class="flex gap-2">
|
||||
<Button icon="pi pi-pencil" text rounded @click="editRule(index)" />
|
||||
<Button icon="pi pi-trash" severity="danger" text rounded @click="deleteRule(index)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<AclRuleDialog v-if="showRuleDialog && editingRule" v-model:visible="showRuleDialog" v-model:rule="editingRule"
|
||||
:group-names="props.groupNames" @save="saveRule" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { Button, Column, DataTable, Dialog, InputText, MultiSelect, Password } from 'primevue';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { GroupIdentity, GroupInfo } from '../../types/network';
|
||||
|
||||
const props = defineProps<{
|
||||
groupNames?: string[]
|
||||
}>()
|
||||
|
||||
const group = defineModel<GroupInfo>({ required: true })
|
||||
const emit = defineEmits(['rename-group'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const editingGroup = ref<GroupIdentity | null>(null)
|
||||
const editingGroupIndex = ref(-1)
|
||||
const showGroupDialog = ref(false)
|
||||
const oldGroupName = ref('')
|
||||
|
||||
function addGroup() {
|
||||
editingGroupIndex.value = -1
|
||||
editingGroup.value = {
|
||||
group_name: '',
|
||||
group_secret: '',
|
||||
}
|
||||
oldGroupName.value = ''
|
||||
showGroupDialog.value = true
|
||||
}
|
||||
|
||||
function editGroup(index: number) {
|
||||
editingGroupIndex.value = index
|
||||
editingGroup.value = JSON.parse(JSON.stringify(group.value.declares[index]))
|
||||
oldGroupName.value = editingGroup.value?.group_name || ''
|
||||
showGroupDialog.value = true
|
||||
}
|
||||
|
||||
function deleteGroup(index: number) {
|
||||
group.value.declares.splice(index, 1)
|
||||
}
|
||||
|
||||
function saveGroup() {
|
||||
if (!editingGroup.value) return
|
||||
const newName = editingGroup.value.group_name
|
||||
|
||||
if (editingGroupIndex.value === -1) {
|
||||
group.value.declares.push(editingGroup.value)
|
||||
} else {
|
||||
if (oldGroupName.value && oldGroupName.value !== newName) {
|
||||
// Sync in members
|
||||
group.value.members = group.value.members.map(m => m === oldGroupName.value ? newName : m)
|
||||
// Notify parent to sync in rules
|
||||
emit('rename-group', { oldName: oldGroupName.value, newName })
|
||||
}
|
||||
group.value.declares[editingGroupIndex.value] = editingGroup.value
|
||||
}
|
||||
showGroupDialog.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex flex-col">
|
||||
<label class="font-bold text-lg">{{ t('acl.group.declares') }}</label>
|
||||
<small class="text-gray-500">{{ t('acl.group.help') }}</small>
|
||||
</div>
|
||||
<Button icon="pi pi-plus" :label="t('web.common.add')" severity="success" @click="addGroup" />
|
||||
</div>
|
||||
|
||||
<DataTable :value="group.declares" responsiveLayout="scroll">
|
||||
<Column field="group_name" :header="t('acl.group.name')" />
|
||||
<Column field="group_secret" :header="t('acl.group.secret')">
|
||||
<template #body="{ data }">
|
||||
<Password v-model="data.group_secret" :feedback="false" toggleMask readonly plain class="w-full" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column :header="t('web.common.edit')" headerStyle="width: 8rem">
|
||||
<template #body="{ index }">
|
||||
<div class="flex gap-2">
|
||||
<Button icon="pi pi-pencil" text rounded @click="editGroup(index)" />
|
||||
<Button icon="pi pi-trash" severity="danger" text rounded @click="deleteGroup(index)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold text-lg">{{ t('acl.group.members') }}</label>
|
||||
<MultiSelect v-model="group.members" :options="props.groupNames" multiple fluid filter
|
||||
:placeholder="t('acl.group.members')" />
|
||||
</div>
|
||||
|
||||
<!-- Group Identity Dialog -->
|
||||
<Dialog v-model:visible="showGroupDialog" modal :header="t('acl.groups')" :style="{ width: '400px' }">
|
||||
<div v-if="editingGroup" class="flex flex-col gap-4 pt-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.group.name') }}</label>
|
||||
<InputText v-model="editingGroup.group_name" fluid />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.group.secret') }}</label>
|
||||
<Password v-model="editingGroup.group_secret" :feedback="false" toggleMask fluid />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button :label="t('web.common.cancel')" icon="pi pi-times" @click="showGroupDialog = false" text />
|
||||
<Button :label="t('web.common.save')" icon="pi pi-save" @click="saveGroup" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { Button, Menu, Tab, TabList, TabPanel, TabPanels, Tabs } from 'primevue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Acl, AclAction, AclChainType } from '../../types/network'
|
||||
import AclChainEditor from './AclChainEditor.vue'
|
||||
import AclGroupEditor from './AclGroupEditor.vue'
|
||||
|
||||
const acl = defineModel<Acl>({ required: true })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const activeTab = ref(0)
|
||||
const menu = ref()
|
||||
|
||||
const addMenuModel = ref([
|
||||
{ label: () => t('acl.inbound'), command: () => addChain(AclChainType.Inbound) },
|
||||
{ label: () => t('acl.outbound'), command: () => addChain(AclChainType.Outbound) },
|
||||
{ label: () => t('acl.forward'), command: () => addChain(AclChainType.Forward) },
|
||||
])
|
||||
|
||||
function addChain(type: AclChainType) {
|
||||
if (!acl.value.acl_v1) {
|
||||
acl.value.acl_v1 = { chains: [], group: { declares: [], members: [] } }
|
||||
}
|
||||
|
||||
let defaultName = ''
|
||||
switch (type) {
|
||||
case AclChainType.Inbound: defaultName = 'Inbound'; break;
|
||||
case AclChainType.Outbound: defaultName = 'Outbound'; break;
|
||||
case AclChainType.Forward: defaultName = 'Forward'; break;
|
||||
}
|
||||
|
||||
acl.value.acl_v1.chains.push({
|
||||
name: defaultName,
|
||||
chain_type: type,
|
||||
description: '',
|
||||
enabled: true,
|
||||
rules: [],
|
||||
default_action: AclAction.Allow
|
||||
})
|
||||
|
||||
activeTab.value = acl.value.acl_v1.chains.length - 1
|
||||
}
|
||||
|
||||
function removeChain(index: number) {
|
||||
if (confirm(t('acl.delete_chain_confirm'))) {
|
||||
acl.value.acl_v1?.chains.splice(index, 1)
|
||||
if (activeTab.value >= (acl.value.acl_v1?.chains.length || 0)) {
|
||||
activeTab.value = Math.max(0, (acl.value.acl_v1?.chains.length || 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRenameGroup({ oldName, newName }: { oldName: string, newName: string }) {
|
||||
if (!acl.value.acl_v1) return
|
||||
acl.value.acl_v1.chains.forEach(chain => {
|
||||
chain.rules.forEach(rule => {
|
||||
rule.source_groups = rule.source_groups.map(g => g === oldName ? newName : g)
|
||||
rule.destination_groups = rule.destination_groups.map(g => g === oldName ? newName : g)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const groupNames = computed(() => {
|
||||
return acl.value.acl_v1?.group?.declares.map(g => g.group_name) || []
|
||||
})
|
||||
|
||||
const tabs = computed(() => {
|
||||
const chains = acl.value.acl_v1?.chains || []
|
||||
const result: { type: string, label: string, index: number }[] = []
|
||||
|
||||
if (chains.length === 0) {
|
||||
result.push({ type: 'empty', label: t('acl.chains'), index: 0 })
|
||||
}
|
||||
else {
|
||||
chains.forEach((c, index) => {
|
||||
result.push({
|
||||
type: 'chain',
|
||||
label: c.name || `Chain ${index}`,
|
||||
index
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
result.push({ type: 'groups', label: t('acl.groups'), index: result.length })
|
||||
return result
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<Tabs v-model:value="activeTab">
|
||||
<div class="flex items-center border-b border-surface-200 dark:border-surface-700">
|
||||
<TabList class="flex-grow min-w-0 overflow-x-auto" style="border-bottom: none;">
|
||||
<Tab v-for="tab in tabs" :key="tab.type + tab.index" :value="tab.index">
|
||||
<div class="flex items-center gap-2 whitespace-nowrap">
|
||||
{{ tab.label }}
|
||||
<Button v-if="tab.type === 'chain'" icon="pi pi-times" severity="danger" text rounded size="small"
|
||||
class="w-6 h-6 p-0" @click.stop="removeChain(tab.index)" />
|
||||
</div>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<div
|
||||
class="flex-shrink-0 flex items-center px-2 bg-white dark:bg-gray-900 border-l border-surface-100 dark:border-surface-800">
|
||||
<Button icon="pi pi-plus" text rounded size="small" class="w-8 h-8 p-0"
|
||||
@click="(event) => menu.toggle(event)" />
|
||||
<Menu ref="menu" :model="addMenuModel" :popup="true" />
|
||||
</div>
|
||||
</div>
|
||||
<TabPanels>
|
||||
<TabPanel v-for="tab in tabs" :key="'panel' + tab.type + tab.index" :value="tab.index">
|
||||
<!-- Empty State within TabPanel -->
|
||||
<div v-if="tab.type === 'empty'"
|
||||
class="py-8 flex flex-col items-center justify-center border-2 border-dashed border-surface-200 rounded-lg bg-surface-50 dark:bg-surface-900 dark:border-surface-700">
|
||||
<i class="pi pi-shield text-5xl mb-4 text-primary" />
|
||||
<div class="text-xl font-bold mb-2">{{ t('acl.chains') }}</div>
|
||||
<p class="text-surface-500 mb-8 text-center max-w-sm px-4">{{ t('acl.help') }}</p>
|
||||
<div class="flex flex-wrap gap-3 justify-center">
|
||||
<Button :label="t('acl.inbound')" icon="pi pi-arrow-down-left" @click="addChain(AclChainType.Inbound)" />
|
||||
<Button :label="t('acl.outbound')" icon="pi pi-arrow-up-right" @click="addChain(AclChainType.Outbound)" />
|
||||
<Button :label="t('acl.forward')" icon="pi pi-directions" @click="addChain(AclChainType.Forward)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rule Chains -->
|
||||
<div v-if="tab.type === 'chain' && acl.acl_v1 && acl.acl_v1.chains[tab.index]" class="py-4">
|
||||
<AclChainEditor v-model="acl.acl_v1.chains[tab.index]" :group-names="groupNames" />
|
||||
</div>
|
||||
|
||||
<!-- Group Management -->
|
||||
<div v-if="tab.type === 'groups'" class="py-4">
|
||||
<template v-if="acl.acl_v1">
|
||||
<AclGroupEditor v-if="acl.acl_v1.group" v-model="acl.acl_v1.group" :group-names="groupNames"
|
||||
@rename-group="handleRenameGroup" />
|
||||
<div v-else class="flex justify-center p-4">
|
||||
<Button :label="t('web.common.add') + ' ' + t('acl.groups')"
|
||||
@click="acl.acl_v1.group = { declares: [], members: [] }" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="flex justify-center p-4">
|
||||
<Button :label="t('acl.enabled')"
|
||||
@click="acl.acl_v1 = { chains: [], group: { declares: [], members: [] } }" />
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { AutoComplete, Button, Checkbox, Dialog, InputNumber, InputText, MultiSelect, Panel, SelectButton, ToggleButton } from 'primevue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AclAction, AclProtocol, AclRule } from '../../types/network';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
groupNames?: string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:visible', 'save'])
|
||||
|
||||
const rule = defineModel<AclRule>('rule', { required: true })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const protocolOptions = [
|
||||
{ label: () => t('acl.any'), value: AclProtocol.Any },
|
||||
{ label: 'TCP', value: AclProtocol.TCP },
|
||||
{ label: 'UDP', value: AclProtocol.UDP },
|
||||
{ label: 'ICMP', value: AclProtocol.ICMP },
|
||||
{ label: 'ICMPv6', value: AclProtocol.ICMPv6 },
|
||||
]
|
||||
|
||||
const actionOptions = [
|
||||
{ label: () => t('acl.allow'), value: AclAction.Allow },
|
||||
{ label: () => t('acl.drop'), value: AclAction.Drop },
|
||||
]
|
||||
|
||||
const showPorts = computed(() => {
|
||||
return rule.value.protocol === AclProtocol.TCP || rule.value.protocol === AclProtocol.UDP || rule.value.protocol === AclProtocol.Any
|
||||
})
|
||||
|
||||
function close() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
function save() {
|
||||
emit('save', rule.value)
|
||||
close()
|
||||
}
|
||||
|
||||
// Suggestions for IP/Port AutoComplete
|
||||
const genericSuggestions = ref<string[]>([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :visible="visible" @update:visible="emit('update:visible', $event)" modal :header="t('acl.edit_rule')"
|
||||
:style="{ width: '90vw', maxWidth: '600px' }">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.name') }}</label>
|
||||
<InputText v-model="rule.name" fluid />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.rule.enabled') }}</label>
|
||||
<ToggleButton v-model="rule.enabled" on-icon="pi pi-check" off-icon="pi pi-times"
|
||||
:on-label="t('web.common.enable')" :off-label="t('web.common.disable')" class="w-24" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.rule.description') }}</label>
|
||||
<InputText v-model="rule.description" fluid />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-4 flex-wrap">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.action') }}</label>
|
||||
<SelectButton v-model="rule.action" :options="actionOptions" :option-label="opt => opt.label()"
|
||||
option-value="value" :allow-empty="false" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.protocol') }}</label>
|
||||
<SelectButton v-model="rule.protocol" :options="protocolOptions"
|
||||
:option-label="opt => typeof opt.label === 'function' ? opt.label() : opt.label" option-value="value"
|
||||
:allow-empty="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Panel :header="t('acl.rules')" toggleable>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.rule.src_ips') }}</label>
|
||||
<AutoComplete v-model="rule.source_ips" multiple fluid :suggestions="genericSuggestions"
|
||||
@complete="genericSuggestions = [$event.query]"
|
||||
:placeholder="t('chips_placeholder', ['10.126.126.0/24'])" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.rule.dst_ips') }}</label>
|
||||
<AutoComplete v-model="rule.destination_ips" multiple fluid :suggestions="genericSuggestions"
|
||||
@complete="genericSuggestions = [$event.query]"
|
||||
:placeholder="t('chips_placeholder', ['10.126.126.2/32'])" />
|
||||
</div>
|
||||
|
||||
<div v-if="showPorts" class="flex flex-row gap-4 flex-wrap">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.src_ports') }}</label>
|
||||
<AutoComplete v-model="rule.source_ports" multiple fluid :suggestions="genericSuggestions"
|
||||
@complete="genericSuggestions = [$event.query]" placeholder="e.g. 80, 1000-2000" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.dst_ports') }}</label>
|
||||
<AutoComplete v-model="rule.ports" multiple fluid :suggestions="genericSuggestions"
|
||||
@complete="genericSuggestions = [$event.query]" placeholder="e.g. 80, 1000-2000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<Panel :header="t('advanced_settings')" toggleable collapsed>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox v-model="rule.stateful" :binary="true" inputId="rule-stateful" />
|
||||
<label for="rule-stateful" class="font-bold">{{ t('acl.rule.stateful') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-4 flex-wrap">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.rate_limit') }}</label>
|
||||
<InputNumber v-model="rule.rate_limit" :min="0" placeholder="0 = no limit" fluid />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<label class="font-bold">{{ t('acl.rule.burst_limit') }}</label>
|
||||
<InputNumber v-model="rule.burst_limit" :min="0" placeholder="0 = no limit" fluid />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.rule.src_groups') }}</label>
|
||||
<MultiSelect v-model="rule.source_groups" :options="props.groupNames" multiple fluid filter
|
||||
:placeholder="t('acl.rule.src_groups')" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="font-bold">{{ t('acl.rule.dst_groups') }}</label>
|
||||
<MultiSelect v-model="rule.destination_groups" :options="props.groupNames" multiple fluid filter
|
||||
:placeholder="t('acl.rule.dst_groups')" />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button :label="t('web.common.cancel')" icon="pi pi-times" @click="close" text />
|
||||
<Button :label="t('web.common.save')" icon="pi pi-save" @click="save" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -355,6 +355,7 @@ web:
|
||||
delete: 删除
|
||||
edit: 编辑
|
||||
refresh: 刷新
|
||||
add: 添加
|
||||
loading: 加载中...
|
||||
error: 错误
|
||||
success: 成功
|
||||
@@ -422,3 +423,46 @@ config-server:
|
||||
client:
|
||||
not_running: 无法连接至远程客户端
|
||||
retry: 重试
|
||||
|
||||
acl:
|
||||
title: 访问控制
|
||||
help: 访问控制列表,用于限制节点间的通信。
|
||||
enabled: 启用 ACL
|
||||
default_action: 默认动作
|
||||
chains: 规则链
|
||||
inbound: 入站
|
||||
outbound: 出站
|
||||
forward: 转发
|
||||
rules: 规则
|
||||
add_rule: 添加规则
|
||||
edit_rule: 编辑规则
|
||||
rule:
|
||||
name: 规则名称
|
||||
description: 描述
|
||||
enabled: 启用
|
||||
protocol: 协议
|
||||
action: 动作
|
||||
src_ips: 来源 IP
|
||||
dst_ips: 目的 IP
|
||||
src_ports: 来源端口
|
||||
dst_ports: 目的端口
|
||||
rate_limit: 速率限制 (pps)
|
||||
burst_limit: 爆发限制
|
||||
stateful: 状态追踪
|
||||
src_groups: 来源组
|
||||
dst_groups: 目的组
|
||||
groups: 组管理
|
||||
group:
|
||||
declares: 声明组
|
||||
members: 加入组
|
||||
name: 组名
|
||||
secret: 密钥
|
||||
help: 在此处定义网络中的组身份,以便在规则中使用。
|
||||
any: 任意
|
||||
allow: 允许
|
||||
drop: 丢弃
|
||||
delete_chain_confirm: 确定要删除此规则链及其所有规则吗?
|
||||
chain:
|
||||
name: 名称
|
||||
type: 类型
|
||||
match: 匹配
|
||||
|
||||
@@ -355,6 +355,7 @@ web:
|
||||
delete: Delete
|
||||
edit: Edit
|
||||
refresh: Refresh
|
||||
add: Add
|
||||
loading: Loading...
|
||||
error: Error
|
||||
success: Success
|
||||
@@ -422,3 +423,46 @@ config-server:
|
||||
client:
|
||||
not_running: Unable to connect to remote client.
|
||||
retry: Retry
|
||||
|
||||
acl:
|
||||
title: Access Control (ACL)
|
||||
help: Access control list to restrict communication between nodes.
|
||||
enabled: Enable ACL
|
||||
default_action: Default Action
|
||||
chains: Rule Chains
|
||||
inbound: Inbound
|
||||
outbound: Outbound
|
||||
forward: Forward
|
||||
rules: Rules
|
||||
add_rule: Add Rule
|
||||
edit_rule: Edit Rule
|
||||
rule:
|
||||
name: Rule Name
|
||||
description: Description
|
||||
enabled: Enabled
|
||||
protocol: Protocol
|
||||
action: Action
|
||||
src_ips: Source IPs
|
||||
dst_ips: Destination IPs
|
||||
src_ports: Source Ports
|
||||
dst_ports: Destination Ports
|
||||
rate_limit: Rate Limit (pps)
|
||||
burst_limit: Burst Limit
|
||||
stateful: Stateful
|
||||
src_groups: Source Groups
|
||||
dst_groups: Destination Groups
|
||||
groups: Groups
|
||||
group:
|
||||
declares: Declared Groups
|
||||
members: Node Memberships
|
||||
name: Group Name
|
||||
secret: Group Secret
|
||||
help: Define group identities in the network to use them in rules.
|
||||
any: Any
|
||||
allow: Allow
|
||||
drop: Drop
|
||||
delete_chain_confirm: Are you sure you want to delete this rule chain and all its rules?
|
||||
chain:
|
||||
name: Name
|
||||
type: Type
|
||||
match: Match
|
||||
|
||||
@@ -14,6 +14,74 @@ export interface SecureModeConfig {
|
||||
local_public_key?: string
|
||||
}
|
||||
|
||||
export enum AclProtocol {
|
||||
Unspecified = 0,
|
||||
TCP = 1,
|
||||
UDP = 2,
|
||||
ICMP = 3,
|
||||
ICMPv6 = 4,
|
||||
Any = 5,
|
||||
}
|
||||
|
||||
export enum AclAction {
|
||||
Noop = 0,
|
||||
Allow = 1,
|
||||
Drop = 2,
|
||||
}
|
||||
|
||||
export enum AclChainType {
|
||||
UnspecifiedChain = 0,
|
||||
Inbound = 1,
|
||||
Outbound = 2,
|
||||
Forward = 3,
|
||||
}
|
||||
|
||||
export interface AclRule {
|
||||
name: string
|
||||
description: string
|
||||
priority: number
|
||||
enabled: boolean
|
||||
protocol: AclProtocol
|
||||
ports: string[]
|
||||
source_ips: string[]
|
||||
destination_ips: string[]
|
||||
source_ports: string[]
|
||||
action: AclAction
|
||||
rate_limit: number
|
||||
burst_limit: number
|
||||
stateful: boolean
|
||||
source_groups: string[]
|
||||
destination_groups: string[]
|
||||
}
|
||||
|
||||
export interface AclChain {
|
||||
name: string
|
||||
chain_type: AclChainType
|
||||
description: string
|
||||
enabled: boolean
|
||||
rules: AclRule[]
|
||||
default_action: AclAction
|
||||
}
|
||||
|
||||
export interface GroupIdentity {
|
||||
group_name: string
|
||||
group_secret: string
|
||||
}
|
||||
|
||||
export interface GroupInfo {
|
||||
declares: GroupIdentity[]
|
||||
members: string[]
|
||||
}
|
||||
|
||||
export interface AclV1 {
|
||||
chains: AclChain[]
|
||||
group?: GroupInfo
|
||||
}
|
||||
|
||||
export interface Acl {
|
||||
acl_v1?: AclV1
|
||||
}
|
||||
|
||||
export interface NetworkConfig {
|
||||
instance_id: string
|
||||
|
||||
@@ -85,6 +153,7 @@ export interface NetworkConfig {
|
||||
enable_private_mode?: boolean
|
||||
|
||||
port_forwards: PortForwardConfig[]
|
||||
acl?: Acl
|
||||
}
|
||||
|
||||
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||
@@ -152,6 +221,15 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||
enable_magic_dns: false,
|
||||
enable_private_mode: false,
|
||||
port_forwards: [],
|
||||
acl: {
|
||||
acl_v1: {
|
||||
group: {
|
||||
declares: [],
|
||||
members: [],
|
||||
},
|
||||
chains: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ pub mod session;
|
||||
pub mod storage;
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
@@ -19,11 +19,11 @@ use maxminddb::geoip2;
|
||||
use session::{Location, Session};
|
||||
use storage::{Storage, StorageToken};
|
||||
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
use crate::FeatureFlags;
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::db::{entity::user_running_network_configs, Db, UserIdInDb};
|
||||
use crate::db::{Db, UserIdInDb, entity::user_running_network_configs};
|
||||
|
||||
#[derive(rust_embed::Embed)]
|
||||
#[folder = "resources/"]
|
||||
@@ -340,7 +340,7 @@ mod tests {
|
||||
};
|
||||
use sqlx::Executor;
|
||||
|
||||
use crate::{client_manager::ClientManager, db::Db, FeatureFlags};
|
||||
use crate::{FeatureFlags, client_manager::ClientManager, db::Db};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_client() {
|
||||
@@ -379,19 +379,26 @@ mod tests {
|
||||
|
||||
let req = tokio::time::timeout(Duration::from_secs(12), async {
|
||||
loop {
|
||||
let session = mgr
|
||||
let sessions = mgr
|
||||
.client_sessions
|
||||
.iter()
|
||||
.next()
|
||||
.map(|item| item.value().clone());
|
||||
let Some(session) = session else {
|
||||
.map(|item| item.value().clone())
|
||||
.collect::<Vec<_>>();
|
||||
if sessions.is_empty() {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
};
|
||||
let mut waiter = session.data().read().await.heartbeat_waiter();
|
||||
if let Ok(req) = waiter.recv().await {
|
||||
}
|
||||
let mut found_req = None;
|
||||
for session in sessions {
|
||||
if let Some(req) = session.data().read().await.req() {
|
||||
found_req = Some(req);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(req) = found_req {
|
||||
break req;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -20,11 +20,11 @@ use easytier::{
|
||||
rpc_service::remote_client::{ListNetworkProps, Storage as _},
|
||||
tunnel::Tunnel,
|
||||
};
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
use tokio::sync::{RwLock, broadcast};
|
||||
|
||||
use super::storage::{Storage, StorageToken, WeakRefStorage};
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
use crate::FeatureFlags;
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Location {
|
||||
@@ -87,30 +87,30 @@ impl SessionData {
|
||||
|
||||
impl Drop for SessionData {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(storage) = Storage::try_from(self.storage.clone()) {
|
||||
if let Some(token) = self.storage_token.as_ref() {
|
||||
storage.remove_client(token);
|
||||
if let Ok(storage) = Storage::try_from(self.storage.clone())
|
||||
&& let Some(token) = self.storage_token.as_ref()
|
||||
{
|
||||
storage.remove_client(token);
|
||||
|
||||
// Notify the webhook receiver when a node disconnects.
|
||||
if self.webhook_config.is_enabled() {
|
||||
let webhook = self.webhook_config.clone();
|
||||
let machine_id = token.machine_id.to_string();
|
||||
let user_id = Some(token.user_id);
|
||||
let token_value = token.token.clone();
|
||||
let web_instance_id = webhook.web_instance_id.clone();
|
||||
let binding_version = self.binding_version;
|
||||
tokio::spawn(async move {
|
||||
webhook
|
||||
.notify_node_disconnected(&crate::webhook::NodeDisconnectedRequest {
|
||||
machine_id,
|
||||
token: token_value,
|
||||
user_id,
|
||||
web_instance_id,
|
||||
binding_version,
|
||||
})
|
||||
.await;
|
||||
});
|
||||
}
|
||||
// Notify the webhook receiver when a node disconnects.
|
||||
if self.webhook_config.is_enabled() {
|
||||
let webhook = self.webhook_config.clone();
|
||||
let machine_id = token.machine_id.to_string();
|
||||
let user_id = Some(token.user_id);
|
||||
let token_value = token.token.clone();
|
||||
let web_instance_id = webhook.web_instance_id.clone();
|
||||
let binding_version = self.binding_version;
|
||||
tokio::spawn(async move {
|
||||
webhook
|
||||
.notify_node_disconnected(&crate::webhook::NodeDisconnectedRequest {
|
||||
machine_id,
|
||||
token: token_value,
|
||||
user_id,
|
||||
web_instance_id,
|
||||
binding_version,
|
||||
})
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,7 @@ impl SessionRpcService {
|
||||
let webhook_req = crate::webhook::ValidateTokenRequest {
|
||||
token: req.user_token.clone(),
|
||||
machine_id: machine_id.to_string(),
|
||||
public_ip: data.client_url.host_str().map(str::to_string),
|
||||
hostname: req.hostname.clone(),
|
||||
version: req.easytier_version.clone(),
|
||||
os_type: req.device_os.as_ref().map(|info| info.os_type.clone()),
|
||||
@@ -385,7 +386,7 @@ impl WebServerService for SessionRpcService {
|
||||
_: easytier::proto::web::GetFeatureRequest,
|
||||
) -> rpc_types::error::Result<easytier::proto::web::GetFeatureResponse> {
|
||||
Ok(easytier::proto::web::GetFeatureResponse {
|
||||
support_encryption: true,
|
||||
support_encryption: easytier::web_client::security::web_secure_tunnel_supported(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ use easytier::{
|
||||
};
|
||||
use entity::user_running_network_configs;
|
||||
use sea_orm::{
|
||||
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
|
||||
QueryFilter as _, Set, SqlxSqliteConnector, TransactionTrait as _,
|
||||
ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait, QueryFilter as _, Set,
|
||||
SqlxSqliteConnector, TransactionTrait as _, prelude::Expr, sea_query::OnConflict,
|
||||
};
|
||||
use sea_orm_migration::MigratorTrait as _;
|
||||
use sqlx::{migrate::MigrateDatabase as _, types::chrono, Sqlite, SqlitePool};
|
||||
use sqlx::{Sqlite, SqlitePool, migrate::MigrateDatabase as _, types::chrono};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::migrator;
|
||||
@@ -280,7 +280,7 @@ mod tests {
|
||||
use easytier::{proto::api::manage::NetworkConfig, rpc_service::remote_client::Storage};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
||||
|
||||
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
|
||||
use crate::db::{Db, ListNetworkProps, entity::user_running_network_configs};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_user_network_config_management() {
|
||||
|
||||
@@ -16,8 +16,8 @@ use easytier::{
|
||||
log,
|
||||
network::{local_ipv4, local_ipv6},
|
||||
},
|
||||
tunnel::{tcp::TcpTunnelListener, udp::UdpTunnelListener, TunnelListener},
|
||||
utils::setup_panic_handler,
|
||||
tunnel::{TunnelListener, tcp::TcpTunnelListener, udp::UdpTunnelListener},
|
||||
utils::panic::setup_panic_handler,
|
||||
};
|
||||
|
||||
use easytier::tunnel::IpScheme;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::{
|
||||
Router,
|
||||
http::StatusCode,
|
||||
routing::{get, post, put},
|
||||
Router,
|
||||
};
|
||||
use axum_login::login_required;
|
||||
use axum_messages::Message;
|
||||
@@ -14,8 +14,8 @@ use std::sync::Arc;
|
||||
use crate::FeatureFlags;
|
||||
|
||||
use super::{
|
||||
users::{AuthSession, Credentials},
|
||||
AppStateInner,
|
||||
users::{AuthSession, Credentials},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
@@ -44,7 +44,7 @@ mod put {
|
||||
use axum_login::AuthUser;
|
||||
use easytier::proto::common::Void;
|
||||
|
||||
use crate::restful::{other_error, users::ChangePassword, HttpHandleError};
|
||||
use crate::restful::{HttpHandleError, other_error, users::ChangePassword};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -71,14 +71,14 @@ mod put {
|
||||
}
|
||||
|
||||
mod post {
|
||||
use axum::{extract::Extension, Json};
|
||||
use axum::{Json, extract::Extension};
|
||||
use easytier::proto::common::Void;
|
||||
|
||||
use crate::restful::{
|
||||
captcha::extension::{axum_tower_sessions::CaptchaAxumTowerSessionStaticExt, CaptchaUtil},
|
||||
HttpHandleError,
|
||||
captcha::extension::{CaptchaUtil, axum_tower_sessions::CaptchaAxumTowerSessionStaticExt},
|
||||
other_error,
|
||||
users::RegisterNewUser,
|
||||
HttpHandleError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@@ -99,7 +99,7 @@ mod post {
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json::from(other_error(format!("{:?}", e))),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,14 +150,15 @@ mod post {
|
||||
|
||||
mod get {
|
||||
use crate::restful::{
|
||||
HttpHandleError,
|
||||
captcha::{
|
||||
builder::spec::SpecCaptcha,
|
||||
extension::{axum_tower_sessions::CaptchaAxumTowerSessionExt as _, CaptchaUtil},
|
||||
NewCaptcha as _,
|
||||
builder::spec::SpecCaptcha,
|
||||
extension::{CaptchaUtil, axum_tower_sessions::CaptchaAxumTowerSessionExt as _},
|
||||
},
|
||||
other_error, HttpHandleError,
|
||||
other_error,
|
||||
};
|
||||
use axum::{response::Response, Json};
|
||||
use axum::{Json, response::Response};
|
||||
use easytier::proto::common::Void;
|
||||
use tower_sessions::Session;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use super::super::base::randoms::Randoms;
|
||||
|
||||
use super::super::utils::color::Color;
|
||||
use super::super::utils::font;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
|
||||
use rusttype::Font;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -9,14 +9,14 @@ use super::super::{CaptchaFont, NewCaptcha};
|
||||
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use imageproc::drawing;
|
||||
use rand::{rngs::ThreadRng, Rng};
|
||||
use rand::{Rng, rngs::ThreadRng};
|
||||
use rusttype::{Font, Scale};
|
||||
use std::io::{Cursor, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod color {
|
||||
use image::Rgba;
|
||||
use rand::{rngs::ThreadRng, Rng};
|
||||
use rand::{Rng, rngs::ThreadRng};
|
||||
pub fn gen_background_color(rng: &mut ThreadRng) -> Rgba<u8> {
|
||||
let red = rng.gen_range(200..=255);
|
||||
let green = rng.gen_range(200..=255);
|
||||
@@ -133,7 +133,7 @@ impl<'a, 'b> CaptchaBuilder<'a, 'b> {
|
||||
|
||||
fn draw_line(&self, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, rng: &mut ThreadRng) {
|
||||
let line_color = color::gen_line_color(rng);
|
||||
let is_h = rng.gen();
|
||||
let is_h = rng.r#gen();
|
||||
let (start, end) = if is_h {
|
||||
let xa = rng.gen_range(0.0..(self.width as f32) / 2.0);
|
||||
let ya = rng.gen_range(0.0..(self.height as f32));
|
||||
|
||||
@@ -8,13 +8,13 @@ mod users;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::extract::Path;
|
||||
use axum::http::{header, Request, StatusCode};
|
||||
use axum::http::{Request, StatusCode, header};
|
||||
use axum::middleware::{self as axum_mw, Next};
|
||||
use axum::response::Response;
|
||||
use axum::routing::{delete, post};
|
||||
use axum::{extract::State, routing::get, Extension, Json, Router};
|
||||
use axum::{Extension, Json, Router, extract::State, routing::get};
|
||||
use axum_login::tower_sessions::{ExpiredDeletion, SessionManagerLayer};
|
||||
use axum_login::{login_required, AuthManagerLayerBuilder, AuthUser, AuthzBackend};
|
||||
use axum_login::{AuthManagerLayerBuilder, AuthUser, AuthzBackend, login_required};
|
||||
use axum_messages::MessagesManagerLayer;
|
||||
use easytier::common::config::{ConfigLoader, TomlConfigLoader};
|
||||
use easytier::common::scoped_task::ScopedTask;
|
||||
@@ -23,17 +23,17 @@ use easytier::proto::rpc_types;
|
||||
use network::NetworkApi;
|
||||
use sea_orm::DbErr;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_sessions::Expiry;
|
||||
use tower_sessions::cookie::time::Duration;
|
||||
use tower_sessions::cookie::{Key, SameSite};
|
||||
use tower_sessions::Expiry;
|
||||
use tower_sessions_sqlx_store::SqliteStore;
|
||||
use users::{AuthSession, Backend};
|
||||
|
||||
use crate::client_manager::storage::StorageToken;
|
||||
use crate::FeatureFlags;
|
||||
use crate::client_manager::ClientManager;
|
||||
use crate::client_manager::storage::StorageToken;
|
||||
use crate::db::{Db, UserIdInDb};
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
use crate::FeatureFlags;
|
||||
|
||||
/// Embed assets for web dashboard, build frontend first
|
||||
#[cfg(feature = "embed")]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::Path;
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{delete, post};
|
||||
use axum::{extract::State, routing::get, Json, Router};
|
||||
use axum::{Json, Router, extract::State, routing::get};
|
||||
use axum_login::AuthUser;
|
||||
use easytier::launcher::NetworkConfig;
|
||||
use easytier::proto::common::Void;
|
||||
@@ -16,7 +16,7 @@ use crate::db::UserIdInDb;
|
||||
|
||||
use super::users::AuthSession;
|
||||
use super::{
|
||||
convert_db_error, other_error, AppState, AppStateInner, Error, HttpHandleError, RpcError,
|
||||
AppState, AppStateInner, Error, HttpHandleError, RpcError, convert_db_error, other_error,
|
||||
};
|
||||
|
||||
fn convert_rpc_error(e: RpcError) -> (StatusCode, Json<Error>) {
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::time::Duration;
|
||||
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use axum::routing::get;
|
||||
use openidconnect::core::{
|
||||
CoreAuthDisplay, CoreAuthPrompt, CoreErrorResponseType, CoreGenderClaim, CoreJsonWebKey,
|
||||
CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, CoreProviderMetadata,
|
||||
@@ -216,7 +216,9 @@ impl OidcConfig {
|
||||
} = opts;
|
||||
|
||||
if oidc_issuer_url.is_none() || oidc_client_id.is_none() || oidc_redirect_url.is_none() {
|
||||
return Err(anyhow::anyhow!("--oidc-issuer-url, --oidc-client-id and --oidc-redirect-url are required when using OIDC authentication"));
|
||||
return Err(anyhow::anyhow!(
|
||||
"--oidc-issuer-url, --oidc-client-id and --oidc-redirect-url are required when using OIDC authentication"
|
||||
));
|
||||
}
|
||||
if oidc_username_claim.trim().is_empty() {
|
||||
return Err(anyhow::anyhow!("--oidc-username-claim cannot be empty"));
|
||||
@@ -373,18 +375,17 @@ mod route {
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if let Some(verifier) = pkce_verifier {
|
||||
if let Err(e) = session
|
||||
if let Some(verifier) = pkce_verifier
|
||||
&& let Err(e) = session
|
||||
.insert("oidc_pkce_verifier", verifier.secret().clone())
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to store pkce_verifier in session: {:?}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(other_error("Session error")),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
{
|
||||
tracing::error!("Failed to store pkce_verifier in session: {:?}", e);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(other_error("Session error")),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if let Err(e) = session.insert("oidc_pkce_used", pkce_enabled).await {
|
||||
tracing::error!("Failed to store pkce_used in session: {:?}", e);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
routing::post,
|
||||
Json, Router,
|
||||
};
|
||||
use axum_login::AuthUser as _;
|
||||
use easytier::proto::rpc_types::controller::BaseController;
|
||||
|
||||
use crate::db::UserIdInDb;
|
||||
|
||||
use super::{other_error, AppState, HttpHandleError};
|
||||
use super::{AppState, HttpHandleError, other_error};
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ProxyRpcRequest {
|
||||
@@ -120,7 +120,7 @@ async fn handle_proxy_rpc_by_session(
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
other_error(format!("Unknown service: {}", service_name)).into(),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ impl AuthUser for User {
|
||||
|
||||
fn session_auth_hash(&self) -> &[u8] {
|
||||
self.db_user.password.as_bytes() // We use the password hash as the auth
|
||||
// hash--what this means
|
||||
// is when the user changes their password the
|
||||
// auth session becomes invalid.
|
||||
// hash--what this means
|
||||
// is when the user changes their password the
|
||||
// auth session becomes invalid.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use axum::{
|
||||
Router,
|
||||
extract::State,
|
||||
http::header,
|
||||
response::{IntoResponse, Response},
|
||||
routing, Router,
|
||||
routing,
|
||||
};
|
||||
use axum_embed::ServeEmbed;
|
||||
use easytier::common::scoped_task::ScopedTask;
|
||||
|
||||
@@ -49,6 +49,7 @@ impl WebhookConfig {
|
||||
pub struct ValidateTokenRequest {
|
||||
pub token: String,
|
||||
pub machine_id: String,
|
||||
pub public_ip: Option<String>,
|
||||
pub hostname: String,
|
||||
pub version: String,
|
||||
pub os_type: Option<String>,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
disallowed-methods = [
|
||||
{ path = "itertools::Itertools::map_into", reason = "Blocks underlying iterator optimizations. Use the native `.map(Into::into)` instead." },
|
||||
{ path = "itertools::Itertools::map_ok", reason = "Blocks underlying iterator optimizations. Use the native `.map(|r| r.map(f))` instead." },
|
||||
{ path = "itertools::Itertools::filter_ok", reason = "Blocks underlying iterator optimizations. Use a native approach, e.g., `.filter(|r| r.as_ref().map_or(true, condition))`." },
|
||||
{ path = "itertools::Itertools::filter_map_ok", reason = "Blocks underlying iterator optimizations. Use native `.map()` and `.flatten()`, or extract logic into a standard `.filter_map()`." },
|
||||
|
||||
{ path = "itertools::Itertools::collect_vec", reason = "Non-standard idiom. Directly use the standard library's `.collect::<Vec<_>>()`." },
|
||||
{ path = "itertools::Itertools::try_collect", reason = "Non-standard idiom. Standard `collect()` already supports Result/Option inversion; use `.collect::<Result<_, _>>()`." },
|
||||
{ path = "itertools::Itertools::set_from", reason = "Non-standard idiom. Directly use the `.extend()` method provided by the standard library's `Extend` trait." },
|
||||
{ path = "itertools::Itertools::concat", reason = "Non-standard idiom. Use native `.flatten().collect()` or a slice's `.concat()` instead." }
|
||||
]
|
||||
+8
-15
@@ -4,11 +4,11 @@ description = "A full meshed p2p VPN, connecting all your devices in one network
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
version = "2.6.0"
|
||||
edition = "2021"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
categories = ["network-programming", "command-line-utilities"]
|
||||
rust-version = "1.93.0"
|
||||
license-file = "LICENSE"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -50,7 +50,7 @@ time = "0.3"
|
||||
toml = "0.8.12"
|
||||
chrono = { version = "0.4.37", features = ["serde"] }
|
||||
|
||||
cfg-if = "1.0"
|
||||
delegate = "0.13.5"
|
||||
|
||||
itertools = "0.14.0"
|
||||
|
||||
@@ -165,7 +165,6 @@ network-interface = "2.0"
|
||||
|
||||
# for ospf route
|
||||
petgraph = "0.8.1"
|
||||
hashbrown = "0.15.3"
|
||||
ordered_hash_map = "0.5.0"
|
||||
|
||||
# for wireguard
|
||||
@@ -242,6 +241,7 @@ hickory-server = { version = "0.25.2", features = [
|
||||
"resolver",
|
||||
], optional = true }
|
||||
|
||||
bon = "3.9.1"
|
||||
derive_builder = "0.20.2"
|
||||
humantime-serde = "1.1.1"
|
||||
multimap = "0.10.1"
|
||||
@@ -324,22 +324,14 @@ easytier-rpc-build = { path = "../easytier-rpc-build", features = [
|
||||
"internal-namespace",
|
||||
] }
|
||||
prost-reflect-build = { version = "0.14.0" }
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = [
|
||||
"win7",
|
||||
] }
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
reqwest = { version = "0.12.12", features = ["blocking"] }
|
||||
zip = "4.0.0"
|
||||
|
||||
# enable thunk-rs when compiling for x86_64 or i686 windows
|
||||
[target.x86_64-pc-windows-msvc.build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = [
|
||||
"win7",
|
||||
] }
|
||||
|
||||
[target.i686-pc-windows-msvc.build-dependencies]
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = [
|
||||
"win7",
|
||||
] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "3.0.0"
|
||||
@@ -347,6 +339,7 @@ rstest = "0.25.0"
|
||||
futures-util = "0.3.31"
|
||||
maplit = "1.0.2"
|
||||
tempfile = "3.22.0"
|
||||
ctor = "0.8.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
defguard_wireguard_rs = "0.4.2"
|
||||
|
||||
+6
-6
@@ -86,7 +86,9 @@ impl WindowsBuild {
|
||||
} else {
|
||||
Self::download_protoc()
|
||||
};
|
||||
std::env::set_var("PROTOC", protoc_path);
|
||||
unsafe {
|
||||
std::env::set_var("PROTOC", protoc_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,12 +143,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
}
|
||||
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
// enable thunk-rs when target os is windows and arch is x86_64 or i686
|
||||
#[cfg(target_os = "windows")]
|
||||
if !std::env::var("TARGET")
|
||||
.unwrap_or_default()
|
||||
.contains("aarch64")
|
||||
{
|
||||
if target_os == "windows" && (target_arch == "x86" || target_arch == "x86_64") {
|
||||
thunk::thunk();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::{io, mem::ManuallyDrop, net::SocketAddr, os::windows::io::AsRawSocket};
|
||||
use anyhow::Context;
|
||||
use network_interface::NetworkInterfaceConfig;
|
||||
use windows::{
|
||||
core::BSTR,
|
||||
Win32::{
|
||||
Foundation::{BOOL, FALSE},
|
||||
NetworkManagement::WindowsFirewall::{
|
||||
@@ -12,15 +11,16 @@ use windows::{
|
||||
NET_FW_RULE_DIR_OUT,
|
||||
},
|
||||
Networking::WinSock::{
|
||||
htonl, setsockopt, WSAGetLastError, WSAIoctl, IPPROTO_IP, IPPROTO_IPV6,
|
||||
IPV6_UNICAST_IF, IP_UNICAST_IF, SIO_UDP_CONNRESET, SOCKET, SOCKET_ERROR,
|
||||
IP_UNICAST_IF, IPPROTO_IP, IPPROTO_IPV6, IPV6_UNICAST_IF, SIO_UDP_CONNRESET, SOCKET,
|
||||
SOCKET_ERROR, WSAGetLastError, WSAIoctl, htonl, setsockopt,
|
||||
},
|
||||
System::Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_MULTITHREADED,
|
||||
CLSCTX_ALL, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize,
|
||||
},
|
||||
System::Ole::{SafeArrayCreateVector, SafeArrayPutElement},
|
||||
System::Variant::{VARENUM, VARIANT, VT_ARRAY, VT_BSTR, VT_VARIANT},
|
||||
},
|
||||
core::BSTR,
|
||||
};
|
||||
|
||||
pub fn disable_connection_reset<S: AsRawSocket>(socket: &S) -> io::Result<()> {
|
||||
@@ -345,7 +345,7 @@ fn add_protocol_firewall_rules(
|
||||
|
||||
SafeArrayPutElement(
|
||||
interface_array,
|
||||
&index as *const _ as *const i32,
|
||||
&index as *const _,
|
||||
&variant_interface as *const _ as *const std::ffi::c_void,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -345,7 +345,7 @@ impl AclProcessor {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort by priority (higher priority first)
|
||||
rules.sort_by(|a, b| b.priority.cmp(&a.priority));
|
||||
rules.sort_by_key(|r| std::cmp::Reverse(r.priority));
|
||||
|
||||
match chain.chain_type() {
|
||||
ChainType::Inbound => inbound_rules.extend(rules),
|
||||
@@ -507,7 +507,7 @@ impl AclProcessor {
|
||||
matched_rule: Some(RuleId::Default),
|
||||
should_log: false,
|
||||
log_context: Some(AclLogContext::UnsupportedChainType),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -679,28 +679,28 @@ impl AclProcessor {
|
||||
}
|
||||
|
||||
// Source port check
|
||||
if let Some(src_port) = packet_info.src_port {
|
||||
if !rule.src_port_ranges.is_empty() {
|
||||
let matches = rule
|
||||
.src_port_ranges
|
||||
.iter()
|
||||
.any(|(start, end)| src_port >= *start && src_port <= *end);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
if let Some(src_port) = packet_info.src_port
|
||||
&& !rule.src_port_ranges.is_empty()
|
||||
{
|
||||
let matches = rule
|
||||
.src_port_ranges
|
||||
.iter()
|
||||
.any(|(start, end)| src_port >= *start && src_port <= *end);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Destination port check
|
||||
if let Some(dst_port) = packet_info.dst_port {
|
||||
if !rule.dst_port_ranges.is_empty() {
|
||||
let matches = rule
|
||||
.dst_port_ranges
|
||||
.iter()
|
||||
.any(|(start, end)| dst_port >= *start && dst_port <= *end);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
if let Some(dst_port) = packet_info.dst_port
|
||||
&& !rule.dst_port_ranges.is_empty()
|
||||
{
|
||||
let matches = rule
|
||||
.dst_port_ranges
|
||||
.iter()
|
||||
.any(|(start, end)| dst_port >= *start && dst_port <= *end);
|
||||
if !matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use zstd::bulk;
|
||||
|
||||
use zerocopy::{AsBytes as _, FromBytes as _};
|
||||
|
||||
use crate::tunnel::packet_def::{CompressorAlgo, CompressorTail, ZCPacket, COMPRESSOR_TAIL_SIZE};
|
||||
use crate::tunnel::packet_def::{COMPRESSOR_TAIL_SIZE, CompressorAlgo, CompressorTail, ZCPacket};
|
||||
|
||||
type Error = anyhow::Error;
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use base64::{prelude::BASE64_STANDARD, Engine as _};
|
||||
use cfg_if::cfg_if;
|
||||
use clap::builder::PossibleValue;
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
use clap::ValueEnum;
|
||||
use clap::builder::PossibleValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString, VariantArray};
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
@@ -109,10 +108,9 @@ impl ValueEnum for EncryptionAlgorithm {
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for EncryptionAlgorithm {
|
||||
fn default() -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto"))] {
|
||||
EncryptionAlgorithm::AesGcm
|
||||
} else {
|
||||
cfg_select! {
|
||||
any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto") => EncryptionAlgorithm::AesGcm,
|
||||
_ => {
|
||||
crate::common::log::warn!("no AEAD encryption algorithm is available, using INSECURE XOR");
|
||||
EncryptionAlgorithm::Xor
|
||||
}
|
||||
@@ -621,14 +619,14 @@ impl ConfigLoader for TomlConfigLoader {
|
||||
if locked_config.proxy_network.is_none() {
|
||||
locked_config.proxy_network = Some(vec![]);
|
||||
}
|
||||
if let Some(mapped_cidr) = mapped_cidr.as_ref() {
|
||||
if cidr.network_length() != mapped_cidr.network_length() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mapped CIDR must have the same network length as the original CIDR: {} != {}",
|
||||
cidr.network_length(),
|
||||
mapped_cidr.network_length()
|
||||
));
|
||||
}
|
||||
if let Some(mapped_cidr) = mapped_cidr.as_ref()
|
||||
&& cidr.network_length() != mapped_cidr.network_length()
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mapped CIDR must have the same network length as the original CIDR: {} != {}",
|
||||
cidr.network_length(),
|
||||
mapped_cidr.network_length()
|
||||
));
|
||||
}
|
||||
// insert if no duplicate
|
||||
if !locked_config
|
||||
@@ -881,10 +879,10 @@ impl ConfigLoader for TomlConfigLoader {
|
||||
|
||||
let mut flag_map: serde_json::Map<String, serde_json::Value> = Default::default();
|
||||
for (key, value) in default_flags_hashmap {
|
||||
if let Some(v) = cur_flags_hashmap.get(&key) {
|
||||
if *v != value {
|
||||
flag_map.insert(key, v.clone());
|
||||
}
|
||||
if let Some(v) = cur_flags_hashmap.get(&key)
|
||||
&& *v != value
|
||||
{
|
||||
flag_map.insert(key, v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,6 +1087,7 @@ pub async fn load_config_from_file(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{remove_env_var, set_env_var};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::NamedTempFile;
|
||||
@@ -1212,8 +1211,8 @@ proto = "tcp"
|
||||
#[tokio::test]
|
||||
async fn test_env_var_expansion_and_readonly_flag() {
|
||||
// 设置测试环境变量
|
||||
std::env::set_var("TEST_SECRET", "my-test-secret-123");
|
||||
std::env::set_var("TEST_NETWORK", "test-network");
|
||||
set_env_var("TEST_SECRET", "my-test-secret-123");
|
||||
set_env_var("TEST_NETWORK", "test-network");
|
||||
|
||||
// 创建临时配置文件,包含环境变量占位符
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
@@ -1253,8 +1252,8 @@ network_secret = "${TEST_SECRET}"
|
||||
);
|
||||
|
||||
// 清理环境变量
|
||||
std::env::remove_var("TEST_SECRET");
|
||||
std::env::remove_var("TEST_NETWORK");
|
||||
remove_env_var("TEST_SECRET");
|
||||
remove_env_var("TEST_NETWORK");
|
||||
}
|
||||
|
||||
/// RPC API 安全测试(只读配置保护)
|
||||
@@ -1267,7 +1266,7 @@ network_secret = "${TEST_SECRET}"
|
||||
/// `easytier/src/rpc_service/instance_manage.rs` 中实现
|
||||
#[tokio::test]
|
||||
async fn test_readonly_config_api_protection() {
|
||||
std::env::set_var("API_TEST_SECRET", "secret-value");
|
||||
set_env_var("API_TEST_SECRET", "secret-value");
|
||||
|
||||
// 创建包含环境变量的配置
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
@@ -1298,7 +1297,7 @@ network_secret = "${API_TEST_SECRET}"
|
||||
"Permission flag should be set correctly"
|
||||
);
|
||||
|
||||
std::env::remove_var("API_TEST_SECRET");
|
||||
remove_env_var("API_TEST_SECRET");
|
||||
}
|
||||
|
||||
/// CLI 参数测试(--disable-env-parsing 开关)
|
||||
@@ -1308,7 +1307,7 @@ network_secret = "${API_TEST_SECRET}"
|
||||
/// - 配置不会被标记为只读
|
||||
#[tokio::test]
|
||||
async fn test_disable_env_parsing_flag() {
|
||||
std::env::set_var("DISABLED_TEST_VAR", "should-not-expand");
|
||||
set_env_var("DISABLED_TEST_VAR", "should-not-expand");
|
||||
|
||||
// 创建包含环境变量占位符的配置
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
@@ -1346,7 +1345,7 @@ network_secret = "${DISABLED_TEST_VAR}"
|
||||
"Config should be NO_DELETE due to no config_dir, not env vars"
|
||||
);
|
||||
|
||||
std::env::remove_var("DISABLED_TEST_VAR");
|
||||
remove_env_var("DISABLED_TEST_VAR");
|
||||
}
|
||||
|
||||
/// 多实例隔离测试
|
||||
@@ -1357,8 +1356,8 @@ network_secret = "${DISABLED_TEST_VAR}"
|
||||
#[tokio::test]
|
||||
async fn test_multiple_instances_with_different_env_vars() {
|
||||
// 实例1:使用第一组环境变量
|
||||
std::env::set_var("INSTANCE_SECRET", "instance1-secret");
|
||||
std::env::set_var("INSTANCE_NAME", "instance-one");
|
||||
set_env_var("INSTANCE_SECRET", "instance1-secret");
|
||||
set_env_var("INSTANCE_NAME", "instance-one");
|
||||
|
||||
let mut temp_file1 = NamedTempFile::new().unwrap();
|
||||
let config_content = r#"
|
||||
@@ -1388,8 +1387,8 @@ network_secret = "${INSTANCE_SECRET}"
|
||||
);
|
||||
|
||||
// 实例2:修改环境变量后加载同一模板
|
||||
std::env::set_var("INSTANCE_SECRET", "instance2-secret");
|
||||
std::env::set_var("INSTANCE_NAME", "instance-two");
|
||||
set_env_var("INSTANCE_SECRET", "instance2-secret");
|
||||
set_env_var("INSTANCE_NAME", "instance-two");
|
||||
|
||||
let mut temp_file2 = NamedTempFile::new().unwrap();
|
||||
temp_file2.write_all(config_content.as_bytes()).unwrap();
|
||||
@@ -1419,8 +1418,8 @@ network_secret = "${INSTANCE_SECRET}"
|
||||
);
|
||||
|
||||
// 清理
|
||||
std::env::remove_var("INSTANCE_SECRET");
|
||||
std::env::remove_var("INSTANCE_NAME");
|
||||
remove_env_var("INSTANCE_SECRET");
|
||||
remove_env_var("INSTANCE_NAME");
|
||||
}
|
||||
|
||||
/// 实际配置字段测试(network_secret、peer.uri 等)
|
||||
@@ -1433,11 +1432,11 @@ network_secret = "${INSTANCE_SECRET}"
|
||||
#[tokio::test]
|
||||
async fn test_real_config_fields_expansion() {
|
||||
// 设置各种实际场景的环境变量
|
||||
std::env::set_var("ET_SECRET", "production-secret-key");
|
||||
std::env::set_var("PEER_HOST", "peer.example.com");
|
||||
std::env::set_var("PEER_PORT", "11011");
|
||||
std::env::set_var("LISTEN_PORT", "11010");
|
||||
std::env::set_var("NETWORK_NAME", "prod-network");
|
||||
set_env_var("ET_SECRET", "production-secret-key");
|
||||
set_env_var("PEER_HOST", "peer.example.com");
|
||||
set_env_var("PEER_PORT", "11011");
|
||||
set_env_var("LISTEN_PORT", "11010");
|
||||
set_env_var("NETWORK_NAME", "prod-network");
|
||||
|
||||
// 创建包含多个实际字段的完整配置
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
@@ -1485,11 +1484,11 @@ uri = "tcp://${PEER_HOST}:${PEER_PORT}"
|
||||
assert!(control.is_no_delete());
|
||||
|
||||
// 清理环境变量
|
||||
std::env::remove_var("ET_SECRET");
|
||||
std::env::remove_var("PEER_HOST");
|
||||
std::env::remove_var("PEER_PORT");
|
||||
std::env::remove_var("LISTEN_PORT");
|
||||
std::env::remove_var("NETWORK_NAME");
|
||||
remove_env_var("ET_SECRET");
|
||||
remove_env_var("PEER_HOST");
|
||||
remove_env_var("PEER_PORT");
|
||||
remove_env_var("LISTEN_PORT");
|
||||
remove_env_var("NETWORK_NAME");
|
||||
}
|
||||
|
||||
/// 带默认值的环境变量
|
||||
@@ -1499,8 +1498,8 @@ uri = "tcp://${PEER_HOST}:${PEER_PORT}"
|
||||
#[tokio::test]
|
||||
async fn test_env_var_with_default_value() {
|
||||
// 确保变量未定义
|
||||
std::env::remove_var("UNDEFINED_PORT");
|
||||
std::env::remove_var("UNDEFINED_SECRET");
|
||||
remove_env_var("UNDEFINED_PORT");
|
||||
remove_env_var("UNDEFINED_SECRET");
|
||||
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
let config_content = r#"
|
||||
@@ -1541,7 +1540,7 @@ network_secret = "${UNDEFINED_SECRET:-default-secret}"
|
||||
/// - 未定义的环境变量保持原样(shellexpand 的默认行为)
|
||||
#[tokio::test]
|
||||
async fn test_undefined_env_var_without_default() {
|
||||
std::env::remove_var("COMPLETELY_UNDEFINED");
|
||||
remove_env_var("COMPLETELY_UNDEFINED");
|
||||
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
let config_content = r#"
|
||||
@@ -1571,6 +1570,8 @@ network_secret = "${COMPLETELY_UNDEFINED}"
|
||||
|
||||
// 注意:由于没有实际替换发生,控制标记不应因环境变量而设置
|
||||
// 但会因为其他原因(如没有 config_dir)被标记为 NO_DELETE
|
||||
// 这里我们主要验证 NO_DELETE 标记的逻辑
|
||||
// 由于没有 config_dir,文件会被标记为 NO_DELETE,但不是因为环境变量
|
||||
assert!(control.is_no_delete());
|
||||
}
|
||||
|
||||
@@ -1582,9 +1583,9 @@ network_secret = "${COMPLETELY_UNDEFINED}"
|
||||
#[tokio::test]
|
||||
async fn test_boolean_type_env_vars() {
|
||||
// 设置布尔类型的环境变量
|
||||
std::env::set_var("ENABLE_DHCP", "true");
|
||||
std::env::set_var("ENABLE_ENCRYPTION", "false");
|
||||
std::env::set_var("ENABLE_IPV6", "true");
|
||||
set_env_var("ENABLE_DHCP", "true");
|
||||
set_env_var("ENABLE_ENCRYPTION", "false");
|
||||
set_env_var("ENABLE_IPV6", "true");
|
||||
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
let config_content = r#"
|
||||
@@ -1622,9 +1623,9 @@ enable_ipv6 = ${ENABLE_IPV6}
|
||||
assert!(control.is_no_delete());
|
||||
|
||||
// 清理
|
||||
std::env::remove_var("ENABLE_DHCP");
|
||||
std::env::remove_var("ENABLE_ENCRYPTION");
|
||||
std::env::remove_var("ENABLE_IPV6");
|
||||
remove_env_var("ENABLE_DHCP");
|
||||
remove_env_var("ENABLE_ENCRYPTION");
|
||||
remove_env_var("ENABLE_IPV6");
|
||||
}
|
||||
|
||||
/// 数字类型环境变量
|
||||
@@ -1635,8 +1636,8 @@ enable_ipv6 = ${ENABLE_IPV6}
|
||||
#[tokio::test]
|
||||
async fn test_numeric_type_env_vars() {
|
||||
// 设置数字类型的环境变量
|
||||
std::env::set_var("MTU_VALUE", "1400");
|
||||
std::env::set_var("THREAD_COUNT", "4");
|
||||
set_env_var("MTU_VALUE", "1400");
|
||||
set_env_var("THREAD_COUNT", "4");
|
||||
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
let config_content = r#"
|
||||
@@ -1671,8 +1672,8 @@ multi_thread_count = ${THREAD_COUNT}
|
||||
assert!(control.is_no_delete());
|
||||
|
||||
// 清理
|
||||
std::env::remove_var("MTU_VALUE");
|
||||
std::env::remove_var("THREAD_COUNT");
|
||||
remove_env_var("MTU_VALUE");
|
||||
remove_env_var("THREAD_COUNT");
|
||||
}
|
||||
|
||||
/// 混合类型环境变量
|
||||
@@ -1684,12 +1685,12 @@ multi_thread_count = ${THREAD_COUNT}
|
||||
#[tokio::test]
|
||||
async fn test_mixed_type_env_vars() {
|
||||
// 设置不同类型的环境变量
|
||||
std::env::set_var("MIXED_SECRET", "mixed-secret-key");
|
||||
std::env::set_var("MIXED_NETWORK", "production");
|
||||
std::env::set_var("MIXED_DHCP", "true");
|
||||
std::env::set_var("MIXED_MTU", "1500");
|
||||
std::env::set_var("MIXED_ENCRYPTION", "false");
|
||||
std::env::set_var("MIXED_LISTEN_PORT", "12345");
|
||||
set_env_var("MIXED_SECRET", "mixed-secret-key");
|
||||
set_env_var("MIXED_NETWORK", "production");
|
||||
set_env_var("MIXED_DHCP", "true");
|
||||
set_env_var("MIXED_MTU", "1500");
|
||||
set_env_var("MIXED_ENCRYPTION", "false");
|
||||
set_env_var("MIXED_LISTEN_PORT", "12345");
|
||||
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
let config_content = r#"
|
||||
@@ -1741,11 +1742,11 @@ enable_encryption = ${MIXED_ENCRYPTION}
|
||||
assert!(control.is_no_delete());
|
||||
|
||||
// 清理
|
||||
std::env::remove_var("MIXED_SECRET");
|
||||
std::env::remove_var("MIXED_NETWORK");
|
||||
std::env::remove_var("MIXED_DHCP");
|
||||
std::env::remove_var("MIXED_MTU");
|
||||
std::env::remove_var("MIXED_ENCRYPTION");
|
||||
std::env::remove_var("MIXED_LISTEN_PORT");
|
||||
remove_env_var("MIXED_SECRET");
|
||||
remove_env_var("MIXED_NETWORK");
|
||||
remove_env_var("MIXED_DHCP");
|
||||
remove_env_var("MIXED_MTU");
|
||||
remove_env_var("MIXED_ENCRYPTION");
|
||||
remove_env_var("MIXED_LISTEN_PORT");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use anyhow::Context;
|
||||
use hickory_proto::runtime::TokioRuntimeProvider;
|
||||
|
||||
@@ -42,10 +42,11 @@ pub fn expand_env_vars(text: &str) -> (String, bool) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{remove_env_var, set_env_var};
|
||||
|
||||
#[test]
|
||||
fn test_expand_standard_syntax() {
|
||||
std::env::set_var("TEST_VAR_STANDARD", "test_value");
|
||||
set_env_var("TEST_VAR_STANDARD", "test_value");
|
||||
let (result, changed) = expand_env_vars("secret=${TEST_VAR_STANDARD}");
|
||||
assert_eq!(result, "secret=test_value");
|
||||
assert!(changed);
|
||||
@@ -53,7 +54,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_expand_short_syntax() {
|
||||
std::env::set_var("TEST_VAR_SHORT", "short_value");
|
||||
set_env_var("TEST_VAR_SHORT", "short_value");
|
||||
let (result, changed) = expand_env_vars("key=$TEST_VAR_SHORT");
|
||||
assert_eq!(result, "key=short_value");
|
||||
assert!(changed);
|
||||
@@ -62,7 +63,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_expand_with_default() {
|
||||
// 确保变量未定义
|
||||
std::env::remove_var("UNDEFINED_VAR_WITH_DEFAULT");
|
||||
remove_env_var("UNDEFINED_VAR_WITH_DEFAULT");
|
||||
let (result, changed) = expand_env_vars("port=${UNDEFINED_VAR_WITH_DEFAULT:-8080}");
|
||||
assert_eq!(result, "port=8080");
|
||||
assert!(changed);
|
||||
@@ -84,8 +85,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_multiple_vars() {
|
||||
std::env::set_var("VAR1", "value1");
|
||||
std::env::set_var("VAR2", "value2");
|
||||
set_env_var("VAR1", "value1");
|
||||
set_env_var("VAR2", "value2");
|
||||
let (result, changed) = expand_env_vars("${VAR1} and ${VAR2}");
|
||||
assert_eq!(result, "value1 and value2");
|
||||
assert!(changed);
|
||||
@@ -94,7 +95,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_undefined_var_without_default() {
|
||||
// 确保变量未定义
|
||||
std::env::remove_var("COMPLETELY_UNDEFINED_VAR");
|
||||
remove_env_var("COMPLETELY_UNDEFINED_VAR");
|
||||
let (result, changed) = expand_env_vars("value=${COMPLETELY_UNDEFINED_VAR}");
|
||||
// shellexpand::env 对未定义的变量会保持原样
|
||||
assert_eq!(result, "value=${COMPLETELY_UNDEFINED_VAR}");
|
||||
@@ -103,8 +104,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_complex_toml_config() {
|
||||
std::env::set_var("ET_SECRET", "my-secret-key");
|
||||
std::env::set_var("ET_PORT", "11010");
|
||||
set_env_var("ET_SECRET", "my-secret-key");
|
||||
set_env_var("ET_PORT", "11010");
|
||||
|
||||
let config = r#"
|
||||
[network_identity]
|
||||
@@ -123,7 +124,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_escape_syntax_double_dollar() {
|
||||
std::env::set_var("ESCAPED_VAR", "should_not_expand");
|
||||
set_env_var("ESCAPED_VAR", "should_not_expand");
|
||||
// shellexpand 使用 $$ 作为转义序列,表示字面量的单个 $
|
||||
// $$ 会被转义为单个 $,不会触发变量扩展
|
||||
let (result, changed) = expand_env_vars("value=$${ESCAPED_VAR}");
|
||||
@@ -133,7 +134,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_escape_syntax_backslash() {
|
||||
std::env::set_var("ESCAPED_VAR", "should_not_expand");
|
||||
set_env_var("ESCAPED_VAR", "should_not_expand");
|
||||
// shellexpand 中反斜杠转义的行为:\$ 会展开为 \<变量值>
|
||||
// 这不是推荐的转义方式,此测试仅为记录实际行为
|
||||
let (result, changed) = expand_env_vars(r"value=\${ESCAPED_VAR}");
|
||||
@@ -143,7 +144,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_multiple_dollar_signs() {
|
||||
std::env::set_var("TEST_VAR", "value");
|
||||
set_env_var("TEST_VAR", "value");
|
||||
// 测试多个连续的 $ 符号
|
||||
let (result1, changed1) = expand_env_vars("$$");
|
||||
assert_eq!(result1, "$");
|
||||
@@ -161,7 +162,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_empty_var_value() {
|
||||
std::env::set_var("EMPTY_VAR", "");
|
||||
set_env_var("EMPTY_VAR", "");
|
||||
let (result, changed) = expand_env_vars("value=${EMPTY_VAR}");
|
||||
// 变量存在但值为空
|
||||
assert_eq!(result, "value=");
|
||||
@@ -170,7 +171,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_default_with_special_chars() {
|
||||
std::env::remove_var("UNDEFINED_SPECIAL");
|
||||
remove_env_var("UNDEFINED_SPECIAL");
|
||||
// 测试默认值包含冒号、等号、空格等特殊字符
|
||||
let (result, changed) = expand_env_vars("url=${UNDEFINED_SPECIAL:-http://localhost:8080}");
|
||||
assert_eq!(result, "url=http://localhost:8080");
|
||||
@@ -187,9 +188,9 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_var_name_with_numbers_underscores() {
|
||||
std::env::set_var("VAR_123", "num_value");
|
||||
std::env::set_var("_VAR", "underscore_prefix");
|
||||
std::env::set_var("VAR_", "underscore_suffix");
|
||||
set_env_var("VAR_123", "num_value");
|
||||
set_env_var("_VAR", "underscore_prefix");
|
||||
set_env_var("VAR_", "underscore_suffix");
|
||||
|
||||
let (result1, changed1) = expand_env_vars("${VAR_123}");
|
||||
assert_eq!(result1, "num_value");
|
||||
@@ -214,7 +215,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
// 注意:未闭合的 ${VAR 实际上 shellexpand 会当作普通文本处理
|
||||
// 它会尝试查找名为 "VAR" 的环境变量(到字符串末尾)
|
||||
std::env::remove_var("VAR");
|
||||
remove_env_var("VAR");
|
||||
let (result2, _changed2) = expand_env_vars("incomplete ${VAR");
|
||||
// 如果 VAR 未定义,shellexpand 会返回错误或保持原样
|
||||
assert_eq!(result2, "incomplete ${VAR");
|
||||
@@ -224,8 +225,8 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_mixed_defined_undefined_vars() {
|
||||
std::env::set_var("DEFINED_VAR", "defined");
|
||||
std::env::remove_var("UNDEFINED_VAR");
|
||||
set_env_var("DEFINED_VAR", "defined");
|
||||
remove_env_var("UNDEFINED_VAR");
|
||||
|
||||
// 混合已定义和未定义的变量
|
||||
// shellexpand::env 在遇到未定义变量时会返回错误(默认行为)
|
||||
@@ -237,7 +238,7 @@ uri = "tcp://127.0.0.1:${ET_PORT}"
|
||||
|
||||
#[test]
|
||||
fn test_nested_braces() {
|
||||
std::env::set_var("OUTER", "outer_value");
|
||||
set_env_var("OUTER", "outer_value");
|
||||
// 嵌套的大括号是无效语法,shellexpand::env 会返回错误
|
||||
let (result, changed) = expand_env_vars("${OUTER} and ${{INNER}}");
|
||||
// 由于语法错误,整个字符串保持不变
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{hash_map::DefaultHasher, HashMap},
|
||||
collections::{HashMap, hash_map::DefaultHasher},
|
||||
hash::Hasher,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::{Arc, Mutex},
|
||||
@@ -10,11 +10,11 @@ use arc_swap::ArcSwap;
|
||||
use dashmap::DashMap;
|
||||
|
||||
use super::{
|
||||
PeerId,
|
||||
config::{ConfigLoader, Flags},
|
||||
netns::NetNS,
|
||||
network::IPCollector,
|
||||
stun::{StunInfoCollector, StunInfoCollectorTrait},
|
||||
PeerId,
|
||||
};
|
||||
use crate::{
|
||||
common::{
|
||||
@@ -28,6 +28,7 @@ use crate::{
|
||||
common::{PeerFeatureFlag, PortForwardConfigPb},
|
||||
peer_rpc::PeerGroupInfo,
|
||||
},
|
||||
rpc_service::protected_port,
|
||||
tunnel::matches_protocol,
|
||||
};
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
@@ -658,6 +659,7 @@ impl GlobalCtx {
|
||||
if dst_is_local_virtual_ip || dst_is_local_phy_ip {
|
||||
// if is local ip, make sure the port is not one of the listening ports
|
||||
self.is_port_in_running_listeners(dst_addr.port(), is_udp)
|
||||
|| (!is_udp && protected_port::is_protected_tcp_port(dst_addr.port()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -765,6 +767,23 @@ pub mod tests {
|
||||
assert!(feature_flags.is_public_server);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_deny_proxy_for_process_wide_rpc_port() {
|
||||
protected_port::clear_protected_tcp_ports_for_test();
|
||||
protected_port::register_protected_tcp_port(15888);
|
||||
|
||||
let config = TomlConfigLoader::default();
|
||||
let global_ctx = GlobalCtx::new(config);
|
||||
let rpc_addr = SocketAddr::from(([127, 0, 0, 1], 15888));
|
||||
let other_tcp_addr = SocketAddr::from(([127, 0, 0, 1], 15889));
|
||||
|
||||
assert!(global_ctx.should_deny_proxy(&rpc_addr, false));
|
||||
assert!(!global_ctx.should_deny_proxy(&rpc_addr, true));
|
||||
assert!(!global_ctx.should_deny_proxy(&other_tcp_addr, false));
|
||||
|
||||
protected_port::clear_protected_tcp_ports_for_test();
|
||||
}
|
||||
|
||||
pub fn get_mock_global_ctx_with_network(
|
||||
network_identy: Option<NetworkIdentity>,
|
||||
) -> ArcGlobalCtx {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::{cidr_to_subnet_mask, run_shell_cmd, Error, IfConfiguerTrait};
|
||||
use super::{Error, IfConfiguerTrait, cidr_to_subnet_mask, run_shell_cmd};
|
||||
use async_trait::async_trait;
|
||||
use cidr::{Ipv4Inet, Ipv6Inet};
|
||||
|
||||
@@ -53,8 +53,8 @@ impl IfConfiguerTrait for MacIfConfiger {
|
||||
) -> Result<(), Error> {
|
||||
run_shell_cmd(
|
||||
format!(
|
||||
"ifconfig {} {:?}/{:?} 10.8.8.8 up",
|
||||
name, address, cidr_prefix,
|
||||
"ifconfig {} {:?}/{:?} {:?} up",
|
||||
name, address, cidr_prefix, address,
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
|
||||
@@ -119,8 +119,8 @@ async fn run_shell_cmd(cmd: &str) -> Result<(), Error> {
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()
|
||||
.await?;
|
||||
stdout = crate::utils::utf8_or_gbk_to_string(cmd_out.stdout.as_slice());
|
||||
stderr = crate::utils::utf8_or_gbk_to_string(cmd_out.stderr.as_slice());
|
||||
stdout = crate::utils::string::utf8_or_gbk_to_string(cmd_out.stdout.as_slice());
|
||||
stderr = crate::utils::string::utf8_or_gbk_to_string(cmd_out.stderr.as_slice());
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
|
||||
@@ -10,27 +10,27 @@ use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use cidr::{IpInet, Ipv4Inet, Ipv6Inet};
|
||||
use netlink_packet_core::{
|
||||
NetlinkDeserializable, NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable,
|
||||
NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_REQUEST,
|
||||
NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_REQUEST, NetlinkDeserializable,
|
||||
NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable,
|
||||
};
|
||||
use netlink_packet_route::{
|
||||
AddressFamily, RouteNetlinkMessage,
|
||||
address::{AddressAttribute, AddressMessage},
|
||||
route::{
|
||||
RouteAddress, RouteAttribute, RouteHeader, RouteMessage, RouteProtocol, RouteScope,
|
||||
RouteType,
|
||||
},
|
||||
AddressFamily, RouteNetlinkMessage,
|
||||
};
|
||||
use netlink_sys::{protocols::NETLINK_ROUTE, Socket, SocketAddr};
|
||||
use netlink_sys::{Socket, SocketAddr, protocols::NETLINK_ROUTE};
|
||||
use nix::{
|
||||
ifaddrs::getifaddrs,
|
||||
libc::{self, ifreq, ioctl, Ioctl, SIOCGIFFLAGS, SIOCGIFMTU, SIOCSIFFLAGS, SIOCSIFMTU},
|
||||
libc::{self, Ioctl, SIOCGIFFLAGS, SIOCGIFMTU, SIOCSIFFLAGS, SIOCSIFMTU, ifreq, ioctl},
|
||||
net::if_::InterfaceFlags,
|
||||
sys::socket::SockaddrLike as _,
|
||||
};
|
||||
use pnet::ipnetwork::ip_mask_to_prefix;
|
||||
|
||||
use super::{route::Route, Error, IfConfiguerTrait};
|
||||
use super::{Error, IfConfiguerTrait, route::Route};
|
||||
|
||||
pub(crate) fn dummy_socket() -> Result<std::net::UdpSocket, Error> {
|
||||
Ok(std::net::UdpSocket::bind("0:0")?)
|
||||
|
||||
@@ -740,10 +740,6 @@ impl InterfaceLuid {
|
||||
|
||||
// SAFETY: TODO
|
||||
let ret = unsafe { SetIpInterfaceEntry(&mut row) };
|
||||
if NO_ERROR == ret {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ret)
|
||||
}
|
||||
if NO_ERROR == ret { Ok(()) } else { Err(ret) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ use std::{
|
||||
};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::NO_ERROR,
|
||||
NetworkManagement::IpHelper::{GetIfEntry, SetIfEntry, MIB_IFROW},
|
||||
NetworkManagement::IpHelper::{GetIfEntry, MIB_IFROW, SetIfEntry},
|
||||
System::Diagnostics::Debug::{
|
||||
FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW,
|
||||
},
|
||||
};
|
||||
use winreg::{
|
||||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE},
|
||||
RegKey,
|
||||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE},
|
||||
};
|
||||
|
||||
use super::{Error, IfConfiguerTrait};
|
||||
@@ -331,7 +331,7 @@ impl RegistryManager {
|
||||
r"SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces\Tcpip_";
|
||||
|
||||
pub fn reg_delete_obsoleted_items(dev_name: &str) -> io::Result<()> {
|
||||
use winreg::{enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS, RegKey};
|
||||
use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS};
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let profiles_key = hklm.open_subkey_with_flags(
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles",
|
||||
@@ -405,7 +405,7 @@ impl RegistryManager {
|
||||
}
|
||||
|
||||
pub fn reg_change_catrgory_in_profile(dev_name: &str) -> io::Result<()> {
|
||||
use winreg::{enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS, RegKey};
|
||||
use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS};
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let profiles_key = hklm.open_subkey_with_flags(
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles",
|
||||
@@ -448,12 +448,11 @@ impl RegistryManager {
|
||||
for guid in network_key.enum_keys().map_while(Result::ok) {
|
||||
if let Ok(guid_key) = network_key.open_subkey_with_flags(&guid, KEY_READ) {
|
||||
// 检查 Connection/Name 是否匹配目标接口名
|
||||
if let Ok(conn_key) = guid_key.open_subkey_with_flags("Connection", KEY_READ) {
|
||||
if let Ok(name) = conn_key.get_value::<String, _>("Name") {
|
||||
if name == interface_name {
|
||||
return Ok(guid);
|
||||
}
|
||||
}
|
||||
if let Ok(conn_key) = guid_key.open_subkey_with_flags("Connection", KEY_READ)
|
||||
&& let Ok(name) = conn_key.get_value::<String, _>("Name")
|
||||
&& name == interface_name
|
||||
{
|
||||
return Ok(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+207
-129
@@ -1,19 +1,18 @@
|
||||
use std::io::IsTerminal as _;
|
||||
|
||||
use crate::common::config::LoggingConfigLoader;
|
||||
use crate::common::config::{FileLoggerConfig, LoggingConfigLoader};
|
||||
use crate::common::get_logger_timer_rfc3339;
|
||||
use crate::common::tracing_rolling_appender::{FileAppenderWrapper, RollingFileAppenderBase};
|
||||
use crate::rpc_service::logger::{CURRENT_LOG_LEVEL, LOGGER_LEVEL_SENDER};
|
||||
use anyhow::Context;
|
||||
use paste::paste;
|
||||
use regex::Regex;
|
||||
use std::io::IsTerminal;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing::{Level, Metadata};
|
||||
use tracing_subscriber::filter::{filter_fn, FilterExt};
|
||||
use tracing_subscriber::Registry;
|
||||
use tracing_subscriber::filter::{FilterExt, filter_fn};
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
use tracing_subscriber::fmt::layer;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::Registry;
|
||||
use tracing_subscriber::{EnvFilter, Layer};
|
||||
|
||||
macro_rules! __log__ {
|
||||
@@ -47,18 +46,16 @@ macro_rules! __log__ {
|
||||
|
||||
__log__!(const LOG_TARGET = "CORE");
|
||||
|
||||
fn parse_env_filter(default_level: LevelFilter) -> Result<EnvFilter, anyhow::Error> {
|
||||
let mut filter = EnvFilter::builder()
|
||||
.with_default_directive(default_level.into())
|
||||
fn parse_env_filter(default_level: Option<LevelFilter>) -> Result<EnvFilter, anyhow::Error> {
|
||||
let directive = match default_level {
|
||||
Some(level) => level.into(),
|
||||
None => format!("{LOG_TARGET}=info").parse()?,
|
||||
};
|
||||
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(directive)
|
||||
.from_env()
|
||||
.with_context(|| "failed to create env filter")?;
|
||||
|
||||
let pattern = Regex::new(&format!(r"(^|,){}\s*=", regex::escape(LOG_TARGET)))?;
|
||||
if !pattern.is_match(&filter.to_string()) {
|
||||
filter = filter.add_directive(format!("{LOG_TARGET}=info").parse()?);
|
||||
}
|
||||
|
||||
Ok(filter)
|
||||
.with_context(|| "failed to create env filter")
|
||||
}
|
||||
|
||||
fn is_log(meta: &Metadata) -> bool {
|
||||
@@ -78,7 +75,6 @@ macro_rules! log_layer {
|
||||
$layer
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_ansi(true)
|
||||
.with_filter(filter_fn(is_log))
|
||||
.boxed()
|
||||
};
|
||||
@@ -86,127 +82,68 @@ macro_rules! log_layer {
|
||||
|
||||
pub fn init(
|
||||
config: impl LoggingConfigLoader,
|
||||
need_reload: bool,
|
||||
reload: bool,
|
||||
) -> Result<Option<NewFilterSender>, anyhow::Error> {
|
||||
let mut layers = Vec::new();
|
||||
|
||||
let file_config = config.get_file_logger_config();
|
||||
let file_level = file_config
|
||||
.level
|
||||
.map(|s| s.parse().unwrap())
|
||||
.unwrap_or(LevelFilter::OFF);
|
||||
let console_layers = console_layers(
|
||||
config
|
||||
.get_console_logger_config()
|
||||
.level
|
||||
.map(|s| s.parse().unwrap()),
|
||||
)?;
|
||||
layers.extend(console_layers);
|
||||
|
||||
let mut ret_sender: Option<NewFilterSender> = None;
|
||||
let sender = if cfg!(not(test)) {
|
||||
let (file_layers, sender) = file_layers(config.get_file_logger_config(), reload)?;
|
||||
layers.extend(file_layers);
|
||||
sender
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// logger to a rolling file
|
||||
if file_level != LevelFilter::OFF || need_reload {
|
||||
let dir = file_config.dir.as_deref().unwrap_or(".");
|
||||
let file = file_config.file.as_deref().unwrap_or("easytier.log");
|
||||
let path = std::path::Path::new(dir).join(file);
|
||||
let path_str = path.to_string_lossy().into_owned();
|
||||
Registry::default()
|
||||
.with(layers)
|
||||
.try_init()
|
||||
.map(|_| sender)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
let builder = RollingFileAppenderBase::builder();
|
||||
let file_appender = builder
|
||||
.filename(path_str)
|
||||
.condition_daily()
|
||||
.max_filecount(file_config.count.unwrap_or(10))
|
||||
.condition_max_file_size(file_config.size_mb.unwrap_or(100) * 1024 * 1024)
|
||||
.build()
|
||||
.unwrap();
|
||||
type BoxLayer = Box<dyn Layer<Registry> + Send + Sync>;
|
||||
|
||||
// Create a simple wrapper that implements MakeWriter
|
||||
let wrapper = FileAppenderWrapper::new(file_appender);
|
||||
|
||||
let (file_filter, file_filter_reloader) =
|
||||
tracing_subscriber::reload::Layer::<_, Registry>::new(parse_env_filter(file_level)?);
|
||||
|
||||
let layer = |wrapper| {
|
||||
layer()
|
||||
.with_ansi(false)
|
||||
.with_writer(wrapper)
|
||||
.with_timer(get_logger_timer_rfc3339())
|
||||
};
|
||||
|
||||
layers.push(
|
||||
vec![
|
||||
tracing_layer!(layer(wrapper.clone())),
|
||||
log_layer!(layer(wrapper.clone())),
|
||||
]
|
||||
.with_filter(file_filter)
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
if need_reload {
|
||||
let (sender, recver) = std::sync::mpsc::channel();
|
||||
ret_sender = Some(sender.clone());
|
||||
|
||||
// 初始化全局状态
|
||||
let _ = LOGGER_LEVEL_SENDER.set(std::sync::Mutex::new(sender));
|
||||
let _ = CURRENT_LOG_LEVEL.set(std::sync::Mutex::new(file_level.to_string()));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(lf) = recver.recv() {
|
||||
let parsed_level = match lf.parse::<LevelFilter>() {
|
||||
Ok(level) => level,
|
||||
Err(e) => {
|
||||
error!("Failed to parse new log level {:?}: {}", lf, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut new_filter = match EnvFilter::builder()
|
||||
.with_default_directive(parsed_level.into())
|
||||
.from_env()
|
||||
.with_context(|| "failed to create file filter")
|
||||
{
|
||||
Ok(filter) => Some(filter),
|
||||
Err(e) => {
|
||||
error!("Failed to build new log filter for {:?}: {:?}", lf, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match file_filter_reloader.modify(|f| {
|
||||
*f = new_filter
|
||||
.take()
|
||||
.expect("log filter reloader only applies one filter per reload");
|
||||
}) {
|
||||
Ok(()) => {
|
||||
info!("Reload log filter succeed, new filter level: {:?}", lf);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to reload log filter: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Stop log filter reloader");
|
||||
});
|
||||
}
|
||||
fn console_layers(default_level: Option<LevelFilter>) -> anyhow::Result<Vec<BoxLayer>> {
|
||||
let mut layers = Vec::new();
|
||||
if matches!(default_level, Some(LevelFilter::OFF)) {
|
||||
return Ok(layers);
|
||||
}
|
||||
|
||||
// logger to console
|
||||
let console_config = config.get_console_logger_config();
|
||||
let console_level = console_config
|
||||
.level
|
||||
.map(|s| s.parse().unwrap())
|
||||
.unwrap_or(LevelFilter::OFF);
|
||||
|
||||
let (console_filter, _) =
|
||||
tracing_subscriber::reload::Layer::new(parse_env_filter(console_level)?);
|
||||
tracing_subscriber::reload::Layer::new(parse_env_filter(default_level)?);
|
||||
|
||||
let (stdout, stderr) = cfg_select! {
|
||||
test => {{
|
||||
let w = tracing_subscriber::fmt::TestWriter::new;
|
||||
(w, w)
|
||||
}}
|
||||
_ => (std::io::stdout, std::io::stderr),
|
||||
};
|
||||
|
||||
let ansi = std::io::stderr().is_terminal() || cfg!(test);
|
||||
|
||||
let layer = || {
|
||||
layer()
|
||||
.compact()
|
||||
.with_ansi(std::io::stderr().is_terminal())
|
||||
.with_timer(get_logger_timer_rfc3339())
|
||||
.with_writer(std::io::stderr)
|
||||
.with_ansi(ansi)
|
||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
.with_writer(stderr)
|
||||
};
|
||||
|
||||
layers.push(
|
||||
vec![
|
||||
tracing_layer!(layer()),
|
||||
log_layer!(layer()).with_filter(LevelFilter::WARN).boxed(),
|
||||
log_layer!(layer().with_writer(std::io::stdout))
|
||||
log_layer!(layer().with_writer(stdout))
|
||||
.with_filter(filter_fn(|metadata| *metadata.level() > Level::WARN))
|
||||
.boxed(),
|
||||
]
|
||||
@@ -219,23 +156,164 @@ pub fn init(
|
||||
layers.push(console_subscriber::ConsoleLayer::builder().spawn().boxed());
|
||||
}
|
||||
|
||||
Registry::default().with(layers).init();
|
||||
Ok(layers)
|
||||
}
|
||||
|
||||
Ok(ret_sender)
|
||||
fn file_layers(
|
||||
config: FileLoggerConfig,
|
||||
reload: bool,
|
||||
) -> anyhow::Result<(Vec<BoxLayer>, Option<NewFilterSender>)> {
|
||||
let mut layers = Vec::new();
|
||||
|
||||
let level = config.level.map(|s| s.parse().unwrap());
|
||||
|
||||
if matches!(level, Some(LevelFilter::OFF)) && !reload {
|
||||
return Ok((layers, None));
|
||||
}
|
||||
|
||||
let (file_filter, file_filter_reloader) =
|
||||
tracing_subscriber::reload::Layer::<_, Registry>::new(parse_env_filter(level)?);
|
||||
|
||||
let layer = |wrapper| {
|
||||
layer()
|
||||
.with_ansi(false)
|
||||
.with_writer(wrapper)
|
||||
.with_timer(get_logger_timer_rfc3339())
|
||||
};
|
||||
|
||||
let wrapper = {
|
||||
let path = {
|
||||
let dir = config.dir.as_deref().unwrap_or(".");
|
||||
let file = config.file.as_deref().unwrap_or("easytier.log");
|
||||
let path = std::path::Path::new(dir).join(file);
|
||||
path.to_string_lossy().into_owned()
|
||||
};
|
||||
|
||||
let builder = RollingFileAppenderBase::builder();
|
||||
let file_appender = builder
|
||||
.filename(path)
|
||||
.condition_daily()
|
||||
.max_filecount(config.count.unwrap_or(10))
|
||||
.condition_max_file_size(config.size_mb.unwrap_or(100) * 1024 * 1024)
|
||||
.build()
|
||||
.with_context(|| "failed to initialize rolling file appender")?;
|
||||
|
||||
FileAppenderWrapper::new(file_appender)
|
||||
};
|
||||
|
||||
layers.push(
|
||||
vec![
|
||||
tracing_layer!(layer(wrapper.clone())),
|
||||
log_layer!(layer(wrapper.clone())),
|
||||
]
|
||||
.with_filter(file_filter)
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
if !reload {
|
||||
return Ok((layers, None));
|
||||
}
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
// 初始化全局状态
|
||||
let _ = LOGGER_LEVEL_SENDER.set(std::sync::Mutex::new(tx.clone()));
|
||||
if let Some(level) = level {
|
||||
let _ = CURRENT_LOG_LEVEL.set(std::sync::Mutex::new(level.to_string()));
|
||||
}
|
||||
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(lf) = rx.recv() {
|
||||
let parsed_level = match lf.parse::<LevelFilter>() {
|
||||
Ok(level) => level,
|
||||
Err(e) => {
|
||||
error!("Failed to parse new log level {:?}: {}", lf, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut new_filter = match EnvFilter::builder()
|
||||
.with_default_directive(parsed_level.into())
|
||||
.from_env()
|
||||
.with_context(|| "failed to create file filter")
|
||||
{
|
||||
Ok(filter) => Some(filter),
|
||||
Err(e) => {
|
||||
error!("Failed to build new log filter for {:?}: {:?}", lf, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match file_filter_reloader.modify(|f| {
|
||||
*f = new_filter
|
||||
.take()
|
||||
.expect("log filter reloader only applies one filter per reload");
|
||||
}) {
|
||||
Ok(()) => {
|
||||
info!("Reload log filter succeed, new filter level: {:?}", lf);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to reload log filter: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Stop log filter reloader");
|
||||
});
|
||||
|
||||
Ok((layers, Some(tx)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::config::{self};
|
||||
use crate::common::config::FileLoggerConfig;
|
||||
|
||||
async fn test_logger_reload() {
|
||||
println!("current working dir: {:?}", std::env::current_dir());
|
||||
let config = config::LoggingConfigBuilder::default().build().unwrap();
|
||||
let s = init(&config, true).unwrap();
|
||||
tracing::debug!("test not display debug");
|
||||
s.unwrap().send(LevelFilter::DEBUG.to_string()).unwrap();
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
tracing::debug!("test display debug");
|
||||
#[ctor::ctor]
|
||||
fn init() {
|
||||
let _ = Registry::default()
|
||||
.with(console_layers(Some(LevelFilter::WARN)).unwrap())
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logger_reload() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let log_file_name = "reload-test.log".to_string();
|
||||
let log_path = temp_dir.path().join(&log_file_name);
|
||||
|
||||
let cfg = FileLoggerConfig {
|
||||
level: Some(LevelFilter::INFO.to_string()),
|
||||
file: Some(log_file_name),
|
||||
dir: Some(temp_dir.path().to_string_lossy().to_string()),
|
||||
size_mb: Some(10),
|
||||
count: Some(1),
|
||||
};
|
||||
|
||||
let (layers, sender) = file_layers(cfg, true).unwrap();
|
||||
let sender = sender.expect("reload=true should return a sender");
|
||||
|
||||
let before_marker = "reload-before-debug-marker";
|
||||
let after_marker = "reload-after-debug-marker";
|
||||
let subscriber = Registry::default().with(layers);
|
||||
|
||||
tracing::subscriber::with_default(subscriber, || {
|
||||
tracing::debug!("{}", before_marker);
|
||||
|
||||
sender.send(LevelFilter::DEBUG.to_string()).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
|
||||
tracing::debug!("{}", after_marker);
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
});
|
||||
|
||||
let content = std::fs::read_to_string(&log_path).unwrap_or_default();
|
||||
assert!(
|
||||
!content.contains(before_marker),
|
||||
"debug log should be filtered before reload"
|
||||
);
|
||||
assert!(
|
||||
content.contains(after_marker),
|
||||
"debug log should be visible after reload"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ pub fn get_logger_timer<F: time::formatting::Formattable>(
|
||||
tracing_subscriber::fmt::time::OffsetTime::new(local_offset, format)
|
||||
}
|
||||
|
||||
pub fn get_logger_timer_rfc3339(
|
||||
) -> tracing_subscriber::fmt::time::OffsetTime<time::format_description::well_known::Rfc3339> {
|
||||
pub fn get_logger_timer_rfc3339()
|
||||
-> tracing_subscriber::fmt::time::OffsetTime<time::format_description::well_known::Rfc3339> {
|
||||
get_logger_timer(time::format_description::well_known::Rfc3339)
|
||||
}
|
||||
|
||||
@@ -117,10 +117,10 @@ pub fn get_machine_id() -> uuid::Uuid {
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("et_machine_id"));
|
||||
|
||||
// try load from local file
|
||||
if let Ok(mid) = std::fs::read_to_string(&machine_id_file) {
|
||||
if let Ok(mid) = uuid::Uuid::parse_str(mid.trim()) {
|
||||
return mid;
|
||||
}
|
||||
if let Ok(mid) = std::fs::read_to_string(&machine_id_file)
|
||||
&& let Ok(mid) = uuid::Uuid::parse_str(mid.trim())
|
||||
{
|
||||
return mid;
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use futures::Future;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix::sched::{setns, CloneFlags};
|
||||
use nix::sched::{CloneFlags, setns};
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::fd::AsFd;
|
||||
|
||||
|
||||
@@ -4,40 +4,25 @@
|
||||
//! For example, if task A spawned task B but is doing something else, and task B is waiting for task C to join,
|
||||
//! aborting A will also abort both B and C.
|
||||
|
||||
use derive_more::{Deref, DerefMut, From};
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScopedTask<T> {
|
||||
inner: JoinHandle<T>,
|
||||
}
|
||||
#[derive(Debug, From, Deref, DerefMut)]
|
||||
pub struct ScopedTask<T>(JoinHandle<T>);
|
||||
|
||||
impl<T> Drop for ScopedTask<T> {
|
||||
fn drop(&mut self) {
|
||||
self.inner.abort()
|
||||
self.abort()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for ScopedTask<T> {
|
||||
type Output = <JoinHandle<T> as Future>::Output;
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.inner).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<JoinHandle<T>> for ScopedTask<T> {
|
||||
fn from(inner: JoinHandle<T>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for ScopedTask<T> {
|
||||
type Target = JoinHandle<T>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
Pin::new(&mut self.0).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ pub enum MetricName {
|
||||
TrafficControlBytesRxByInstance,
|
||||
/// Traffic bytes forwarded
|
||||
TrafficBytesForwarded,
|
||||
/// Control-plane traffic bytes forwarded
|
||||
TrafficControlBytesForwarded,
|
||||
/// Traffic bytes sent to self
|
||||
TrafficBytesSelfTx,
|
||||
/// Traffic bytes received from self
|
||||
@@ -71,6 +73,8 @@ pub enum MetricName {
|
||||
TrafficControlPacketsRxByInstance,
|
||||
/// Traffic packets forwarded
|
||||
TrafficPacketsForwarded,
|
||||
/// Control-plane traffic packets forwarded
|
||||
TrafficControlPacketsForwarded,
|
||||
/// Traffic packets sent to self
|
||||
TrafficPacketsSelfTx,
|
||||
/// Traffic packets received from self
|
||||
@@ -117,6 +121,9 @@ impl fmt::Display for MetricName {
|
||||
write!(f, "traffic_control_bytes_rx_by_instance")
|
||||
}
|
||||
MetricName::TrafficBytesForwarded => write!(f, "traffic_bytes_forwarded"),
|
||||
MetricName::TrafficControlBytesForwarded => {
|
||||
write!(f, "traffic_control_bytes_forwarded")
|
||||
}
|
||||
MetricName::TrafficBytesSelfTx => write!(f, "traffic_bytes_self_tx"),
|
||||
MetricName::TrafficBytesSelfRx => write!(f, "traffic_bytes_self_rx"),
|
||||
MetricName::TrafficBytesForeignForwardRx => {
|
||||
@@ -146,6 +153,9 @@ impl fmt::Display for MetricName {
|
||||
write!(f, "traffic_control_packets_rx_by_instance")
|
||||
}
|
||||
MetricName::TrafficPacketsForwarded => write!(f, "traffic_packets_forwarded"),
|
||||
MetricName::TrafficControlPacketsForwarded => {
|
||||
write!(f, "traffic_control_packets_forwarded")
|
||||
}
|
||||
MetricName::TrafficPacketsSelfTx => write!(f, "traffic_packets_self_tx"),
|
||||
MetricName::TrafficPacketsSelfRx => write!(f, "traffic_packets_self_rx"),
|
||||
MetricName::TrafficPacketsForeignForwardRx => {
|
||||
@@ -374,7 +384,9 @@ impl UnsafeCounter {
|
||||
/// that no other thread is accessing this counter simultaneously.
|
||||
pub unsafe fn add(&self, delta: u64) {
|
||||
let ptr = self.value.get();
|
||||
*ptr = (*ptr).saturating_add(delta);
|
||||
unsafe {
|
||||
*ptr = (*ptr).saturating_add(delta);
|
||||
}
|
||||
}
|
||||
|
||||
/// Increment the counter by 1
|
||||
@@ -382,7 +394,9 @@ impl UnsafeCounter {
|
||||
/// This method is unsafe because it uses UnsafeCell. The caller must ensure
|
||||
/// that no other thread is accessing this counter simultaneously.
|
||||
pub unsafe fn inc(&self) {
|
||||
self.add(1);
|
||||
unsafe {
|
||||
self.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current value of the counter
|
||||
@@ -391,7 +405,7 @@ impl UnsafeCounter {
|
||||
/// that no other thread is modifying this counter simultaneously.
|
||||
pub unsafe fn get(&self) -> u64 {
|
||||
let ptr = self.value.get();
|
||||
*ptr
|
||||
unsafe { *ptr }
|
||||
}
|
||||
|
||||
/// Reset the counter to zero
|
||||
@@ -400,7 +414,9 @@ impl UnsafeCounter {
|
||||
/// that no other thread is accessing this counter simultaneously.
|
||||
pub unsafe fn reset(&self) {
|
||||
let ptr = self.value.get();
|
||||
*ptr = 0;
|
||||
unsafe {
|
||||
*ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the counter to a specific value
|
||||
@@ -409,7 +425,9 @@ impl UnsafeCounter {
|
||||
/// that no other thread is accessing this counter simultaneously.
|
||||
pub unsafe fn set(&self, value: u64) {
|
||||
let ptr = self.value.get();
|
||||
*ptr = value;
|
||||
unsafe {
|
||||
*ptr = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +464,9 @@ impl MetricData {
|
||||
/// that no other thread is accessing this timestamp simultaneously.
|
||||
unsafe fn touch(&self) {
|
||||
let ptr = self.last_updated.get();
|
||||
*ptr = Instant::now();
|
||||
unsafe {
|
||||
*ptr = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the last updated timestamp
|
||||
@@ -455,7 +475,7 @@ impl MetricData {
|
||||
/// that no other thread is modifying this timestamp simultaneously.
|
||||
unsafe fn get_last_updated(&self) -> Instant {
|
||||
let ptr = self.last_updated.get();
|
||||
*ptr
|
||||
unsafe { *ptr }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-31
@@ -11,8 +11,8 @@ use crossbeam::atomic::AtomicCell;
|
||||
use rand::seq::IteratorRandom;
|
||||
use socket2::{SockAddr, SockRef};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{lookup_host, UdpSocket};
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
use tokio::net::{UdpSocket, lookup_host};
|
||||
use tokio::sync::{Mutex, broadcast};
|
||||
use tokio::task::JoinSet;
|
||||
use tracing::{Instrument, Level};
|
||||
|
||||
@@ -239,15 +239,11 @@ impl StunClient {
|
||||
let mut mapped_addr = None;
|
||||
for x in msg.attributes() {
|
||||
match x {
|
||||
Attribute::MappedAddress(addr) => {
|
||||
if mapped_addr.is_none() {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
Attribute::MappedAddress(addr) if mapped_addr.is_none() => {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
Attribute::XorMappedAddress(addr) => {
|
||||
if mapped_addr.is_none() {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
Attribute::XorMappedAddress(addr) if mapped_addr.is_none() => {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -259,15 +255,11 @@ impl StunClient {
|
||||
let mut changed_addr = None;
|
||||
for x in msg.attributes() {
|
||||
match x {
|
||||
Attribute::OtherAddress(m) => {
|
||||
if changed_addr.is_none() {
|
||||
let _ = changed_addr.insert(m.address());
|
||||
}
|
||||
Attribute::OtherAddress(m) if changed_addr.is_none() => {
|
||||
let _ = changed_addr.insert(m.address());
|
||||
}
|
||||
Attribute::ChangedAddress(m) => {
|
||||
if changed_addr.is_none() {
|
||||
let _ = changed_addr.insert(m.address());
|
||||
}
|
||||
Attribute::ChangedAddress(m) if changed_addr.is_none() => {
|
||||
let _ = changed_addr.insert(m.address());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -714,15 +706,11 @@ impl TcpStunClient {
|
||||
let mut mapped_addr = None;
|
||||
for x in msg.attributes() {
|
||||
match x {
|
||||
Attribute::MappedAddress(addr) => {
|
||||
if mapped_addr.is_none() {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
Attribute::MappedAddress(addr) if mapped_addr.is_none() => {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
Attribute::XorMappedAddress(addr) => {
|
||||
if mapped_addr.is_none() {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
Attribute::XorMappedAddress(addr) if mapped_addr.is_none() => {
|
||||
let _ = mapped_addr.insert(addr.address());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1340,7 +1328,7 @@ impl StunInfoCollectorTrait for MockStunInfoCollector {
|
||||
mod tests {
|
||||
use crate::{
|
||||
common::scoped_task::ScopedTask,
|
||||
tunnel::{udp::UdpTunnelListener, TunnelListener},
|
||||
tunnel::{TunnelListener, udp::UdpTunnelListener},
|
||||
};
|
||||
use tokio::time::{sleep, timeout};
|
||||
|
||||
@@ -1404,10 +1392,10 @@ mod tests {
|
||||
loop {
|
||||
let ret = detector.detect_nat_type(0).await;
|
||||
println!("{:#?}, {:?}", ret, ret.as_ref().map(|x| x.nat_type()));
|
||||
if let Ok(resp) = ret {
|
||||
if !resp.stun_resps.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(resp) = ret
|
||||
&& !resp.stun_resps.is_empty()
|
||||
{
|
||||
return;
|
||||
}
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use bytecodec::fixnum::{U32beDecoder, U32beEncoder};
|
||||
use stun_codec::net::{socket_addr_xor, SocketAddrDecoder, SocketAddrEncoder};
|
||||
use stun_codec::net::{SocketAddrDecoder, SocketAddrEncoder, socket_addr_xor};
|
||||
|
||||
use stun_codec::rfc5389::attributes::{
|
||||
MappedAddress, Software, XorMappedAddress, XorMappedAddress2,
|
||||
};
|
||||
use stun_codec::rfc5780::attributes::{OtherAddress, ResponseOrigin};
|
||||
use stun_codec::{define_attribute_enums, AttributeType, Message, TransactionId};
|
||||
use stun_codec::{AttributeType, Message, TransactionId, define_attribute_enums};
|
||||
|
||||
use bytecodec::{ByteCount, Decode, Encode, Eos, Result, SizedEncode, TryTaggedDecode};
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ mod tests {
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
/// Test initial state after creation
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RollingConditionBase {
|
||||
@@ -57,17 +58,16 @@ impl Default for RollingConditionBase {
|
||||
impl RollingCondition for RollingConditionBase {
|
||||
fn should_rollover(&mut self, now: &DateTime<Local>, current_filesize: u64) -> bool {
|
||||
let mut rollover = false;
|
||||
if let Some(frequency) = self.frequency_opt.as_ref() {
|
||||
if let Some(last_write) = self.last_write_opt.as_ref() {
|
||||
if frequency.equivalent_datetime(now) != frequency.equivalent_datetime(last_write) {
|
||||
rollover = true;
|
||||
}
|
||||
}
|
||||
if let Some(frequency) = self.frequency_opt.as_ref()
|
||||
&& let Some(last_write) = self.last_write_opt.as_ref()
|
||||
&& frequency.equivalent_datetime(now) != frequency.equivalent_datetime(last_write)
|
||||
{
|
||||
rollover = true;
|
||||
}
|
||||
if let Some(max_size) = self.max_size_opt.as_ref() {
|
||||
if current_filesize >= *max_size {
|
||||
rollover = true;
|
||||
}
|
||||
if let Some(max_size) = self.max_size_opt.as_ref()
|
||||
&& current_filesize >= *max_size
|
||||
{
|
||||
rollover = true;
|
||||
}
|
||||
self.last_write_opt = Some(*now);
|
||||
rollover
|
||||
@@ -136,9 +136,11 @@ impl RollingFileAppenderBaseBuilder {
|
||||
/// Builds a RollingFileAppenderBase instance from the current settings.
|
||||
///
|
||||
/// Returns an error if the filename is empty.
|
||||
pub fn build(self) -> Result<RollingFileAppenderBase, &'static str> {
|
||||
pub fn build(self) -> anyhow::Result<RollingFileAppenderBase> {
|
||||
if self.filename.is_empty() {
|
||||
return Err("A filename is required to be set and can not be blank");
|
||||
return Err(anyhow!(
|
||||
"A filename is required to be set and can not be blank"
|
||||
));
|
||||
}
|
||||
Ok(RollingFileAppenderBase {
|
||||
condition: self.condition,
|
||||
|
||||
@@ -81,11 +81,7 @@ where
|
||||
/// Determines the final filename, where n==0 indicates the current file
|
||||
fn filename_for(&self, n: usize) -> String {
|
||||
let f = self.filename.clone();
|
||||
if n > 0 {
|
||||
format!("{}.{}", f, n)
|
||||
} else {
|
||||
f
|
||||
}
|
||||
if n > 0 { format!("{}.{}", f, n) } else { f }
|
||||
}
|
||||
|
||||
/// Rotates old files to make room for a new one.
|
||||
@@ -145,14 +141,14 @@ where
|
||||
|
||||
/// Writes data using the given datetime to calculate the rolling condition
|
||||
pub fn write_with_datetime(&mut self, buf: &[u8], now: &DateTime<Local>) -> io::Result<usize> {
|
||||
if self.condition.should_rollover(now, self.current_filesize) {
|
||||
if let Err(e) = self.rollover() {
|
||||
// If we can't rollover, just try to continue writing anyway
|
||||
// (better than missing data).
|
||||
// This will likely used to implement logging, so
|
||||
// avoid using log::warn and log to stderr directly
|
||||
eprintln!("WARNING: Failed to rotate logfile {}: {}", self.filename, e);
|
||||
}
|
||||
if self.condition.should_rollover(now, self.current_filesize)
|
||||
&& let Err(e) = self.rollover()
|
||||
{
|
||||
// If we can't rollover, just try to continue writing anyway
|
||||
// (better than missing data).
|
||||
// This will likely used to implement logging, so
|
||||
// avoid using log::warn and log to stderr directly
|
||||
eprintln!("WARNING: Failed to rotate logfile {}: {}", self.filename, e);
|
||||
}
|
||||
self.open_writer_if_needed()?;
|
||||
if let Some(writer) = self.writer_opt.as_mut() {
|
||||
|
||||
@@ -5,16 +5,16 @@ use std::{
|
||||
net::{IpAddr, Ipv6Addr, SocketAddr},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
dns::socket_addrs, error::Error, global_ctx::ArcGlobalCtx, stun::StunInfoCollectorTrait,
|
||||
PeerId,
|
||||
PeerId, dns::socket_addrs, error::Error, global_ctx::ArcGlobalCtx,
|
||||
stun::StunInfoCollectorTrait,
|
||||
},
|
||||
connector::udp_hole_punch::handle_rpc_result,
|
||||
peers::{
|
||||
@@ -31,7 +31,7 @@ use crate::{
|
||||
},
|
||||
rpc_types::controller::BaseController,
|
||||
},
|
||||
tunnel::{matches_protocol, udp::UdpTunnelConnector, IpVersion},
|
||||
tunnel::{IpVersion, matches_protocol, udp::UdpTunnelConnector},
|
||||
use_global_var,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ use super::{
|
||||
create_connector_by_url, should_background_p2p_with_peer, should_try_p2p_with_peer,
|
||||
udp_hole_punch,
|
||||
};
|
||||
use crate::tunnel::{matches_scheme, FromUrl, IpScheme, TunnelScheme};
|
||||
use crate::tunnel::{FromUrl, IpScheme, TunnelScheme, matches_scheme};
|
||||
use anyhow::Context;
|
||||
use rand::Rng;
|
||||
use socket2::Protocol;
|
||||
@@ -769,12 +769,9 @@ mod tests {
|
||||
|
||||
let port = if proto == "wg" { 11040 } else { 11041 };
|
||||
if !ipv6 {
|
||||
p_c.get_global_ctx().config.set_listeners(vec![format!(
|
||||
"{}://0.0.0.0:{}",
|
||||
proto, port
|
||||
)
|
||||
.parse()
|
||||
.unwrap()]);
|
||||
p_c.get_global_ctx().config.set_listeners(vec![
|
||||
format!("{}://0.0.0.0:{}", proto, port).parse().unwrap(),
|
||||
]);
|
||||
} else {
|
||||
p_c.get_global_ctx()
|
||||
.config
|
||||
@@ -814,11 +811,12 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(data
|
||||
.dst_listener_blacklist
|
||||
.contains(&DstListenerUrlBlackListItem(
|
||||
1,
|
||||
"tcp://127.0.0.1:10222".parse().unwrap()
|
||||
)));
|
||||
assert!(
|
||||
data.dst_listener_blacklist
|
||||
.contains(&DstListenerUrlBlackListItem(
|
||||
1,
|
||||
"tcp://127.0.0.1:10222".parse().unwrap()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{net::SocketAddr, sync::Arc};
|
||||
use super::{create_connector_by_url, http_connector::TunnelWithInfo};
|
||||
use crate::{
|
||||
common::{
|
||||
dns::{resolve_txt_record, RESOLVER},
|
||||
dns::{RESOLVER, resolve_txt_record},
|
||||
error::Error,
|
||||
global_ctx::ArcGlobalCtx,
|
||||
log,
|
||||
@@ -14,8 +14,7 @@ use crate::{
|
||||
use anyhow::Context;
|
||||
use dashmap::DashSet;
|
||||
use hickory_resolver::proto::rr::rdata::SRV;
|
||||
use itertools::Itertools;
|
||||
use rand::{seq::SliceRandom, Rng as _};
|
||||
use rand::{Rng as _, seq::SliceRandom};
|
||||
use strum::VariantArray;
|
||||
|
||||
fn weighted_choice<T>(options: &[(T, u64)]) -> Option<&T> {
|
||||
@@ -117,7 +116,7 @@ impl DnsTunnelConnector {
|
||||
let srv_domains = IpScheme::VARIANTS
|
||||
.iter()
|
||||
.map(|s| (s, format!("_easytier._{}.{}", s, domain_name)))
|
||||
.collect_vec();
|
||||
.collect::<Vec<_>>();
|
||||
tracing::info!("build srv_domains: {:?}", srv_domains);
|
||||
let responses = Arc::new(DashSet::new());
|
||||
let srv_lookup_tasks = srv_domains
|
||||
|
||||
@@ -10,9 +10,9 @@ use rand::seq::SliceRandom as _;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
VERSION,
|
||||
common::{error::Error, global_ctx::ArcGlobalCtx},
|
||||
tunnel::{IpVersion, Tunnel, TunnelConnector, TunnelError, ZCPacketSink, ZCPacketStream},
|
||||
VERSION,
|
||||
};
|
||||
|
||||
use crate::proto::common::TunnelInfo;
|
||||
@@ -257,7 +257,7 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
common::global_ctx::tests::get_mock_global_ctx_with_network,
|
||||
tunnel::{tcp::TcpTunnelListener, TunnelConnector, TunnelListener},
|
||||
tunnel::{TunnelConnector, TunnelListener, tcp::TcpTunnelListener},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -7,7 +7,7 @@ use dashmap::DashSet;
|
||||
use tokio::{sync::mpsc, task::JoinSet, time::timeout};
|
||||
|
||||
use crate::{
|
||||
common::{dns::socket_addrs, join_joinset_background, PeerId},
|
||||
common::{PeerId, dns::socket_addrs, join_joinset_background},
|
||||
peers::peer_conn::PeerConnId,
|
||||
proto::{
|
||||
api::instance::{
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::{
|
||||
connector::dns_connector::DnsTunnelConnector,
|
||||
proto::common::PeerFeatureFlag,
|
||||
tunnel::{
|
||||
self, ring::RingTunnelConnector, tcp::TcpTunnelConnector, udp::UdpTunnelConnector, FromUrl,
|
||||
IpScheme, IpVersion, TunnelConnector, TunnelError, TunnelScheme,
|
||||
self, FromUrl, IpScheme, IpVersion, TunnelConnector, TunnelError, TunnelScheme,
|
||||
ring::RingTunnelConnector, tcp::TcpTunnelConnector, udp::UdpTunnelConnector,
|
||||
},
|
||||
utils::BoxExt,
|
||||
};
|
||||
@@ -105,7 +105,9 @@ pub async fn create_connector_by_url(
|
||||
IpScheme::Tcp => TcpTunnelConnector::new(url).boxed(),
|
||||
IpScheme::Udp => UdpTunnelConnector::new(url).boxed(),
|
||||
#[cfg(feature = "quic")]
|
||||
IpScheme::Quic => tunnel::quic::QuicTunnelConnector::new(url).boxed(),
|
||||
IpScheme::Quic => {
|
||||
tunnel::quic::QuicTunnelConnector::new(url, global_ctx.clone()).boxed()
|
||||
}
|
||||
#[cfg(feature = "wireguard")]
|
||||
IpScheme::Wg => {
|
||||
use crate::tunnel::wireguard::{WgConfig, WgTunnelConnector};
|
||||
|
||||
@@ -9,7 +9,7 @@ use rand::Rng as _;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::{
|
||||
common::{join_joinset_background, stun::StunInfoCollectorTrait, PeerId},
|
||||
common::{PeerId, join_joinset_background, stun::StunInfoCollectorTrait},
|
||||
connector::udp_hole_punch::BackOff,
|
||||
peers::{
|
||||
peer_manager::PeerManager,
|
||||
@@ -24,8 +24,8 @@ use crate::{
|
||||
rpc_types::{self, controller::BaseController},
|
||||
},
|
||||
tunnel::{
|
||||
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
||||
TunnelConnector as _, TunnelListener as _,
|
||||
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -719,18 +719,20 @@ mod tests {
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
assert!(p_a
|
||||
.get_peer_map()
|
||||
.list_peer_conns(p_c.my_peer_id())
|
||||
.await
|
||||
.map(|c| c.is_empty())
|
||||
.unwrap_or(true));
|
||||
assert!(p_c
|
||||
.get_peer_map()
|
||||
.list_peer_conns(p_a.my_peer_id())
|
||||
.await
|
||||
.map(|c| c.is_empty())
|
||||
.unwrap_or(true));
|
||||
assert!(
|
||||
p_a.get_peer_map()
|
||||
.list_peer_conns(p_c.my_peer_id())
|
||||
.await
|
||||
.map(|c| c.is_empty())
|
||||
.unwrap_or(true)
|
||||
);
|
||||
assert!(
|
||||
p_c.get_peer_map()
|
||||
.list_peer_conns(p_a.my_peer_id())
|
||||
.await
|
||||
.map(|c| c.is_empty())
|
||||
.unwrap_or(true)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -751,14 +753,18 @@ mod tests {
|
||||
connect_peer_manager(p_b.clone(), p_c.clone()).await;
|
||||
wait_route_appear(p_a.clone(), p_c.clone()).await.unwrap();
|
||||
|
||||
assert!(!collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id()));
|
||||
assert!(
|
||||
!collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id())
|
||||
);
|
||||
|
||||
p_a.mark_recent_traffic(p_c.my_peer_id());
|
||||
|
||||
assert!(collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id()));
|
||||
assert!(
|
||||
collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ use anyhow::Context;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
common::{scoped_task::ScopedTask, stun::StunInfoCollectorTrait, PeerId},
|
||||
common::{PeerId, scoped_task::ScopedTask, stun::StunInfoCollectorTrait},
|
||||
connector::udp_hole_punch::common::{
|
||||
try_connect_with_socket, UdpHolePunchListener, HOLE_PUNCH_PACKET_BODY_LEN,
|
||||
HOLE_PUNCH_PACKET_BODY_LEN, UdpHolePunchListener, try_connect_with_socket,
|
||||
},
|
||||
connector::udp_hole_punch::handle_rpc_result,
|
||||
peers::peer_manager::PeerManager,
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
},
|
||||
rpc_types::{self, controller::BaseController},
|
||||
},
|
||||
tunnel::{udp::new_hole_punch_packet, Tunnel},
|
||||
tunnel::{Tunnel, udp::new_hole_punch_packet},
|
||||
};
|
||||
|
||||
use super::common::{PunchHoleServerCommon, UdpNatType, UdpSocketArray};
|
||||
@@ -340,7 +340,7 @@ impl PunchBothEasySymHoleClient {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::{
|
||||
sync::{atomic::AtomicU32, Arc},
|
||||
sync::{Arc, atomic::AtomicU32},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -349,7 +349,7 @@ pub mod tests {
|
||||
use crate::connector::udp_hole_punch::RUN_TESTING;
|
||||
use crate::{
|
||||
connector::udp_hole_punch::{
|
||||
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector,
|
||||
UdpHolePunchConnector, tests::create_mock_peer_manager_with_mock_stun,
|
||||
},
|
||||
peers::tests::{connect_peer_manager, wait_route_appear},
|
||||
proto::common::NatType,
|
||||
|
||||
@@ -8,21 +8,21 @@ use crossbeam::atomic::AtomicCell;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use rand::seq::SliceRandom as _;
|
||||
use tokio::{net::UdpSocket, sync::Mutex, task::JoinSet};
|
||||
use tracing::{instrument, Instrument, Level};
|
||||
use tracing::{Instrument, Level, instrument};
|
||||
use zerocopy::FromBytes as _;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
error::Error, global_ctx::ArcGlobalCtx, join_joinset_background, netns::NetNS,
|
||||
stun::StunInfoCollectorTrait as _, PeerId,
|
||||
PeerId, error::Error, global_ctx::ArcGlobalCtx, join_joinset_background, netns::NetNS,
|
||||
stun::StunInfoCollectorTrait as _,
|
||||
},
|
||||
defer,
|
||||
peers::peer_manager::PeerManager,
|
||||
proto::common::NatType,
|
||||
tunnel::{
|
||||
packet_def::{UDPTunnelHeader, UdpPacketType, UDP_TUNNEL_HEADER_SIZE},
|
||||
udp::{new_hole_punch_packet, UdpTunnelConnector, UdpTunnelListener},
|
||||
Tunnel, TunnelConnCounter, TunnelListener as _,
|
||||
packet_def::{UDP_TUNNEL_HEADER_SIZE, UDPTunnelHeader, UdpPacketType},
|
||||
udp::{UdpTunnelConnector, UdpTunnelListener, new_hole_punch_packet},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ use anyhow::Context;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use crate::{
|
||||
common::{scoped_task::ScopedTask, stun::StunInfoCollectorTrait, PeerId},
|
||||
common::{PeerId, scoped_task::ScopedTask, stun::StunInfoCollectorTrait},
|
||||
connector::udp_hole_punch::common::{
|
||||
try_connect_with_socket, UdpSocketArray, HOLE_PUNCH_PACKET_BODY_LEN,
|
||||
HOLE_PUNCH_PACKET_BODY_LEN, UdpSocketArray, try_connect_with_socket,
|
||||
},
|
||||
connector::udp_hole_punch::handle_rpc_result,
|
||||
peers::peer_manager::PeerManager,
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
},
|
||||
rpc_types::{self, controller::BaseController},
|
||||
},
|
||||
tunnel::{udp::new_hole_punch_packet, Tunnel},
|
||||
tunnel::{Tunnel, udp::new_hole_punch_packet},
|
||||
};
|
||||
|
||||
use super::common::PunchHoleServerCommon;
|
||||
@@ -120,7 +120,7 @@ impl PunchConeHoleClient {
|
||||
|
||||
let local_addr = local_socket
|
||||
.local_addr()
|
||||
.with_context(|| anyhow::anyhow!("failed to get local port from udp array"))?;
|
||||
.with_context(|| "failed to get local port from udp array")?;
|
||||
let local_port = local_addr.port();
|
||||
|
||||
drop(local_socket);
|
||||
@@ -249,7 +249,7 @@ pub mod tests {
|
||||
|
||||
use crate::{
|
||||
connector::udp_hole_punch::{
|
||||
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector,
|
||||
UdpHolePunchConnector, tests::create_mock_peer_manager_with_mock_stun,
|
||||
},
|
||||
peers::tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost},
|
||||
proto::common::NatType,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ use sym_to_cone::{PunchSymToConeHoleClient, PunchSymToConeHoleServer};
|
||||
use tokio::{sync::Mutex, task::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
common::{stun::StunInfoCollectorTrait, PeerId},
|
||||
common::{PeerId, stun::StunInfoCollectorTrait},
|
||||
peers::{
|
||||
peer_manager::PeerManager,
|
||||
peer_task::{PeerTaskLauncher, PeerTaskManager},
|
||||
@@ -601,7 +601,7 @@ pub mod tests {
|
||||
use crate::proto::common::NatType;
|
||||
use crate::tunnel::common::tests::wait_for_condition;
|
||||
|
||||
use super::{UdpHolePunchConnector, UdpHolePunchPeerTaskLauncher, RUN_TESTING};
|
||||
use super::{RUN_TESTING, UdpHolePunchConnector, UdpHolePunchPeerTaskLauncher};
|
||||
|
||||
pub fn replace_stun_info_collector(peer_mgr: Arc<PeerManager>, udp_nat_type: NatType) {
|
||||
let collector = Box::new(MockStunInfoCollector { udp_nat_type });
|
||||
@@ -676,14 +676,18 @@ pub mod tests {
|
||||
connect_peer_manager(p_b.clone(), p_c.clone()).await;
|
||||
wait_route_appear(p_a.clone(), p_c.clone()).await.unwrap();
|
||||
|
||||
assert!(!collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id()));
|
||||
assert!(
|
||||
!collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id())
|
||||
);
|
||||
|
||||
p_a.mark_recent_traffic(p_c.my_peer_id());
|
||||
|
||||
assert!(collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id()));
|
||||
assert!(
|
||||
collect_lazy_punch_peers(p_a.clone())
|
||||
.await
|
||||
.contains(&p_c.my_peer_id())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@ use std::{
|
||||
net::Ipv4Addr,
|
||||
ops::{Div, Mul},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use rand::{Rng, seq::SliceRandom};
|
||||
use tokio::{net::UdpSocket, sync::RwLock};
|
||||
use tracing::Level;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
global_ctx::ArcGlobalCtx, scoped_task::ScopedTask, stun::StunInfoCollectorTrait, PeerId,
|
||||
PeerId, global_ctx::ArcGlobalCtx, scoped_task::ScopedTask, stun::StunInfoCollectorTrait,
|
||||
},
|
||||
connector::udp_hole_punch::{
|
||||
common::{
|
||||
send_symmetric_hole_punch_packet, try_connect_with_socket, HOLE_PUNCH_PACKET_BODY_LEN,
|
||||
HOLE_PUNCH_PACKET_BODY_LEN, send_symmetric_hole_punch_packet, try_connect_with_socket,
|
||||
},
|
||||
handle_rpc_result,
|
||||
},
|
||||
@@ -33,7 +33,7 @@ use crate::{
|
||||
},
|
||||
rpc_types::{self, controller::BaseController},
|
||||
},
|
||||
tunnel::{udp::new_hole_punch_packet, Tunnel},
|
||||
tunnel::{Tunnel, udp::new_hole_punch_packet},
|
||||
};
|
||||
|
||||
use super::common::{PunchHoleServerCommon, UdpNatType, UdpSocketArray};
|
||||
@@ -445,16 +445,15 @@ impl PunchSymToConeHoleClient {
|
||||
))?;
|
||||
|
||||
// try direct connect first
|
||||
if self.try_direct_connect.load(Ordering::Relaxed) {
|
||||
if let Ok(tunnel) = try_connect_with_socket(
|
||||
if self.try_direct_connect.load(Ordering::Relaxed)
|
||||
&& let Ok(tunnel) = try_connect_with_socket(
|
||||
global_ctx.clone(),
|
||||
Arc::new(UdpSocket::bind("0.0.0.0:0").await?),
|
||||
remote_mapped_addr.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Ok(Some(tunnel));
|
||||
}
|
||||
{
|
||||
return Ok(Some(tunnel));
|
||||
}
|
||||
|
||||
let stun_info = global_ctx.get_stun_info_collector().get_stun_info();
|
||||
@@ -467,7 +466,7 @@ impl PunchSymToConeHoleClient {
|
||||
return Err(anyhow::anyhow!("failed to get public ips"));
|
||||
}
|
||||
|
||||
let tid = rand::thread_rng().gen();
|
||||
let tid = rand::thread_rng().r#gen();
|
||||
let packet = new_hole_punch_packet(tid, HOLE_PUNCH_PACKET_BODY_LEN).into_bytes();
|
||||
udp_array.add_intreast_tid(tid);
|
||||
defer! { udp_array.remove_intreast_tid(tid);}
|
||||
@@ -544,7 +543,7 @@ impl PunchSymToConeHoleClient {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::{
|
||||
sync::{atomic::AtomicU32, Arc},
|
||||
sync::{Arc, atomic::AtomicU32},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -552,7 +551,7 @@ pub mod tests {
|
||||
|
||||
use crate::{
|
||||
connector::udp_hole_punch::{
|
||||
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector, RUN_TESTING,
|
||||
RUN_TESTING, UdpHolePunchConnector, tests::create_mock_peer_manager_with_mock_stun,
|
||||
},
|
||||
peers::tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost},
|
||||
proto::common::NatType,
|
||||
@@ -617,7 +616,7 @@ pub mod tests {
|
||||
.await
|
||||
.is_ok()
|
||||
},
|
||||
Duration::from_secs(30),
|
||||
Duration::from_secs(60),
|
||||
)
|
||||
.await;
|
||||
println!("{:?}", p_a.list_routes().await);
|
||||
|
||||
+102
-63
@@ -4,15 +4,16 @@ use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
process::ExitCode,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ShellType,
|
||||
common::{
|
||||
config::{
|
||||
load_config_from_file, process_secure_mode_cfg, ConfigFileControl, ConfigLoader,
|
||||
ConsoleLoggerConfig, EncryptionAlgorithm, FileLoggerConfig, LoggingConfigLoader,
|
||||
NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader, VpnPortalConfig,
|
||||
ConfigFileControl, ConfigLoader, ConsoleLoggerConfig, EncryptionAlgorithm,
|
||||
FileLoggerConfig, LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig,
|
||||
TomlConfigLoader, VpnPortalConfig, load_config_from_file, process_secure_mode_cfg,
|
||||
},
|
||||
constants::EASYTIER_VERSION,
|
||||
log,
|
||||
@@ -22,8 +23,8 @@ use crate::{
|
||||
launcher::add_proxy_network_to_config,
|
||||
proto::common::{CompressionAlgoPb, SecureModeConfig},
|
||||
rpc_service::ApiRpcServer,
|
||||
utils::setup_panic_handler,
|
||||
web_client, ShellType,
|
||||
utils::panic::setup_panic_handler,
|
||||
web_client,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use cidr::IpCidr;
|
||||
@@ -34,7 +35,7 @@ use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::tunnel::IpScheme;
|
||||
#[cfg(feature = "jemalloc-prof")]
|
||||
use jemalloc_ctl::{epoch, stats, Access as _, AsName as _};
|
||||
use jemalloc_ctl::{Access as _, AsName as _, epoch, stats};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
windows_service::define_windows_service!(ffi_service_main, win_service_main);
|
||||
@@ -739,61 +740,81 @@ impl Cli {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let origin_listeners = listeners;
|
||||
let mut listeners: Vec<String> = Vec::new();
|
||||
if origin_listeners.len() == 1 {
|
||||
if let Ok(port) = origin_listeners[0].parse::<u16>() {
|
||||
for proto in IpScheme::VARIANTS {
|
||||
listeners.push(format!(
|
||||
if listeners.len() == 1
|
||||
&& let Ok(port) = listeners[0].parse::<u16>()
|
||||
{
|
||||
let listeners = IpScheme::VARIANTS
|
||||
.iter()
|
||||
.map(|proto| {
|
||||
format!(
|
||||
"{}://0.0.0.0:{}",
|
||||
proto,
|
||||
port + proto.port_offset()
|
||||
));
|
||||
}
|
||||
return Ok(listeners);
|
||||
}
|
||||
if port == 0 {
|
||||
0
|
||||
} else {
|
||||
port + proto.port_offset()
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
return Ok(listeners);
|
||||
}
|
||||
|
||||
for l in &origin_listeners {
|
||||
let proto_port: Vec<&str> = l.split(':').collect();
|
||||
if proto_port.len() > 2 {
|
||||
if let Ok(url) = l.parse::<url::Url>() {
|
||||
listeners.push(url.to_string());
|
||||
} else {
|
||||
panic!("failed to parse listener: {}", l);
|
||||
}
|
||||
} else {
|
||||
let scheme: IpScheme = proto_port[0].parse()?;
|
||||
listeners
|
||||
.into_iter()
|
||||
.map(|l| {
|
||||
let l = l
|
||||
.parse::<url::Url>()
|
||||
.or_else(|_| url::Url::parse(&format!("{}:", l)))?;
|
||||
|
||||
let port = if proto_port.len() == 2 {
|
||||
proto_port[1].parse::<u16>().unwrap()
|
||||
} else {
|
||||
11010 + scheme.port_offset()
|
||||
if l.has_authority() {
|
||||
return Ok(l.to_string());
|
||||
}
|
||||
|
||||
let scheme: IpScheme = l.scheme().parse()?;
|
||||
let port = {
|
||||
let port = l.path();
|
||||
if port.is_empty() {
|
||||
11010 + scheme.port_offset()
|
||||
} else {
|
||||
port.parse::<u16>()
|
||||
.with_context(|| format!("invalid port: {}", port))?
|
||||
}
|
||||
};
|
||||
|
||||
listeners.push(format!("{}://0.0.0.0:{}", scheme, port));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(listeners)
|
||||
Ok(format!("{}://0.0.0.0:{}", scheme, port))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkOptions {
|
||||
fn can_merge(&self, cfg: &TomlConfigLoader, config_file_count: usize) -> bool {
|
||||
fn can_merge(
|
||||
&self,
|
||||
cfg: &TomlConfigLoader,
|
||||
source: ConfigSource,
|
||||
explicit_config_file_count: usize,
|
||||
config_dir_file_count: usize,
|
||||
) -> bool {
|
||||
if (*self) == NetworkOptions::default() {
|
||||
return false;
|
||||
}
|
||||
if config_file_count == 1 {
|
||||
|
||||
if source == ConfigSource::CliConfigFile
|
||||
&& explicit_config_file_count == 1
|
||||
&& config_dir_file_count == 0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(network_name) = &self.network_name else {
|
||||
return false;
|
||||
};
|
||||
if cfg.get_network_identity().network_name == *network_name {
|
||||
return true;
|
||||
|
||||
if source == ConfigSource::ConfigDir {
|
||||
return cfg.get_network_identity().network_name == *network_name;
|
||||
}
|
||||
false
|
||||
|
||||
cfg.get_network_identity().network_name == *network_name
|
||||
}
|
||||
|
||||
fn merge_into(&self, cfg: &TomlConfigLoader) -> anyhow::Result<()> {
|
||||
@@ -847,7 +868,8 @@ impl NetworkOptions {
|
||||
|
||||
if self.no_listener || !self.listeners.is_empty() {
|
||||
cfg.set_listeners(
|
||||
Cli::parse_listeners(self.no_listener, self.listeners.clone())?
|
||||
Cli::parse_listeners(self.no_listener, self.listeners.clone())
|
||||
.with_context(|| format!("failed to parse listeners: {:?}", self.listeners))?
|
||||
.into_iter()
|
||||
.map(|s| s.parse().unwrap())
|
||||
.collect(),
|
||||
@@ -994,15 +1016,15 @@ impl NetworkOptions {
|
||||
local_public_key: None,
|
||||
};
|
||||
cfg.set_secure_mode(Some(process_secure_mode_cfg(c)?));
|
||||
} else if let Some(secure_mode) = self.secure_mode {
|
||||
if secure_mode {
|
||||
let c = SecureModeConfig {
|
||||
enabled: secure_mode,
|
||||
local_private_key: self.local_private_key.clone(),
|
||||
local_public_key: self.local_public_key.clone(),
|
||||
};
|
||||
cfg.set_secure_mode(Some(process_secure_mode_cfg(c)?));
|
||||
}
|
||||
} else if let Some(secure_mode) = self.secure_mode
|
||||
&& secure_mode
|
||||
{
|
||||
let c = SecureModeConfig {
|
||||
enabled: secure_mode,
|
||||
local_private_key: self.local_private_key.clone(),
|
||||
local_public_key: self.local_public_key.clone(),
|
||||
};
|
||||
cfg.set_secure_mode(Some(process_secure_mode_cfg(c)?));
|
||||
}
|
||||
|
||||
let mut f = cfg.get_flags();
|
||||
@@ -1113,6 +1135,12 @@ impl NetworkOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ConfigSource {
|
||||
CliConfigFile,
|
||||
ConfigDir,
|
||||
}
|
||||
|
||||
impl LoggingConfigLoader for &LoggingOptions {
|
||||
fn get_console_logger_config(&self) -> ConsoleLoggerConfig {
|
||||
ConsoleLoggerConfig {
|
||||
@@ -1134,7 +1162,7 @@ impl LoggingConfigLoader for &LoggingOptions {
|
||||
#[cfg(target_os = "windows")]
|
||||
fn win_service_set_work_dir(service_name: &std::ffi::OsString) -> anyhow::Result<()> {
|
||||
use crate::common::constants::WIN_SERVICE_WORK_DIR_REG_KEY;
|
||||
use winreg::{enums::*, RegKey};
|
||||
use winreg::{RegKey, enums::*};
|
||||
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let key = hklm.open_subkey_with_flags(WIN_SERVICE_WORK_DIR_REG_KEY, KEY_READ)?;
|
||||
@@ -1185,9 +1213,9 @@ fn win_service_event_loop(
|
||||
status_handle.set_service_status(normal_status).unwrap();
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
Err(error) => {
|
||||
status_handle.set_service_status(error_status).unwrap();
|
||||
log::error!("{}", e);
|
||||
log::error!(?error);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1295,8 +1323,13 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
let explicit_config_file_count = cli.config_file.as_ref().map_or(0, |files| files.len());
|
||||
let mut config_dir_file_count = 0;
|
||||
let mut config_files = if let Some(v) = cli.config_file {
|
||||
v.clone()
|
||||
v.iter()
|
||||
.cloned()
|
||||
.map(|path| (path, ConfigSource::CliConfigFile))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
@@ -1317,7 +1350,8 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
if ext != "toml" {
|
||||
continue;
|
||||
}
|
||||
config_files.push(path);
|
||||
config_dir_file_count += 1;
|
||||
config_files.push((path, ConfigSource::ConfigDir));
|
||||
}
|
||||
}
|
||||
let config_file_count = config_files.len();
|
||||
@@ -1330,7 +1364,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
cli.network_options.network_name.is_some()
|
||||
}
|
||||
};
|
||||
for config_file in config_files {
|
||||
for (config_file, source) in config_files {
|
||||
let (cfg, mut control) = load_config_from_file(
|
||||
&config_file,
|
||||
cli.config_dir.as_ref(),
|
||||
@@ -1338,7 +1372,12 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
if cli.network_options.can_merge(&cfg, config_file_count) {
|
||||
if cli.network_options.can_merge(
|
||||
&cfg,
|
||||
source,
|
||||
explicit_config_file_count,
|
||||
config_dir_file_count,
|
||||
) {
|
||||
cli.network_options
|
||||
.merge_into(&cfg)
|
||||
.with_context(|| format!("failed to merge config from cli: {:?}", config_file))?;
|
||||
@@ -1501,8 +1540,8 @@ pub async fn main() -> ExitCode {
|
||||
|
||||
// Verify configurations
|
||||
if cli.check_config {
|
||||
if let Err(e) = validate_config(&cli).await {
|
||||
log::error!("Config validation failed: {:?}", e);
|
||||
if let Err(error) = validate_config(&cli).await {
|
||||
log::error!(?error, "Config validation failed");
|
||||
return ExitCode::FAILURE;
|
||||
} else {
|
||||
return ExitCode::SUCCESS;
|
||||
@@ -1512,7 +1551,7 @@ pub async fn main() -> ExitCode {
|
||||
let mut ret_code = 0;
|
||||
|
||||
if let Err(error) = run_main(cli).await {
|
||||
log::error!(%error);
|
||||
log::error!(?error);
|
||||
ret_code = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine as _;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use cidr::Ipv4Inet;
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
use dashmap::DashMap;
|
||||
@@ -21,8 +21,8 @@ use easytier::ShellType;
|
||||
use humansize::format_size;
|
||||
use rust_i18n::t;
|
||||
use service_manager::*;
|
||||
use tabled::settings::{location::ByColumnName, object::Columns, Disable, Modify, Style, Width};
|
||||
use terminal_size::{terminal_size, Width as TerminalWidth};
|
||||
use tabled::settings::{Disable, Modify, Style, Width, location::ByColumnName, object::Columns};
|
||||
use terminal_size::{Width as TerminalWidth, terminal_size};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use easytier::service_manager::{Service, ServiceInstallOptions};
|
||||
@@ -42,9 +42,7 @@ use easytier::{
|
||||
InstanceConfigPatch, PatchConfigRequest, PortForwardPatch, StringPatch, UrlPatch,
|
||||
},
|
||||
instance::{
|
||||
instance_identifier::{InstanceSelector, Selector},
|
||||
list_global_foreign_network_response, list_peer_route_pair, AclManageRpc,
|
||||
AclManageRpcClientFactory, Connector, ConnectorManageRpc,
|
||||
AclManageRpc, AclManageRpcClientFactory, Connector, ConnectorManageRpc,
|
||||
ConnectorManageRpcClientFactory, CredentialManageRpc,
|
||||
CredentialManageRpcClientFactory, DumpRouteRequest, ForeignNetworkEntryPb,
|
||||
GenerateCredentialRequest, GetAclStatsRequest, GetPrometheusStatsRequest,
|
||||
@@ -60,6 +58,8 @@ use easytier::{
|
||||
StatsRpc, StatsRpcClientFactory, TcpProxyEntryState, TcpProxyEntryTransportType,
|
||||
TcpProxyRpc, TcpProxyRpcClientFactory, TrustedKeySourcePb, VpnPortalInfo,
|
||||
VpnPortalRpc, VpnPortalRpcClientFactory,
|
||||
instance_identifier::{InstanceSelector, Selector},
|
||||
list_global_foreign_network_response, list_peer_route_pair,
|
||||
},
|
||||
logger::{
|
||||
GetLoggerConfigRequest, LogLevel, LoggerRpc, LoggerRpcClientFactory,
|
||||
@@ -75,8 +75,8 @@ use easytier::{
|
||||
rpc_impl::standalone::StandAloneClient,
|
||||
rpc_types::controller::BaseController,
|
||||
},
|
||||
tunnel::{tcp::TcpTunnelConnector, TunnelScheme},
|
||||
utils::{cost_to_str, PeerRoutePair},
|
||||
tunnel::{TunnelScheme, tcp::TcpTunnelConnector},
|
||||
utils::{PeerRoutePair, string::cost_to_str},
|
||||
};
|
||||
|
||||
rust_i18n::i18n!("locales", fallback = "en");
|
||||
@@ -1972,7 +1972,12 @@ impl<'a> CommandHandler<'a> {
|
||||
"info" => LogLevel::Info,
|
||||
"debug" => LogLevel::Debug,
|
||||
"trace" => LogLevel::Trace,
|
||||
_ => return Err(anyhow::anyhow!("Invalid log level: {}. Valid levels are: disabled, error, warning, info, debug, trace", level)),
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid log level: {}. Valid levels are: disabled, error, warning, info, debug, trace",
|
||||
level
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let client = self.get_logger_client().await?;
|
||||
@@ -2497,10 +2502,9 @@ fn header_indices(headers: &[String], names: &[&str]) -> Vec<usize> {
|
||||
if let Some(index) = headers
|
||||
.iter()
|
||||
.position(|header| header.eq_ignore_ascii_case(name))
|
||||
&& !indices.contains(&index)
|
||||
{
|
||||
if !indices.contains(&index) {
|
||||
indices.push(index);
|
||||
}
|
||||
indices.push(index);
|
||||
}
|
||||
}
|
||||
indices
|
||||
|
||||
@@ -13,12 +13,12 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
#[cfg(feature = "jemalloc-prof")]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[export_name = "malloc_conf"]
|
||||
#[unsafe(export_name = "malloc_conf")]
|
||||
pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19,retain:false\0";
|
||||
|
||||
#[cfg(not(feature = "jemalloc-prof"))]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[export_name = "malloc_conf"]
|
||||
#[unsafe(export_name = "malloc_conf")]
|
||||
pub static malloc_conf: &[u8] = b"retain:false\0";
|
||||
|
||||
rust_i18n::i18n!("locales", fallback = "en");
|
||||
|
||||
@@ -46,9 +46,9 @@ use anyhow::Context;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use util::target_addr::read_address;
|
||||
use util::target_addr::TargetAddr;
|
||||
use util::target_addr::ToTargetAddr;
|
||||
use util::target_addr::read_address;
|
||||
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::Socks5Command;
|
||||
use super::new_udp_header;
|
||||
use super::parse_udp_request;
|
||||
use super::read_exact;
|
||||
use super::util::stream::tcp_connect_with_timeout;
|
||||
use super::util::target_addr::{read_address, TargetAddr};
|
||||
use super::Socks5Command;
|
||||
use super::{consts, AuthenticationMethod, ReplyError, Result, SocksError};
|
||||
use super::util::target_addr::{TargetAddr, read_address};
|
||||
use super::{AuthenticationMethod, ReplyError, Result, SocksError, consts};
|
||||
use anyhow::Context;
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::gateway::fast_socks5::SocksError;
|
||||
use crate::gateway::fast_socks5::consts;
|
||||
use crate::gateway::fast_socks5::consts::SOCKS5_ADDR_TYPE_IPV4;
|
||||
use crate::gateway::fast_socks5::SocksError;
|
||||
use crate::read_exact;
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -99,7 +99,7 @@ impl TargetAddr {
|
||||
buf.extend_from_slice(&(addr.ip()).octets()); // ip
|
||||
buf.extend_from_slice(&addr.port().to_be_bytes()); // port
|
||||
}
|
||||
TargetAddr::Domain(ref domain, port) => {
|
||||
TargetAddr::Domain(domain, port) => {
|
||||
debug!("TargetAddr::Domain");
|
||||
if domain.len() > u8::MAX as usize {
|
||||
return Err(SocksError::ExceededMaxDomainLen(domain.len()).into());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user