Compare commits

..

13 Commits

Author SHA1 Message Date
Sijie.Sun a6773aa549 zstd should reuse ctx to avoid huge mmap cost (#941) 2025-06-06 08:59:06 +08:00
Sijie.Sun 0314c66635 some improvements (#939)
1. ospf route conn map should also use version
2. treat nopat as cone
2025-06-05 22:49:57 +08:00
chenxudong2020 3fb172b4d2 Modify SNI logic: always use "localhost" as SNI to avoid IP blocking (#934) 2025-06-05 11:56:07 +08:00
Sijie.Sun 96fc19b803 fix minor bugs (#936)
1. update upx to v5.0.1 to avoid mips bug.
2. use latest mimalloc.
3. fix panic in ospf route
4. potential residual conn.
2025-06-05 11:55:44 +08:00
Wang Zeng 9f7ba8ab8f fix(easytier-gui): restore window correctly when clicking tray icon while minimized (#930)
以前在最小化窗口时单击托盘图标会错误地切换任务栏图标的可见性。

这一改变实现了预期的行为:
- 当窗口最小化时:单击托盘将窗口恢复到原始状态
- 当窗口可见时:托盘单击最小化到托盘
- 当窗口隐藏时:单击托盘恢复窗口

该修复通过提供标准的托盘交互行为来增强用户体验。包括必要的事件处理窗口状态转换。
2025-06-04 16:33:06 +08:00
Mg Pig e592e9f29a 节点信息组件添加隧道协议字段 (#931) 2025-06-04 09:22:58 +08:00
Sijie.Sun 4608bca998 improve performance of route generation (#914)
this may fix following problem:

1. cpu 100% when large number of nodes in network.
2. high cpu usage when large number of foreign networks.
3. packet loss when new node enters/exits.
4. old routes not cleand and show as an obloleted entry.
2025-06-02 20:12:27 +08:00
FuturePrayer b5dfc7374c add private mode (#897)
---------

Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
2025-06-02 06:47:17 +08:00
Mg Pig b469f8197a Supports customizing the API server address of the Web frontend through the --api-host parameter (#913) 2025-06-02 06:46:12 +08:00
Sijie.Sun 0a38a8ef4a fix musl download fail in ci action (#902)
https://github.com/orgs/community/discussions/27906

musl.cc banned microsoft ips

this patch replace musl.cc with https://github.com/cross-tools/musl-cross
2025-05-29 09:35:32 +08:00
Mg Pig e75be7801f easytier-web add websocket support (#901)
Co-authored-by: xzzpig <w2xzzig@hotmail.com>
2025-05-28 21:29:21 +08:00
Sijie.Sun 6c49bb1865 rename magisk kill.sh to action.sh (#893) 2025-05-27 09:32:40 +08:00
Sijie.Sun f9c24bc205 fix bugs (#892)
1. traffic stats not work.
2. magisk zip malformat
2025-05-27 09:28:28 +08:00
56 changed files with 1180 additions and 556 deletions
+27 -15
View File
@@ -6,72 +6,84 @@ rustflags = ["-C", "linker-flavor=ld.lld"]
linker = "aarch64-linux-gnu-gcc" linker = "aarch64-linux-gnu-gcc"
[target.aarch64-unknown-linux-musl] [target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc" linker = "aarch64-unknown-linux-musl-gcc"
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]
[target.'cfg(all(windows, target_env = "msvc"))'] [target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]
[target.mipsel-unknown-linux-musl] [target.mipsel-unknown-linux-musl]
linker = "mipsel-linux-muslsf-gcc" linker = "mipsel-unknown-linux-muslsf-gcc"
rustflags = [ rustflags = [
"-C", "-C",
"target-feature=+crt-static", "target-feature=+crt-static",
"-L", "-L",
"./musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib", "./musl_gcc/mipsel-unknown-linux-muslsf/mipsel-unknown-linux-muslsf/lib",
"-L", "-L",
"./musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1", "./musl_gcc/mipsel-unknown-linux-muslsf/mipsel-unknown-linux-muslsf/sysroot/usr/lib",
"-L",
"./musl_gcc/mipsel-unknown-linux-muslsf/lib/gcc/mipsel-unknown-linux-muslsf/15.1.0",
"-l", "-l",
"atomic", "atomic",
"-l", "-l",
"ctz", "ctz",
"-l",
"gcc",
] ]
[target.mips-unknown-linux-musl] [target.mips-unknown-linux-musl]
linker = "mips-linux-muslsf-gcc" linker = "mips-unknown-linux-muslsf-gcc"
rustflags = [ rustflags = [
"-C", "-C",
"target-feature=+crt-static", "target-feature=+crt-static",
"-L", "-L",
"./musl_gcc/mips-linux-muslsf-cross/mips-linux-muslsf/lib", "./musl_gcc/mips-unknown-linux-muslsf/mips-unknown-linux-muslsf/lib",
"-L", "-L",
"./musl_gcc/mips-linux-muslsf-cross/lib/gcc/mips-linux-muslsf/11.2.1", "./musl_gcc/mips-unknown-linux-muslsf/mips-unknown-linux-muslsf/sysroot/usr/lib",
"-L",
"./musl_gcc/mips-unknown-linux-muslsf/lib/gcc/mips-unknown-linux-muslsf/15.1.0",
"-l", "-l",
"atomic", "atomic",
"-l", "-l",
"ctz", "ctz",
"-l",
"gcc",
] ]
[target.armv7-unknown-linux-musleabihf] [target.armv7-unknown-linux-musleabihf]
linker = "armv7l-linux-musleabihf-gcc" linker = "armv7-unknown-linux-musleabihf-gcc"
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]
[target.armv7-unknown-linux-musleabi] [target.armv7-unknown-linux-musleabi]
linker = "armv7m-linux-musleabi-gcc" linker = "armv7-unknown-linux-musleabi-gcc"
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]
[target.arm-unknown-linux-musleabihf] [target.arm-unknown-linux-musleabihf]
linker = "arm-linux-musleabihf-gcc" linker = "arm-unknown-linux-musleabihf-gcc"
rustflags = [ rustflags = [
"-C", "-C",
"target-feature=+crt-static", "target-feature=+crt-static",
"-L", "-L",
"./musl_gcc/arm-linux-musleabihf-cross/arm-linux-musleabihf/lib", "./musl_gcc/arm-unknown-linux-musleabihf/arm-unknown-linux-musleabihf/lib",
"-L", "-L",
"./musl_gcc/arm-linux-musleabihf-cross/lib/gcc/arm-linux-musleabihf/11.2.1", "./musl_gcc/arm-unknown-linux-musleabihf/lib/gcc/arm-unknown-linux-musleabihf/15.1.0",
"-l", "-l",
"atomic", "atomic",
"-l",
"gcc",
] ]
[target.arm-unknown-linux-musleabi] [target.arm-unknown-linux-musleabi]
linker = "arm-linux-musleabi-gcc" linker = "arm-unknown-linux-musleabi-gcc"
rustflags = [ rustflags = [
"-C", "-C",
"target-feature=+crt-static", "target-feature=+crt-static",
"-L", "-L",
"./musl_gcc/arm-linux-musleabi-cross/arm-linux-musleabi/lib", "./musl_gcc/arm-unknown-linux-musleabi/arm-unknown-linux-musleabi/lib",
"-L", "-L",
"./musl_gcc/arm-linux-musleabi-cross/lib/gcc/arm-linux-musleabi/11.2.1", "./musl_gcc/arm-unknown-linux-musleabi/lib/gcc/arm-unknown-linux-musleabi/15.1.0",
"-l", "-l",
"atomic", "atomic",
"-l",
"gcc",
] ]
+11 -10
View File
@@ -166,9 +166,14 @@ jobs:
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }} if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
run: | run: |
bash ./.github/workflows/install_rust.sh bash ./.github/workflows/install_rust.sh
# we set the sysroot when sysroot is a dir
# this dir is a soft link generated by install_rust.sh # 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 # 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
export KCP_SYS_EXTRA_HEADER_PATH=/usr/include/musl-cross if [[ -d "./musl_gcc/sysroot" ]]; then
export BINDGEN_EXTRA_CLANG_ARGS=--sysroot=$(readlink -f ./musl_gcc/sysroot)
fi
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
cargo +nightly build -r --verbose --target $TARGET -Z build-std=std,panic_abort --no-default-features --features mips --package=easytier cargo +nightly build -r --verbose --target $TARGET -Z build-std=std,panic_abort --no-default-features --features mips --package=easytier
else else
@@ -218,13 +223,6 @@ jobs:
mv ./target/$TARGET/release/easytier-web ./target/$TARGET/release/easytier-web-embed mv ./target/$TARGET/release/easytier-web ./target/$TARGET/release/easytier-web-embed
cargo build --release --verbose --target $TARGET cargo build --release --verbose --target $TARGET
- name: Install UPX
if: ${{ matrix.OS != 'macos-latest' }}
uses: crazy-max/ghaction-upx@v3
with:
version: latest
install-only: true
- name: Compress - name: Compress
run: | run: |
mkdir -p ./artifacts/objects/ mkdir -p ./artifacts/objects/
@@ -246,8 +244,11 @@ jobs:
fi fi
if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^.*freebsd$ ]]; then if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ ^.*freebsd$ ]]; then
upx --lzma --best ./target/$TARGET/release/easytier-core"$SUFFIX" UPX_VERSION=5.0.1
upx --lzma --best ./target/$TARGET/release/easytier-cli"$SUFFIX" 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"
fi fi
mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/ mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/
+1 -1
View File
@@ -11,7 +11,7 @@ on:
image_tag: image_tag:
description: 'Tag for this image build' description: 'Tag for this image build'
type: string type: string
default: 'v2.3.0' default: 'v2.3.1'
required: true required: true
mark_latest: mark_latest:
description: 'Mark this image as latest' description: 'Mark this image as latest'
+16 -32
View File
@@ -8,38 +8,22 @@
# dependencies are only needed on ubuntu as that's the only place where # dependencies are only needed on ubuntu as that's the only place where
# we make cross-compilation # we make cross-compilation
if [[ $OS =~ ^ubuntu.*$ ]]; then if [[ $OS =~ ^ubuntu.*$ ]]; then
sudo apt-get update && sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf musl-tools libappindicator3-dev llvm clang sudo apt-get update && sudo apt-get install -qq musl-tools libappindicator3-dev llvm clang
# curl -s musl.cc | grep mipsel # https://github.com/cross-tools/musl-cross/releases
case $TARGET in # if "musl" is a substring of TARGET, we assume that we are using musl
mipsel-unknown-linux-musl) MUSL_TARGET=$TARGET
MUSL_URI=mipsel-linux-muslsf # if target is mips or mipsel, we should use soft-float version of musl
;; if [[ $TARGET =~ ^mips.*$ || $TARGET =~ ^mipsel.*$ ]]; then
mips-unknown-linux-musl) MUSL_TARGET=${TARGET}sf
MUSL_URI=mips-linux-muslsf fi
;; if [[ $MUSL_TARGET =~ musl ]]; then
aarch64-unknown-linux-musl)
MUSL_URI=aarch64-linux-musl
;;
armv7-unknown-linux-musleabihf)
MUSL_URI=armv7l-linux-musleabihf
;;
armv7-unknown-linux-musleabi)
MUSL_URI=armv7m-linux-musleabi
;;
arm-unknown-linux-musleabihf)
MUSL_URI=arm-linux-musleabihf
;;
arm-unknown-linux-musleabi)
MUSL_URI=arm-linux-musleabi
;;
esac
if [ -n "$MUSL_URI" ]; then
mkdir -p ./musl_gcc mkdir -p ./musl_gcc
wget --inet4-only -c https://musl.cc/${MUSL_URI}-cross.tgz -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 zxf ./musl_gcc/${MUSL_URI}-cross.tgz -C ./musl_gcc/ tar xf ./musl_gcc/${MUSL_TARGET}.tar.xz -C ./musl_gcc/
sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/bin/*gcc /usr/bin/ sudo ln -sf $(pwd)/musl_gcc/${MUSL_TARGET}/bin/*gcc /usr/bin/
sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/${MUSL_URI}/include/ /usr/include/musl-cross 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
fi fi
@@ -50,7 +34,7 @@ rustup default 1.86
# mips/mipsel cannot add target from rustup, need compile by ourselves # mips/mipsel cannot add target from rustup, need compile by ourselves
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
cd "$PWD/musl_gcc/${MUSL_URI}-cross/lib/gcc/${MUSL_URI}/11.2.1" || exit 255 cd "$PWD/musl_gcc/${MUSL_TARGET}/lib/gcc/${MUSL_TARGET}/15.1.0" || exit 255
# for panic-abort # for panic-abort
cp libgcc_eh.a libunwind.a cp libgcc_eh.a libunwind.a
+10 -3
View File
@@ -21,7 +21,7 @@ on:
version: version:
description: 'Version for this release' description: 'Version for this release'
type: string type: string
default: 'v2.3.0' default: 'v2.3.1'
required: true required: true
make_latest: make_latest:
description: 'Mark this release as latest' description: 'Mark this release as latest'
@@ -57,7 +57,7 @@ jobs:
repo: EasyTier/EasyTier repo: EasyTier/EasyTier
path: release_assets_nozip path: release_assets_nozip
- name: Download GUI Artifact - name: Download Mobile Artifact
uses: dawidd6/action-download-artifact@v6 uses: dawidd6/action-download-artifact@v6
with: with:
github_token: ${{secrets.GITHUB_TOKEN}} github_token: ${{secrets.GITHUB_TOKEN}}
@@ -78,7 +78,14 @@ jobs:
ls -l -R ./ ls -l -R ./
chmod -R 755 . chmod -R 755 .
for x in `ls`; do for x in `ls`; do
zip ../zipped_assets/$x-${VERSION}.zip $x/*; if [ "$x" = "Easytier-Magisk" ]; then
# for Easytier-Magisk, make sure files are in the root of the zip
cd $x;
zip -r ../../zipped_assets/$x-${VERSION}.zip .;
cd ..;
else
zip -r ../zipped_assets/$x-${VERSION}.zip $x;
fi
done done
- name: Release - name: Release
Generated
+170 -107
View File
@@ -23,6 +23,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]] [[package]]
name = "aead" name = "aead"
version = "0.5.2" version = "0.5.2"
@@ -196,6 +202,15 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
dependencies = [
"derive_arbitrary",
]
[[package]] [[package]]
name = "arboard" name = "arboard"
version = "3.4.0" version = "3.4.0"
@@ -229,7 +244,7 @@ dependencies = [
"base64ct", "base64ct",
"blake2", "blake2",
"cpufeatures", "cpufeatures",
"password-hash 0.5.0", "password-hash",
] ]
[[package]] [[package]]
@@ -274,8 +289,8 @@ dependencies = [
"memchr", "memchr",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"zstd 0.13.2", "zstd",
"zstd-safe 7.2.1", "zstd-safe",
] ]
[[package]] [[package]]
@@ -632,7 +647,7 @@ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"libc", "libc",
"miniz_oxide", "miniz_oxide 0.7.4",
"object", "object",
"rustc-demangle", "rustc-demangle",
] ]
@@ -853,9 +868,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]] [[package]]
name = "bytecheck" name = "bytecheck"
@@ -924,22 +939,20 @@ dependencies = [
[[package]] [[package]]
name = "bzip2" name = "bzip2"
version = "0.4.4" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
dependencies = [ dependencies = [
"bzip2-sys", "bzip2-sys",
"libc",
] ]
[[package]] [[package]]
name = "bzip2-sys" name = "bzip2-sys"
version = "0.1.11+1.0.8" version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [ dependencies = [
"cc", "cc",
"libc",
"pkg-config", "pkg-config",
] ]
@@ -1289,9 +1302,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]] [[package]]
name = "conv" name = "conv"
@@ -1550,12 +1563,6 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "cty"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "4.1.3" version = "4.1.3"
@@ -1674,6 +1681,12 @@ dependencies = [
"thiserror 1.0.63", "thiserror 1.0.63",
] ]
[[package]]
name = "deflate64"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
[[package]] [[package]]
name = "defmt" name = "defmt"
version = "0.3.8" version = "0.3.8"
@@ -1719,14 +1732,25 @@ dependencies = [
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde", "serde",
] ]
[[package]]
name = "derive_arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]] [[package]]
name = "derive_builder" name = "derive_builder"
version = "0.20.2" version = "0.20.2"
@@ -1918,11 +1942,10 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]] [[package]]
name = "easytier" name = "easytier"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
"async-compression",
"async-recursion", "async-recursion",
"async-ringbuf", "async-ringbuf",
"async-stream", "async-stream",
@@ -1950,6 +1973,7 @@ dependencies = [
"gethostname 0.5.0", "gethostname 0.5.0",
"git-version", "git-version",
"globwalk", "globwalk",
"hashbrown 0.15.3",
"hickory-client", "hickory-client",
"hickory-proto", "hickory-proto",
"hickory-resolver", "hickory-resolver",
@@ -1964,7 +1988,7 @@ dependencies = [
"kcp-sys", "kcp-sys",
"machine-uid", "machine-uid",
"maplit", "maplit",
"mimalloc-rust", "mimalloc",
"multimap", "multimap",
"netlink-packet-core", "netlink-packet-core",
"netlink-packet-route 0.21.0", "netlink-packet-route 0.21.0",
@@ -2030,6 +2054,7 @@ dependencies = [
"winreg 0.52.0", "winreg 0.52.0",
"zerocopy", "zerocopy",
"zip", "zip",
"zstd",
] ]
[[package]] [[package]]
@@ -2045,7 +2070,7 @@ dependencies = [
[[package]] [[package]]
name = "easytier-gui" name = "easytier-gui"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@@ -2091,7 +2116,7 @@ dependencies = [
[[package]] [[package]]
name = "easytier-web" name = "easytier-web"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -2401,12 +2426,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.31" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "libz-rs-sys",
"miniz_oxide 0.8.8",
] ]
[[package]] [[package]]
@@ -2813,9 +2839,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.2+wasi-0.2.4", "wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -3075,9 +3103,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -3681,7 +3709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.2", "hashbrown 0.15.3",
"serde", "serde",
] ]
@@ -3946,10 +3974,11 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.70" version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [ dependencies = [
"once_cell",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -4090,12 +4119,42 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "liblzma"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66352d7a8ac12d4877b6e6ea5a9b7650ee094257dc40889955bea5bc5b08c1d0"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5839bad90c3cc2e0b8c4ed8296b80e86040240f81d46b9c0e9bc8dd51ddd3af1"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "libm" name = "libm"
version = "0.2.8" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libmimalloc-sys"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.3" version = "0.1.3"
@@ -4123,6 +4182,15 @@ version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961"
[[package]]
name = "libz-rs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
dependencies = [
"zlib-rs",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@@ -4154,9 +4222,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "loom" name = "loom"
@@ -4298,21 +4366,12 @@ dependencies = [
] ]
[[package]] [[package]]
name = "mimalloc-rust" name = "mimalloc"
version = "0.2.1" version = "0.1.46"
source = "git+https://github.com/EasyTier/mimalloc-rust#eb61c4d50fef4eb5fbd5db83e4ea83153646b482" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af"
dependencies = [ dependencies = [
"cty", "libmimalloc-sys",
"mimalloc-rust-sys",
]
[[package]]
name = "mimalloc-rust-sys"
version = "2.1.2-source"
source = "git+https://github.com/EasyTier/mimalloc-rust#eb61c4d50fef4eb5fbd5db83e4ea83153646b482"
dependencies = [
"cc",
"cty",
] ]
[[package]] [[package]]
@@ -4347,6 +4406,15 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.2" version = "1.0.2"
@@ -4788,7 +4856,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [ dependencies = [
"proc-macro-crate 2.0.0", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
@@ -5270,21 +5338,10 @@ checksum = "1a2a4764cc1f8d961d802af27193c6f4f0124bd0e76e8393cf818e18880f0524"
dependencies = [ dependencies = [
"argon2", "argon2",
"getrandom 0.2.15", "getrandom 0.2.15",
"password-hash 0.5.0", "password-hash",
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]] [[package]]
name = "password-hash" name = "password-hash"
version = "0.5.0" version = "0.5.0"
@@ -5310,14 +5367,12 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]] [[package]]
name = "pbkdf2" name = "pbkdf2"
version = "0.11.0" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [ dependencies = [
"digest", "digest",
"hmac", "hmac",
"password-hash 0.4.2",
"sha2",
] ]
[[package]] [[package]]
@@ -5362,7 +5417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06"
dependencies = [ dependencies = [
"fixedbitset 0.5.7", "fixedbitset 0.5.7",
"hashbrown 0.15.2", "hashbrown 0.15.3",
"indexmap 2.7.1", "indexmap 2.7.1",
"serde", "serde",
] ]
@@ -5667,7 +5722,7 @@ dependencies = [
"crc32fast", "crc32fast",
"fdeflate", "fdeflate",
"flate2", "flate2",
"miniz_oxide", "miniz_oxide 0.7.4",
] ]
[[package]] [[package]]
@@ -8421,9 +8476,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.36" version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa 1.0.11", "itoa 1.0.11",
@@ -8438,15 +8493,15 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.2" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.18" version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [ dependencies = [
"num-conv", "num-conv",
"time-core", "time-core",
@@ -9294,24 +9349,24 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.93" version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
"rustversion",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.93" version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
@@ -9332,9 +9387,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.93" version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -9342,9 +9397,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.93" version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -9355,9 +9410,12 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.93" version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]] [[package]]
name = "wasm-streams" name = "wasm-streams"
@@ -10311,31 +10369,46 @@ dependencies = [
[[package]] [[package]]
name = "zip" name = "zip"
version = "0.6.6" version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" checksum = "153a6fff49d264c4babdcfa6b4d534747f520e56e8f0f384f3b808c4b64cc1fd"
dependencies = [ dependencies = [
"aes", "aes",
"byteorder", "arbitrary",
"bzip2", "bzip2",
"constant_time_eq", "constant_time_eq",
"crc32fast", "crc32fast",
"crossbeam-utils", "deflate64",
"flate2", "flate2",
"getrandom 0.3.2",
"hmac", "hmac",
"indexmap 2.7.1",
"liblzma",
"memchr",
"pbkdf2", "pbkdf2",
"sha1", "sha1",
"time", "time",
"zstd 0.11.2+zstd.1.5.2", "zeroize",
"zopfli",
"zstd",
] ]
[[package]] [[package]]
name = "zstd" name = "zlib-rs"
version = "0.11.2+zstd.1.5.2" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"
[[package]]
name = "zopfli"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [ dependencies = [
"zstd-safe 5.0.2+zstd.1.5.2", "bumpalo",
"crc32fast",
"log",
"simd-adler32",
] ]
[[package]] [[package]]
@@ -10344,17 +10417,7 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
dependencies = [ dependencies = [
"zstd-safe 7.2.1", "zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
] ]
[[package]] [[package]]
+1 -1
View File
@@ -16,5 +16,5 @@ panic = "unwind"
panic = "abort" panic = "abort"
lto = true lto = true
codegen-units = 1 codegen-units = 1
opt-level = 'z' opt-level = 3
strip = true strip = true
+1 -1
View File
@@ -2,5 +2,5 @@
magisk安装后重启 magisk安装后重启
目录位置:/data/adb/modules/easytier_magisk 目录位置:/data/adb/modules/easytier_magisk
配置文件位置://data/adb/modules/easytier_magisk/config/config.conf 配置文件位置://data/adb/modules/easytier_magisk/config/config.toml
修改config.conf即可,修改后配置文件后去magisk app重新开关模块即可生效 修改config.conf即可,修改后配置文件后去magisk app重新开关模块即可生效
+8 -10
View File
@@ -1,16 +1,14 @@
#!/data/adb/magisk/busybox sh #!/data/adb/magisk/busybox sh
MODDIR=${0%/*} MODDIR=${0%/*}
echo 'Easytier 服务停止中....'
PIDS=$(pgrep -f "^${MODDIR}/easytier-core -c ${MODDIR}/config/config.conf") # 查找 easytier-core 进程的 PID
PID=$(pgrep easytier-core)
if [ -n "$PIDS" ]; then # 检查是否找到了进程
kill $PIDS # 杀死所有匹配的进程 if [ -z "$PID" ]; then
echo "已停止所有 Easytier 进程 (PIDs: $PIDS)" echo "easytier-core 进程未找到"
else else
echo "Easytier 服务未运行" # 结束进程
kill $PID
echo "已结束 easytier-core 进程 (PID: $PID)"
fi fi
echo '重启服务中...'
nohup sh ${MODDIR}/service.sh >> ${MODDIR}/log/start.log 2>&1 &
echo '服务已重启'
exit
@@ -2,6 +2,6 @@ ui_print '安装完成'
ui_print '当前架构为' + $ARCH ui_print '当前架构为' + $ARCH
ui_print '当前系统版本为' + $API ui_print '当前系统版本为' + $API
ui_print '安装目录为: /data/adb/modules/easytier_magisk' ui_print '安装目录为: /data/adb/modules/easytier_magisk'
ui_print '配置文件位置: /data/adb/modules/easytier_magisk/config/config.conf' ui_print '配置文件位置: /data/adb/modules/easytier_magisk/config/config.toml'
ui_print '修改后配置文件后在magisk app点击操作按钮即可生效' ui_print '修改后配置文件后在magisk app点击操作按钮即可生效'
ui_print '记得重启' ui_print '记得重启'
@@ -0,0 +1,48 @@
#!/system/bin/sh
MODDIR=${0%/*}
CONFIG_FILE="${MODDIR}/config/config.toml"
LOG_FILE="${MODDIR}/log.log"
MODULE_PROP="${MODDIR}/module.prop"
EASYTIER="${MODDIR}/easytier-core"
# 更新module.prop文件中的description
update_module_description() {
local status_message=$1
sed -i "/^description=/c\description=[状态]${status_message}" ${MODULE_PROP}
}
if [ ! -e /dev/net/tun ]; then
if [ ! -d /dev/net ]; then
mkdir -p /dev/net
fi
ln -s /dev/tun /dev/net/tun
fi
while true; do
if ls $MODDIR | grep -q "disable"; then
update_module_description "关闭中"
if pgrep -f 'easytier-core' >/dev/null; then
echo "开关控制$(date "+%Y-%m-%d %H:%M:%S") 进程已存在,正在关闭 ..."
pkill easytier-core # 关闭进程
fi
else
if ! pgrep -f 'easytier-core' >/dev/null; then
if [ ! -f "$CONFIG_FILE" ]; then
update_module_description "config.toml不存在"
sleep 3s
continue
fi
TZ=Asia/Shanghai ${EASYTIER} -c ${CONFIG_FILE} > ${LOG_FILE} &
sleep 5s # 等待easytier-core启动完成
update_module_description "已开启(不一定运行成功)"
ip rule add from all lookup main
else
echo "开关控制$(date "+%Y-%m-%d %H:%M:%S") 进程已存在"
fi
fi
sleep 3s # 暂停3秒后再次执行循环
done
+3 -3
View File
@@ -1,7 +1,7 @@
id=easytier_magisk id=easytier_magisk
name=easytier_magisk name=EasyTier_Magisk
version=v2.2.4 version=v2.3.1
versionCode=1 versionCode=1
author=EasyTier author=EasyTier
description=easytier_magisk版模块 作者:EasyTier https://github.com/EasyTier/EasyTier description=easytier magisk module @EasyTier(https://github.com/EasyTier/EasyTier)
updateJson=https://raw.githubusercontent.com/EasyTier/EasyTier/refs/heads/main/easytier-contrib/easytier-magisk/magisk_update.json updateJson=https://raw.githubusercontent.com/EasyTier/EasyTier/refs/heads/main/easytier-contrib/easytier-magisk/magisk_update.json
+15 -8
View File
@@ -1,20 +1,27 @@
#!/data/adb/magisk/busybox sh #!/data/adb/magisk/busybox sh
MODDIR=${0%/*} MODDIR=${0%/*}
# MODDIR="$(dirname $(readlink -f "$0"))" # MODDIR="$(dirname $(readlink -f "$0"))"
mkdir -p ${MODDIR}/log
chmod 755 ${MODDIR}/* chmod 755 ${MODDIR}/*
echo $MODDIR >> ${MODDIR}/log/start.log # 等待系统启动成功
while [ "$(getprop sys.boot_completed)" != "1" ]; do
sleep 5s
done
echo "Easytier 服务启动" >> ${MODDIR}/log/start.log # 防止系统挂起
echo "PowerManagerService.noSuspend" > /sys/power/wake_lock
# 启动 # 修改模块描述
nohup ${MODDIR}/easytier-core -c ${MODDIR}/config/config.conf >> ${MODDIR}/log/start.log 2>&1 & sed -i 's/$(description=)$[^"]*/\1[状态]关闭中/' "$MODDIR/module.prop"
# 等待 3 秒
sleep 3s
"${MODDIR}/easytier_core.sh" &
# 检查是否启用模块 # 检查是否启用模块
while [ ! -f ${MODDIR}/disable ]; do while [ ! -f ${MODDIR}/disable ]; do
sleep 2 sleep 2
done done
PID=$(ps -ef|grep "${MODDIR}/easytier-core -c ${MODDIR}/config/config.conf" | awk '{print $2}')
kill $PID pkill easytier-core
echo "Easytier 服务停止" >> ${MODDIR}/log/start.log
@@ -0,0 +1,2 @@
nameserver 114.114.114.114
nameserver 223.5.5.5
@@ -1,2 +1,3 @@
MODDIR=${0%/*} MODDIR=${0%/*}
pkill easytier-core # 结束 easytier-core 进程
rm -rf $MODDIR/* rm -rf $MODDIR/*
+5 -1
View File
@@ -18,7 +18,11 @@ cd ../tauri-plugin-vpnservice
pnpm install pnpm install
pnpm build pnpm build
cd ../easytier-gui cd ../easytier-web/frontend-lib
pnpm install
pnpm build
cd ../../easytier-gui
pnpm install pnpm install
pnpm tauri build pnpm tauri build
``` ```
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "easytier-gui", "name": "easytier-gui",
"type": "module", "type": "module",
"version": "2.2.4", "version": "2.3.1",
"private": true, "private": true,
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4", "packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "easytier-gui" name = "easytier-gui"
version = "2.3.0" version = "2.3.1"
description = "EasyTier GUI" description = "EasyTier GUI"
authors = ["you"] authors = ["you"]
edition = "2021" edition = "2021"
+6 -1
View File
@@ -108,7 +108,12 @@ fn set_tun_fd(instance_id: String, fd: i32) -> Result<(), String> {
fn toggle_window_visibility<R: tauri::Runtime>(app: &tauri::AppHandle<R>) { fn toggle_window_visibility<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
if window.is_visible().unwrap_or_default() { if window.is_visible().unwrap_or_default() {
let _ = window.hide(); if window.is_minimized().unwrap_or_default() {
let _ = window.unminimize();
let _ = window.set_focus();
} else {
let _ = window.hide();
}
} else { } else {
let _ = window.show(); let _ = window.show();
let _ = window.set_focus(); let _ = window.set_focus();
+1 -1
View File
@@ -17,7 +17,7 @@
"createUpdaterArtifacts": false "createUpdaterArtifacts": false
}, },
"productName": "easytier-gui", "productName": "easytier-gui",
"version": "2.3.0", "version": "2.3.1",
"identifier": "com.kkrainbow.easytier", "identifier": "com.kkrainbow.easytier",
"plugins": {}, "plugins": {},
"app": { "app": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "easytier-web" name = "easytier-web"
version = "2.3.0" version = "2.3.1"
edition = "2021" edition = "2021"
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server." description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
@@ -157,6 +157,7 @@ const bool_flags: BoolFlag[] = [
{ field: 'disable_encryption', help: 'disable_encryption_help' }, { field: 'disable_encryption', help: 'disable_encryption_help' },
{ field: 'disable_udp_hole_punching', help: 'disable_udp_hole_punching_help' }, { field: 'disable_udp_hole_punching', help: 'disable_udp_hole_punching_help' },
{ field: 'enable_magic_dns', help: 'enable_magic_dns_help' }, { field: 'enable_magic_dns', help: 'enable_magic_dns_help' },
{ field: 'enable_private_mode', help: 'enable_private_mode_help' },
] ]
</script> </script>
@@ -106,6 +106,10 @@ function ipFormat(info: PeerRoutePair) {
return ip ? `${IPv4.fromNumber(ip.address.addr)}/${ip.network_length}` : '' return ip ? `${IPv4.fromNumber(ip.address.addr)}/${ip.network_length}` : ''
} }
function tunnelProto(info: PeerRoutePair) {
return [...new Set(info.peer?.conns.map(c => c.tunnel?.tunnel_type))].join(',')
}
const myNodeInfo = computed(() => { const myNodeInfo = computed(() => {
if (!props.curNetworkInst) if (!props.curNetworkInst)
return {} as NodeInfo return {} as NodeInfo
@@ -311,7 +315,7 @@ function showEventLogs() {
<Timeline v-else :value="dialogContent"> <Timeline v-else :value="dialogContent">
<template #opposite="slotProps"> <template #opposite="slotProps">
<small class="text-surface-500 dark:text-surface-400">{{ useTimeAgo(Date.parse(slotProps.item.time)) <small class="text-surface-500 dark:text-surface-400">{{ useTimeAgo(Date.parse(slotProps.item.time))
}}</small> }}</small>
</template> </template>
<template #content="slotProps"> <template #content="slotProps">
<HumanEvent :event="slotProps.item.event" /> <HumanEvent :event="slotProps.item.event" />
@@ -408,6 +412,7 @@ function showEventLogs() {
</template> </template>
</Column> </Column>
<Column :field="routeCost" :header="t('route_cost')" /> <Column :field="routeCost" :header="t('route_cost')" />
<Column :field="tunnelProto" :header="t('tunnel_proto')" />
<Column :field="latencyMs" :header="t('latency')" /> <Column :field="latencyMs" :header="t('latency')" />
<Column :field="txBytes" :header="t('upload_bytes')" /> <Column :field="txBytes" :header="t('upload_bytes')" />
<Column :field="rxBytes" :header="t('download_bytes')" /> <Column :field="rxBytes" :header="t('download_bytes')" />
@@ -64,6 +64,7 @@ event_log: 事件日志
peer_info: 节点信息 peer_info: 节点信息
hostname: 主机名 hostname: 主机名
route_cost: 路由 route_cost: 路由
tunnel_proto: 协议
latency: 延迟 latency: 延迟
upload_bytes: 上传 upload_bytes: 上传
download_bytes: 下载 download_bytes: 下载
@@ -116,6 +117,10 @@ enable_magic_dns: 启用魔法DNS
enable_magic_dns_help: | enable_magic_dns_help: |
启用魔法DNS,允许通过EasyTier的DNS服务器访问其他节点的虚拟IPv4地址, 如 node1.et.net。 启用魔法DNS,允许通过EasyTier的DNS服务器访问其他节点的虚拟IPv4地址, 如 node1.et.net。
enable_private_mode: 启用私有模式
enable_private_mode_help: |
启用私有模式,则不允许使用了与本网络不相同的网络名称和密码的节点通过本节点进行握手或中转。
relay_network_whitelist: 网络白名单 relay_network_whitelist: 网络白名单
relay_network_whitelist_help: | relay_network_whitelist_help: |
仅转发白名单网络的流量,支持通配符字符串。多个网络名称间可以使用英文空格间隔。 仅转发白名单网络的流量,支持通配符字符串。多个网络名称间可以使用英文空格间隔。
@@ -62,6 +62,7 @@ show_event_log: Show Event Log
event_log: Event Log event_log: Event Log
peer_info: Peer Info peer_info: Peer Info
route_cost: Route Cost route_cost: Route Cost
tunnel_proto: Protocol
hostname: Hostname hostname: Hostname
latency: Latency latency: Latency
upload_bytes: Upload upload_bytes: Upload
@@ -115,6 +116,10 @@ enable_magic_dns: Enable Magic DNS
enable_magic_dns_help: | enable_magic_dns_help: |
Enable magic dns, all nodes in the network can access each other by domain name, e.g.: node1.et.net. Enable magic dns, all nodes in the network can access each other by domain name, e.g.: node1.et.net.
enable_private_mode: Enable Private Mode
enable_private_mode_help: |
Enable private mode, nodes with different network names or passwords from this network are not allowed to perform handshake or relay through this node.
relay_network_whitelist: Network Whitelist relay_network_whitelist: Network Whitelist
relay_network_whitelist_help: | relay_network_whitelist_help: |
Only forward traffic from the whitelist networks, supporting wildcard strings, multiple network names can be separated by spaces. Only forward traffic from the whitelist networks, supporting wildcard strings, multiple network names can be separated by spaces.
@@ -64,6 +64,7 @@ export interface NetworkConfig {
mapped_listeners: string[] mapped_listeners: string[]
enable_magic_dns?: boolean enable_magic_dns?: boolean
enable_private_mode?: boolean
} }
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
@@ -121,6 +122,7 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
mtu: null, mtu: null,
mapped_listeners: [], mapped_listeners: [],
enable_magic_dns: false, enable_magic_dns: false,
enable_private_mode: false,
} }
} }
+1
View File
@@ -5,6 +5,7 @@
<link rel="icon" type="image/png" href="/easytier.png" /> <link rel="icon" type="image/png" href="/easytier.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EasyTier Dashboard</title> <title>EasyTier Dashboard</title>
<script src="/api_meta.js"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+11 -4
View File
@@ -1,10 +1,17 @@
const defaultApiHost = 'https://config-server.easytier.cn';
interface ApiHost { interface ApiHost {
value: string; value: string;
usedAt: number; usedAt: number;
} }
let apiMeta: {
api_host: string;
} | undefined = (window as any).apiMeta;
// remove trailing slashes from the URL
const cleanUrl = (url: string) => url.replace(/\/+$/, '');
const defaultApiHost = cleanUrl(apiMeta?.api_host ?? `${location.origin}${location.pathname}`);
const isValidHttpUrl = (s: string): boolean => { const isValidHttpUrl = (s: string): boolean => {
let url; let url;
@@ -45,7 +52,7 @@ const saveApiHost = (host: string) => {
} }
let hosts = cleanAndLoadApiHosts(); let hosts = cleanAndLoadApiHosts();
const newHost: ApiHost = {value: host, usedAt: Date.now()}; const newHost: ApiHost = { value: host, usedAt: Date.now() };
hosts = hosts.filter((h) => h.value !== host); hosts = hosts.filter((h) => h.value !== host);
hosts.push(newHost); hosts.push(newHost);
localStorage.setItem('apiHosts', JSON.stringify(hosts)); localStorage.setItem('apiHosts', JSON.stringify(hosts));
@@ -61,4 +68,4 @@ const getInitialApiHost = (): string => {
} }
}; };
export {getInitialApiHost, cleanAndLoadApiHosts, saveApiHost} export { getInitialApiHost, cleanAndLoadApiHosts, saveApiHost }
+11
View File
@@ -3,9 +3,20 @@ import vue from '@vitejs/plugin-vue'
// import { viteSingleFile } from "vite-plugin-singlefile" // import { viteSingleFile } from "vite-plugin-singlefile"
const WEB_BASE_URL = process.env.WEB_BASE_URL || ''; const WEB_BASE_URL = process.env.WEB_BASE_URL || '';
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:11211';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
base: WEB_BASE_URL, base: WEB_BASE_URL,
plugins: [vue(),/* viteSingleFile() */], plugins: [vue(),/* viteSingleFile() */],
server: {
proxy: {
"/api": {
target: API_BASE_URL,
},
"/api_meta.js": {
target: API_BASE_URL,
},
}
}
}) })
+3
View File
@@ -28,3 +28,6 @@ cli:
no_web: no_web:
en: "Do not run the web dashboard server" en: "Do not run the web dashboard server"
zh-CN: "不运行 web dashboard 服务器" zh-CN: "不运行 web dashboard 服务器"
api_host:
en: "The URL of the API server, used by the web frontend to connect to"
zh-CN: "API 服务器的 URL,用于 web 前端连接"
+44 -21
View File
@@ -12,7 +12,9 @@ use easytier::{
constants::EASYTIER_VERSION, constants::EASYTIER_VERSION,
error::Error, error::Error,
}, },
tunnel::{tcp::TcpTunnelListener, udp::UdpTunnelListener, TunnelListener}, tunnel::{
tcp::TcpTunnelListener, udp::UdpTunnelListener, websocket::WSTunnelListener, TunnelListener,
},
utils::{init_logger, setup_panic_handler}, utils::{init_logger, setup_panic_handler},
}; };
@@ -27,7 +29,7 @@ mod web;
rust_i18n::i18n!("locales", fallback = "en"); rust_i18n::i18n!("locales", fallback = "en");
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "easytier-core", author, version = EASYTIER_VERSION , about, long_about = None)] #[command(name = "easytier-web", author, version = EASYTIER_VERSION , about, long_about = None)]
struct Cli { struct Cli {
#[arg(short, long, default_value = "et.db", help = t!("cli.db").to_string())] #[arg(short, long, default_value = "et.db", help = t!("cli.db").to_string())]
db: String, db: String,
@@ -89,12 +91,20 @@ struct Cli {
default_value = "false" default_value = "false"
)] )]
no_web: bool, no_web: bool,
#[cfg(feature = "embed")]
#[arg(
long,
help = t!("cli.api_host").to_string()
)]
api_host: Option<url::Url>,
} }
pub fn get_listener_by_url(l: &url::Url) -> Result<Box<dyn TunnelListener>, Error> { pub fn get_listener_by_url(l: &url::Url) -> Result<Box<dyn TunnelListener>, Error> {
Ok(match l.scheme() { Ok(match l.scheme() {
"tcp" => Box::new(TcpTunnelListener::new(l.clone())), "tcp" => Box::new(TcpTunnelListener::new(l.clone())),
"udp" => Box::new(UdpTunnelListener::new(l.clone())), "udp" => Box::new(UdpTunnelListener::new(l.clone())),
"ws" => Box::new(WSTunnelListener::new(l.clone())),
_ => { _ => {
return Err(Error::InvalidUrl(l.to_string())); return Err(Error::InvalidUrl(l.to_string()));
} }
@@ -136,36 +146,49 @@ async fn main() {
let mgr = Arc::new(mgr); let mgr = Arc::new(mgr);
#[cfg(feature = "embed")] #[cfg(feature = "embed")]
let restful_also_serve_web = !cli.no_web let (web_router_restful, web_router_static) = if cli.no_web {
&& (cli.web_server_port.is_none() || cli.web_server_port == Some(cli.api_server_port)); (None, None)
} else {
let web_router = web::build_router(cli.api_host.clone());
if cli.web_server_port.is_none() || cli.web_server_port == Some(cli.api_server_port) {
(Some(web_router), None)
} else {
(None, Some(web_router))
}
};
#[cfg(not(feature = "embed"))] #[cfg(not(feature = "embed"))]
let restful_also_serve_web = false; let web_router_restful = None;
let mut restful_server = restful::RestfulServer::new( let _restful_server_tasks = restful::RestfulServer::new(
format!("0.0.0.0:{}", cli.api_server_port).parse().unwrap(), format!("0.0.0.0:{}", cli.api_server_port).parse().unwrap(),
mgr.clone(), mgr.clone(),
db, db,
restful_also_serve_web, web_router_restful,
) )
.await .await
.unwrap()
.start()
.await
.unwrap(); .unwrap();
restful_server.start().await.unwrap();
#[cfg(feature = "embed")] #[cfg(feature = "embed")]
let mut web_server = web::WebServer::new( let _web_server_task = if let Some(web_router) = web_router_static {
format!("0.0.0.0:{}", cli.web_server_port.unwrap_or(0)) Some(
.parse() web::WebServer::new(
format!("0.0.0.0:{}", cli.web_server_port.unwrap_or(0))
.parse()
.unwrap(),
web_router,
)
.await
.unwrap()
.start()
.await
.unwrap(), .unwrap(),
) )
.await } else {
.unwrap(); None
};
#[cfg(feature = "embed")]
if !cli.no_web && !restful_also_serve_web {
web_server.start().await.unwrap();
}
tokio::signal::ctrl_c().await.unwrap(); tokio::signal::ctrl_c().await.unwrap();
} }
+24 -20
View File
@@ -39,12 +39,11 @@ pub struct RestfulServer {
client_mgr: Arc<ClientManager>, client_mgr: Arc<ClientManager>,
db: Db, db: Db,
serve_task: Option<ScopedTask<()>>, // serve_task: Option<ScopedTask<()>>,
delete_task: Option<ScopedTask<tower_sessions::session_store::Result<()>>>, // delete_task: Option<ScopedTask<tower_sessions::session_store::Result<()>>>,
network_api: NetworkApi, network_api: NetworkApi,
enable_web_embed: bool, web_router: Option<Router>,
} }
type AppStateInner = Arc<ClientManager>; type AppStateInner = Arc<ClientManager>;
@@ -94,7 +93,7 @@ impl RestfulServer {
bind_addr: SocketAddr, bind_addr: SocketAddr,
client_mgr: Arc<ClientManager>, client_mgr: Arc<ClientManager>,
db: Db, db: Db,
enable_web_embed: bool, web_router: Option<Router>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
assert!(client_mgr.is_running()); assert!(client_mgr.is_running());
@@ -104,10 +103,10 @@ impl RestfulServer {
bind_addr, bind_addr,
client_mgr, client_mgr,
db, db,
serve_task: None, // serve_task: None,
delete_task: None, // delete_task: None,
network_api, network_api,
enable_web_embed, web_router,
}) })
} }
@@ -159,7 +158,15 @@ impl RestfulServer {
} }
} }
pub async fn start(&mut self) -> Result<(), anyhow::Error> { pub async fn start(
mut self,
) -> Result<
(
ScopedTask<()>,
ScopedTask<tower_sessions::session_store::Result<()>>,
),
anyhow::Error,
> {
let listener = TcpListener::bind(self.bind_addr).await?; let listener = TcpListener::bind(self.bind_addr).await?;
// Session layer. // Session layer.
@@ -169,14 +176,13 @@ impl RestfulServer {
let session_store = SqliteStore::new(self.db.inner()); let session_store = SqliteStore::new(self.db.inner());
session_store.migrate().await?; session_store.migrate().await?;
self.delete_task.replace( let delete_task: ScopedTask<tower_sessions::session_store::Result<()>> =
tokio::task::spawn( tokio::task::spawn(
session_store session_store
.clone() .clone()
.continuously_delete_expired(tokio::time::Duration::from_secs(60)), .continuously_delete_expired(tokio::time::Duration::from_secs(60)),
) )
.into(), .into();
);
// Generate a cryptographic key to sign the session cookie. // Generate a cryptographic key to sign the session cookie.
let key = Key::generate(); let key = Key::generate();
@@ -216,19 +222,17 @@ impl RestfulServer {
.layer(compression_layer); .layer(compression_layer);
#[cfg(feature = "embed")] #[cfg(feature = "embed")]
let app = if self.enable_web_embed { let app = if let Some(web_router) = self.web_router.take() {
use axum_embed::ServeEmbed; app.merge(web_router)
let service = ServeEmbed::<Assets>::new();
app.fallback_service(service)
} else { } else {
app app
}; };
let task = tokio::spawn(async move { let serve_task: ScopedTask<()> = tokio::spawn(async move {
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
}); })
self.serve_task = Some(task.into()); .into();
Ok(()) Ok((serve_task, delete_task))
} }
} }
+57 -10
View File
@@ -1,8 +1,13 @@
use axum::Router; use axum::{
extract::State,
http::header,
response::{IntoResponse, Response},
routing, Router,
};
use axum_embed::ServeEmbed;
use easytier::common::scoped_task::ScopedTask; use easytier::common::scoped_task::ScopedTask;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use std::net::SocketAddr; use std::net::SocketAddr;
use axum_embed::ServeEmbed;
use tokio::net::TcpListener; use tokio::net::TcpListener;
/// Embed assets for web dashboard, build frontend first /// Embed assets for web dashboard, build frontend first
@@ -10,30 +15,72 @@ use tokio::net::TcpListener;
#[folder = "frontend/dist/"] #[folder = "frontend/dist/"]
struct Assets; struct Assets;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ApiMetaResponse {
api_host: String,
}
async fn handle_api_meta(State(api_host): State<url::Url>) -> impl IntoResponse {
Response::builder()
.header(
header::CONTENT_TYPE,
"application/javascript; charset=utf-8",
)
.header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate")
.header(header::PRAGMA, "no-cache")
.header(header::EXPIRES, "0")
.body(format!(
"window.apiMeta = {}",
serde_json::to_string(&ApiMetaResponse {
api_host: api_host.to_string()
})
.unwrap(),
))
.unwrap()
}
pub fn build_router(api_host: Option<url::Url>) -> Router {
let service = ServeEmbed::<Assets>::new();
let router = Router::new();
let router = if let Some(api_host) = api_host {
let sub_router = Router::new()
.route("/api_meta.js", routing::get(handle_api_meta))
.with_state(api_host);
router.merge(sub_router)
} else {
router
};
let router = router.fallback_service(service);
router
}
pub struct WebServer { pub struct WebServer {
bind_addr: SocketAddr, bind_addr: SocketAddr,
router: Router,
serve_task: Option<ScopedTask<()>>, serve_task: Option<ScopedTask<()>>,
} }
impl WebServer { impl WebServer {
pub async fn new(bind_addr: SocketAddr) -> anyhow::Result<Self> { pub async fn new(bind_addr: SocketAddr, router: Router) -> anyhow::Result<Self> {
Ok(WebServer { Ok(WebServer {
bind_addr, bind_addr,
router,
serve_task: None, serve_task: None,
}) })
} }
pub async fn start(&mut self) -> Result<(), anyhow::Error> { pub async fn start(self) -> Result<ScopedTask<()>, anyhow::Error> {
let listener = TcpListener::bind(self.bind_addr).await?; let listener = TcpListener::bind(self.bind_addr).await?;
let service = ServeEmbed::<Assets>::new(); let app = self.router;
let app = Router::new().fallback_service(service);
let task = tokio::spawn(async move { let task = tokio::spawn(async move {
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
}); })
.into();
self.serve_task = Some(task.into()); Ok(task)
Ok(())
} }
} }
+6 -8
View File
@@ -3,7 +3,7 @@ name = "easytier"
description = "A full meshed p2p VPN, connecting all your devices in one network with one command." description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
homepage = "https://github.com/EasyTier/EasyTier" homepage = "https://github.com/EasyTier/EasyTier"
repository = "https://github.com/EasyTier/EasyTier" repository = "https://github.com/EasyTier/EasyTier"
version = "2.3.0" version = "2.3.1"
edition = "2021" edition = "2021"
authors = ["kkrainbow"] authors = ["kkrainbow"]
keywords = ["vpn", "p2p", "network", "easytier"] keywords = ["vpn", "p2p", "network", "easytier"]
@@ -138,6 +138,7 @@ network-interface = "2.0"
# for ospf route # for ospf route
petgraph = "0.8.1" petgraph = "0.8.1"
hashbrown = "0.15.3"
# for wireguard # for wireguard
boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true } boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true }
@@ -153,7 +154,7 @@ humansize = "2.1.3"
base64 = "0.22" base64 = "0.22"
mimalloc-rust = { git = "https://github.com/EasyTier/mimalloc-rust", optional = true } mimalloc = { version = "*", optional = true }
# mips # mips
atomic-shim = "0.2.0" atomic-shim = "0.2.0"
@@ -185,10 +186,7 @@ async-ringbuf = "0.3.1"
service-manager = { git = "https://github.com/chipsenkbeil/service-manager-rs.git", branch = "main" } service-manager = { git = "https://github.com/chipsenkbeil/service-manager-rs.git", branch = "main" }
async-compression = { version = "0.4.17", default-features = false, features = [ zstd = { version = "0.13" }
"zstd",
"tokio",
] }
kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" } kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" }
@@ -260,7 +258,7 @@ prost-reflect-build = { version = "0.14.0" }
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]
reqwest = { version = "0.12.12", features = ["blocking"] } reqwest = { version = "0.12.12", features = ["blocking"] }
zip = "0.6.6" zip = "4.0.0"
# enable thunk-rs when compiling for x86_64 or i686 windows # enable thunk-rs when compiling for x86_64 or i686 windows
[target.x86_64-pc-windows-msvc.build-dependencies] [target.x86_64-pc-windows-msvc.build-dependencies]
@@ -296,7 +294,7 @@ full = [
mips = ["aes-gcm", "mimalloc", "wireguard", "tun", "smoltcp", "socks5"] mips = ["aes-gcm", "mimalloc", "wireguard", "tun", "smoltcp", "socks5"]
wireguard = ["dep:boringtun", "dep:ring"] wireguard = ["dep:boringtun", "dep:ring"]
quic = ["dep:quinn", "dep:rustls", "dep:rcgen"] quic = ["dep:quinn", "dep:rustls", "dep:rcgen"]
mimalloc = ["dep:mimalloc-rust"] mimalloc = ["dep:mimalloc"]
aes-gcm = ["dep:aes-gcm"] aes-gcm = ["dep:aes-gcm"]
tun = ["dep:tun"] tun = ["dep:tun"]
websocket = [ websocket = [
+3
View File
@@ -155,6 +155,9 @@ core_clap:
accept_dns: accept_dns:
en: "if true, enable magic dns. with magic dns, you can access other nodes with a domain name, e.g.: <hostname>.et.net. magic dns will modify your system dns settings, enable it carefully." en: "if true, enable magic dns. with magic dns, you can access other nodes with a domain name, e.g.: <hostname>.et.net. magic dns will modify your system dns settings, enable it carefully."
zh-CN: "如果为true,则启用魔法DNS。使用魔法DNS,您可以使用域名访问其他节点,例如:<hostname>.et.net。魔法DNS将修改您的系统DNS设置,请谨慎启用。" zh-CN: "如果为true,则启用魔法DNS。使用魔法DNS,您可以使用域名访问其他节点,例如:<hostname>.et.net。魔法DNS将修改您的系统DNS设置,请谨慎启用。"
private_mode:
en: "if true, nodes with different network names or passwords from this network are not allowed to perform handshake or relay through this node."
zh-CN: "如果为true,则不允许使用了与本网络不相同的网络名称和密码的节点通过本节点进行握手或中转"
core_app: core_app:
panic_backtrace_save: panic_backtrace_save:
+46 -22
View File
@@ -1,5 +1,10 @@
use async_compression::tokio::write::{ZstdDecoder, ZstdEncoder}; use std::io::{Read, Write};
use tokio::io::AsyncWriteExt;
use dashmap::DashMap;
use std::cell::RefCell;
use zstd::stream::read::Decoder;
use zstd::stream::write::Encoder;
use zstd::zstd_safe::{CCtx, DCtx};
use zerocopy::{AsBytes as _, FromBytes as _}; use zerocopy::{AsBytes as _, FromBytes as _};
@@ -29,17 +34,20 @@ impl DefaultCompressor {
data: &[u8], data: &[u8],
compress_algo: CompressorAlgo, compress_algo: CompressorAlgo,
) -> Result<Vec<u8>, Error> { ) -> Result<Vec<u8>, Error> {
let buf = match compress_algo { match compress_algo {
CompressorAlgo::ZstdDefault => { CompressorAlgo::ZstdDefault => {
let mut o = ZstdEncoder::new(Vec::new()); let ret = CTX_MAP.with(|map_cell| {
o.write_all(data).await?; let map = map_cell.borrow();
o.shutdown().await?; let mut ctx_entry = map.entry(compress_algo).or_default();
o.into_inner() let writer = Vec::new();
let mut o = Encoder::with_context(writer, ctx_entry.value_mut());
o.write_all(data)?;
o.finish()
});
Ok(ret?)
} }
CompressorAlgo::None => data.to_vec(), CompressorAlgo::None => Ok(data.to_vec()),
}; }
Ok(buf)
} }
pub async fn decompress_raw( pub async fn decompress_raw(
@@ -47,17 +55,17 @@ impl DefaultCompressor {
data: &[u8], data: &[u8],
compress_algo: CompressorAlgo, compress_algo: CompressorAlgo,
) -> Result<Vec<u8>, Error> { ) -> Result<Vec<u8>, Error> {
let buf = match compress_algo { match compress_algo {
CompressorAlgo::ZstdDefault => { CompressorAlgo::ZstdDefault => DCTX_MAP.with(|map_cell| {
let mut o = ZstdDecoder::new(Vec::new()); let map = map_cell.borrow();
o.write_all(data).await?; let mut ctx_entry = map.entry(compress_algo).or_default();
o.shutdown().await?; let mut decoder = Decoder::with_context(data, ctx_entry.value_mut());
o.into_inner() let mut output = Vec::new();
} decoder.read_to_end(&mut output)?;
CompressorAlgo::None => data.to_vec(), Ok(output)
}; }),
CompressorAlgo::None => Ok(data.to_vec()),
Ok(buf) }
} }
} }
@@ -146,6 +154,11 @@ impl Compressor for DefaultCompressor {
} }
} }
thread_local! {
static CTX_MAP: RefCell<DashMap<CompressorAlgo, CCtx<'static>>> = RefCell::new(DashMap::new());
static DCTX_MAP: RefCell<DashMap<CompressorAlgo, DCtx<'static>>> = RefCell::new(DashMap::new());
}
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
@@ -158,10 +171,21 @@ pub mod tests {
let compressor = DefaultCompressor {}; let compressor = DefaultCompressor {};
println!(
"Uncompressed packet: {:?}, len: {}",
packet,
packet.payload_len()
);
compressor compressor
.compress(&mut packet, CompressorAlgo::ZstdDefault) .compress(&mut packet, CompressorAlgo::ZstdDefault)
.await .await
.unwrap(); .unwrap();
println!(
"Compressed packet: {:?}, len: {}",
packet,
packet.payload_len()
);
assert_eq!(packet.peer_manager_header().unwrap().is_compressed(), true); assert_eq!(packet.peer_manager_header().unwrap().is_compressed(), true);
compressor.decompress(&mut packet).await.unwrap(); compressor.decompress(&mut packet).await.unwrap();
+1
View File
@@ -37,6 +37,7 @@ pub fn gen_default_flags() -> Flags {
disable_kcp_input: false, disable_kcp_input: false,
disable_relay_kcp: true, disable_relay_kcp: true,
accept_dns: false, accept_dns: false,
private_mode: false,
} }
} }
+5 -3
View File
@@ -4,6 +4,7 @@ use std::{
io::Write as _, io::Write as _,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use time::util::refresh_tz;
use tokio::{task::JoinSet, time::timeout}; use tokio::{task::JoinSet, time::timeout};
use tracing::Instrument; use tracing::Instrument;
@@ -24,9 +25,7 @@ pub mod stun_codec_ext;
pub fn get_logger_timer<F: time::formatting::Formattable>( pub fn get_logger_timer<F: time::formatting::Formattable>(
format: F, format: F,
) -> tracing_subscriber::fmt::time::OffsetTime<F> { ) -> tracing_subscriber::fmt::time::OffsetTime<F> {
unsafe { refresh_tz();
time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound)
};
let local_offset = time::UtcOffset::current_local_offset() let local_offset = time::UtcOffset::current_local_offset()
.unwrap_or(time::UtcOffset::from_whole_seconds(0).unwrap()); .unwrap_or(time::UtcOffset::from_whole_seconds(0).unwrap());
tracing_subscriber::fmt::time::OffsetTime::new(local_offset, format) tracing_subscriber::fmt::time::OffsetTime::new(local_offset, format)
@@ -109,6 +108,9 @@ pub fn get_machine_id() -> uuid::Uuid {
))] ))]
let gen_mid = machine_uid::get() let gen_mid = machine_uid::get()
.map(|x| { .map(|x| {
if x.is_empty() {
return uuid::Uuid::new_v4();
}
let mut b = [0u8; 16]; let mut b = [0u8; 16];
crate::tunnel::generate_digest_from_str("", x.as_str(), &mut b); crate::tunnel::generate_digest_from_str("", x.as_str(), &mut b);
uuid::Uuid::from_bytes(b) uuid::Uuid::from_bytes(b)
@@ -56,8 +56,8 @@ impl From<NatType> for UdpNatType {
fn from(nat_type: NatType) -> Self { fn from(nat_type: NatType) -> Self {
match nat_type { match nat_type {
NatType::Unknown => UdpNatType::Unknown, NatType::Unknown => UdpNatType::Unknown,
NatType::NoPat | NatType::OpenInternet => UdpNatType::Open(nat_type), NatType::OpenInternet => UdpNatType::Open(nat_type),
NatType::FullCone | NatType::Restricted | NatType::PortRestricted => { NatType::NoPat | NatType::FullCone | NatType::Restricted | NatType::PortRestricted => {
UdpNatType::Cone(nat_type) UdpNatType::Cone(nat_type)
} }
NatType::Symmetric | NatType::SymUdpFirewall => UdpNatType::HardSymmetric(nat_type), NatType::Symmetric | NatType::SymUdpFirewall => UdpNatType::HardSymmetric(nat_type),
+11 -4
View File
@@ -24,7 +24,7 @@ use easytier::{
scoped_task::ScopedTask, scoped_task::ScopedTask,
stun::MockStunInfoCollector, stun::MockStunInfoCollector,
}, },
connector::{create_connector_by_url, dns_connector::DNSTunnelConnector}, connector::create_connector_by_url,
launcher, launcher,
proto::{ proto::{
self, self,
@@ -39,11 +39,11 @@ use easytier::{
windows_service::define_windows_service!(ffi_service_main, win_service_main); windows_service::define_windows_service!(ffi_service_main, win_service_main);
#[cfg(all(feature = "mimalloc", not(feature = "jemalloc")))] #[cfg(all(feature = "mimalloc", not(feature = "jemalloc")))]
use mimalloc_rust::GlobalMiMalloc; use mimalloc::MiMalloc;
#[cfg(all(feature = "mimalloc", not(feature = "jemalloc")))] #[cfg(all(feature = "mimalloc", not(feature = "jemalloc")))]
#[global_allocator] #[global_allocator]
static GLOBAL_MIMALLOC: GlobalMiMalloc = GlobalMiMalloc; static GLOBAL_MIMALLOC: MiMalloc = MiMalloc;
#[cfg(feature = "jemalloc")] #[cfg(feature = "jemalloc")]
use jemalloc_ctl::{epoch, stats, Access as _, AsName as _}; use jemalloc_ctl::{epoch, stats, Access as _, AsName as _};
@@ -452,6 +452,13 @@ struct Cli {
help = t!("core_clap.accept_dns").to_string(), help = t!("core_clap.accept_dns").to_string(),
)] )]
accept_dns: Option<bool>, accept_dns: Option<bool>,
#[arg(
long,
env = "ET_PRIVATE_MODE",
help = t!("core_clap.private_mode").to_string(),
)]
private_mode: Option<bool>,
} }
rust_i18n::i18n!("locales", fallback = "en"); rust_i18n::i18n!("locales", fallback = "en");
@@ -770,6 +777,7 @@ impl TryFrom<&Cli> for TomlConfigLoader {
f.enable_kcp_proxy = cli.enable_kcp_proxy.unwrap_or(f.enable_kcp_proxy); f.enable_kcp_proxy = cli.enable_kcp_proxy.unwrap_or(f.enable_kcp_proxy);
f.disable_kcp_input = cli.disable_kcp_input.unwrap_or(f.disable_kcp_input); f.disable_kcp_input = cli.disable_kcp_input.unwrap_or(f.disable_kcp_input);
f.accept_dns = cli.accept_dns.unwrap_or(f.accept_dns); f.accept_dns = cli.accept_dns.unwrap_or(f.accept_dns);
f.private_mode = cli.private_mode.unwrap_or(f.private_mode);
cfg.set_flags(f); cfg.set_flags(f);
if !cli.exit_nodes.is_empty() { if !cli.exit_nodes.is_empty() {
@@ -1079,7 +1087,6 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
hostname, hostname,
); );
tokio::signal::ctrl_c().await.unwrap(); tokio::signal::ctrl_c().await.unwrap();
DNSTunnelConnector::new("".parse().unwrap(), global_ctx);
return Ok(()); return Ok(());
} }
+4
View File
@@ -676,6 +676,10 @@ impl NetworkConfig {
flags.mtu = mtu as u32; flags.mtu = mtu as u32;
} }
if let Some(enable_private_mode) = self.enable_private_mode {
flags.private_mode = enable_private_mode;
}
cfg.set_flags(flags); cfg.set_flags(flags);
Ok(cfg) Ok(cfg)
} }
+179
View File
@@ -0,0 +1,179 @@
use core::cmp::Ordering;
use hashbrown::hash_map::{
Entry::{Occupied, Vacant},
HashMap,
};
use petgraph::{
algo::Measure,
visit::{EdgeRef as _, IntoEdges, VisitMap as _, Visitable},
};
use std::{collections::BinaryHeap, hash::Hash};
/// `MinScored<K, T>` holds a score `K` and a scored object `T` in
/// a pair for use with a `BinaryHeap`.
///
/// `MinScored` compares in reverse order by the score, so that we can
/// use `BinaryHeap` as a min-heap to extract the score-value pair with the
/// least score.
///
/// **Note:** `MinScored` implements a total order (`Ord`), so that it is
/// possible to use float types as scores.
#[derive(Copy, Clone, Debug)]
pub struct MinScored<K, T>(pub K, pub T);
impl<K: PartialOrd, T> PartialEq for MinScored<K, T> {
#[inline]
fn eq(&self, other: &MinScored<K, T>) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl<K: PartialOrd, T> Eq for MinScored<K, T> {}
impl<K: PartialOrd, T> PartialOrd for MinScored<K, T> {
#[inline]
fn partial_cmp(&self, other: &MinScored<K, T>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<K: PartialOrd, T> Ord for MinScored<K, T> {
#[inline]
fn cmp(&self, other: &MinScored<K, T>) -> Ordering {
let a = &self.0;
let b = &other.0;
if a == b {
Ordering::Equal
} else if a < b {
Ordering::Greater
} else if a > b {
Ordering::Less
} else if a.ne(a) && b.ne(b) {
// these are the NaN cases
Ordering::Equal
} else if a.ne(a) {
// Order NaN less, so that it is last in the MinScore order
Ordering::Less
} else {
Ordering::Greater
}
}
}
pub fn dijkstra_with_first_hop<G, F, K>(
graph: G,
start: G::NodeId,
mut edge_cost: F,
) -> (
HashMap<G::NodeId, K>,
HashMap<G::NodeId, (G::NodeId, usize)>,
)
where
G: IntoEdges + Visitable,
G::NodeId: Eq + Hash + Clone,
F: FnMut(G::EdgeRef) -> K,
K: Measure + Copy,
{
let mut visited = graph.visit_map();
let mut scores = HashMap::new();
let mut first_hop = HashMap::new();
let mut visit_next = BinaryHeap::new();
let zero_score = K::default();
scores.insert(start.clone(), zero_score);
visit_next.push(MinScored(zero_score, start.clone()));
first_hop.insert(start.clone(), (start.clone(), 0));
while let Some(MinScored(node_score, node)) = visit_next.pop() {
if visited.is_visited(&node) {
continue;
}
for edge in graph.edges(node.clone()) {
let next = edge.target();
if visited.is_visited(&next) {
continue;
}
let next_score = node_score + edge_cost(edge);
match scores.entry(next.clone()) {
Occupied(mut ent) => {
if next_score < *ent.get() {
*ent.get_mut() = next_score;
visit_next.push(MinScored(next_score, next.clone()));
// 继承前驱的 first_hop,或自己就是第一跳
let hop = if node == start {
(next.clone(), 0)
} else {
first_hop[&node].clone()
};
first_hop.insert(next.clone(), (hop.0, hop.1 + 1));
}
}
Vacant(ent) => {
ent.insert(next_score);
visit_next.push(MinScored(next_score, next.clone()));
let hop = if node == start {
(next.clone(), 0)
} else {
first_hop[&node].clone()
};
first_hop.insert(next.clone(), (hop.0, hop.1 + 1));
}
}
}
visited.visit(node);
}
(scores, first_hop)
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::graph::DiGraph;
#[test]
fn test_dijkstra_with_first_hop_4node() {
let mut graph = DiGraph::<&str, u32>::new();
let a = graph.add_node("a");
let b = graph.add_node("b");
let c = graph.add_node("c");
let d = graph.add_node("d");
graph.extend_with_edges(&[(a, b, 1)]);
graph.extend_with_edges(&[(b, c, 1)]);
graph.extend_with_edges(&[(c, d, 2)]);
let (scores, first_hop) = dijkstra_with_first_hop(&graph, a, |edge| *edge.weight());
assert_eq!(scores[&b], 1);
assert_eq!(scores[&c], 2);
assert_eq!(scores[&d], 4);
assert_eq!(first_hop[&b], (b, 1));
assert_eq!(first_hop[&c], (b, 2));
assert_eq!(first_hop[&d], (b, 3));
}
#[test]
fn test_dijkstra_with_first_hop() {
let mut graph = DiGraph::<&str, u32>::new();
let a = graph.add_node("a");
let b = graph.add_node("b");
let c = graph.add_node("c");
let d = graph.add_node("d");
let e = graph.add_node("e");
graph.extend_with_edges(&[(a, b, 1), (a, c, 2), (b, d, 1), (c, d, 3), (d, e, 1)]);
let (scores, first_hop) = dijkstra_with_first_hop(&graph, a, |edge| *edge.weight());
assert_eq!(scores[&b], 1);
assert_eq!(scores[&c], 2);
assert_eq!(scores[&d], 2);
assert_eq!(scores[&e], 3);
assert_eq!(first_hop[&b], (b, 1));
assert_eq!(first_hop[&c], (c, 1));
assert_eq!(first_hop[&d], (b, 2)); // d is reached via b
assert_eq!(first_hop[&e], (b, 3)); // e is reached via d
}
}
+2
View File
@@ -1,3 +1,5 @@
mod graph_algo;
pub mod peer; pub mod peer;
// pub mod peer_conn; // pub mod peer_conn;
pub mod peer_conn; pub mod peer_conn;
+19 -10
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use dashmap::DashMap; use dashmap::DashMap;
use tokio::{select, sync::mpsc, task::JoinHandle}; use tokio::{select, sync::mpsc};
use tracing::Instrument; use tracing::Instrument;
@@ -32,7 +32,7 @@ pub struct Peer {
packet_recv_chan: PacketRecvChan, packet_recv_chan: PacketRecvChan,
close_event_sender: mpsc::Sender<PeerConnId>, close_event_sender: mpsc::Sender<PeerConnId>,
close_event_listener: JoinHandle<()>, close_event_listener: ScopedTask<()>,
shutdown_notifier: Arc<tokio::sync::Notify>, shutdown_notifier: Arc<tokio::sync::Notify>,
@@ -87,7 +87,8 @@ impl Peer {
"peer_close_event_listener", "peer_close_event_listener",
?peer_node_id, ?peer_node_id,
)), )),
); )
.into();
let default_conn_id = Arc::new(AtomicCell::new(PeerConnId::default())); let default_conn_id = Arc::new(AtomicCell::new(PeerConnId::default()));
@@ -118,8 +119,14 @@ impl Peer {
} }
pub async fn add_peer_conn(&self, mut conn: PeerConn) { pub async fn add_peer_conn(&self, mut conn: PeerConn) {
let close_event_sender = self.close_event_sender.clone();
let close_notifier = conn.get_close_notifier(); let close_notifier = conn.get_close_notifier();
let conn_info = conn.get_conn_info();
conn.start_recv_loop(self.packet_recv_chan.clone()).await;
conn.start_pingpong();
self.conns.insert(conn.get_conn_id(), Arc::new(conn));
let close_event_sender = self.close_event_sender.clone();
tokio::spawn(async move { tokio::spawn(async move {
let conn_id = close_notifier.get_conn_id(); let conn_id = close_notifier.get_conn_id();
if let Some(mut waiter) = close_notifier.get_waiter().await { if let Some(mut waiter) = close_notifier.get_waiter().await {
@@ -130,12 +137,8 @@ impl Peer {
} }
}); });
conn.start_recv_loop(self.packet_recv_chan.clone()).await;
conn.start_pingpong();
self.global_ctx self.global_ctx
.issue_event(GlobalCtxEvent::PeerConnAdded(conn.get_conn_info())); .issue_event(GlobalCtxEvent::PeerConnAdded(conn_info));
self.conns.insert(conn.get_conn_id(), Arc::new(conn));
} }
async fn select_conn(&self) -> Option<ArcPeerConn> { async fn select_conn(&self) -> Option<ArcPeerConn> {
@@ -186,7 +189,13 @@ impl Peer {
let mut ret = Vec::new(); let mut ret = Vec::new();
for conn in conns { for conn in conns {
ret.push(conn.get_conn_info()); let info = conn.get_conn_info();
if !info.is_closed {
ret.push(info);
} else {
let conn_id = info.conn_id.parse().unwrap();
let _ = self.close_peer_conn(&conn_id).await;
}
} }
ret ret
} }
+1
View File
@@ -432,6 +432,7 @@ impl PeerConn {
loss_rate: (f64::from(self.loss_rate_stats.load(Ordering::Relaxed)) / 100.0) as f32, loss_rate: (f64::from(self.loss_rate_stats.load(Ordering::Relaxed)) / 100.0) as f32,
is_client: self.is_client.unwrap_or_default(), is_client: self.is_client.unwrap_or_default(),
network_name: info.network_name.clone(), network_name: info.network_name.clone(),
is_closed: self.close_event_notifier.is_closed(),
} }
} }
} }
+2 -2
View File
@@ -55,7 +55,7 @@ impl std::fmt::Debug for PingIntervalController {
impl PingIntervalController { impl PingIntervalController {
fn new(throughput: Arc<Throughput>, loss_counter: Arc<AtomicU32>) -> Self { fn new(throughput: Arc<Throughput>, loss_counter: Arc<AtomicU32>) -> Self {
let last_throughput = *throughput; let last_throughput = (*throughput).clone();
Self { Self {
throughput, throughput,
@@ -92,7 +92,7 @@ impl PingIntervalController {
self.backoff_idx = 0; self.backoff_idx = 0;
} }
self.last_throughput = *self.throughput; self.last_throughput = (*self.throughput).clone();
if (self.logic_time - self.last_send_logic_time) < (1 << self.backoff_idx) { if (self.logic_time - self.last_send_logic_time) < (1 << self.backoff_idx) {
return false; return false;
+54 -2
View File
@@ -422,6 +422,14 @@ impl PeerManager {
tracing::info!("add tunnel as server start"); tracing::info!("add tunnel as server start");
let mut peer = PeerConn::new(self.my_peer_id, self.global_ctx.clone(), tunnel); let mut peer = PeerConn::new(self.my_peer_id, self.global_ctx.clone(), tunnel);
peer.do_handshake_as_server().await?; peer.do_handshake_as_server().await?;
if self.global_ctx.config.get_flags().private_mode
&& peer.get_network_identity().network_name
!= self.global_ctx.get_network_identity().network_name
{
return Err(Error::SecretKeyError(
"private mode is turned on, network identity not match".to_string(),
));
}
if peer.get_network_identity().network_name if peer.get_network_identity().network_name
== self.global_ctx.get_network_identity().network_name == self.global_ctx.get_network_identity().network_name
{ {
@@ -1078,7 +1086,8 @@ mod tests {
use crate::{ use crate::{
common::{config::Flags, global_ctx::tests::get_mock_global_ctx}, common::{config::Flags, global_ctx::tests::get_mock_global_ctx},
connector::{ connector::{
create_connector_by_url, udp_hole_punch::tests::create_mock_peer_manager_with_mock_stun, create_connector_by_url, direct::PeerManagerForDirectConnector,
udp_hole_punch::tests::create_mock_peer_manager_with_mock_stun,
}, },
instance::listeners::get_listener_by_url, instance::listeners::get_listener_by_url,
peers::{ peers::{
@@ -1089,7 +1098,12 @@ mod tests {
tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost}, tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost},
}, },
proto::common::{CompressionAlgoPb, NatType, PeerFeatureFlag}, proto::common::{CompressionAlgoPb, NatType, PeerFeatureFlag},
tunnel::{common::tests::wait_for_condition, TunnelConnector, TunnelListener}, tunnel::{
common::tests::wait_for_condition,
filter::{tests::DropSendTunnelFilter, TunnelWithFilter},
ring::create_ring_tunnel_pair,
TunnelConnector, TunnelListener,
},
}; };
use super::PeerManager; use super::PeerManager;
@@ -1268,6 +1282,12 @@ mod tests {
let peer_mgr_d = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await; let peer_mgr_d = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
let peer_mgr_e = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await; let peer_mgr_e = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
println!("peer_mgr_a: {}", peer_mgr_a.my_peer_id);
println!("peer_mgr_b: {}", peer_mgr_b.my_peer_id);
println!("peer_mgr_c: {}", peer_mgr_c.my_peer_id);
println!("peer_mgr_d: {}", peer_mgr_d.my_peer_id);
println!("peer_mgr_e: {}", peer_mgr_e.my_peer_id);
connect_peer_manager(peer_mgr_a.clone(), peer_mgr_b.clone()).await; connect_peer_manager(peer_mgr_a.clone(), peer_mgr_b.clone()).await;
connect_peer_manager(peer_mgr_b.clone(), peer_mgr_c.clone()).await; connect_peer_manager(peer_mgr_b.clone(), peer_mgr_c.clone()).await;
@@ -1323,4 +1343,36 @@ mod tests {
.await; .await;
assert_eq!(ret, Some(peer_mgr_b.my_peer_id)); assert_eq!(ret, Some(peer_mgr_b.my_peer_id));
} }
#[tokio::test]
async fn test_client_inbound_blackhole() {
let peer_mgr_a = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
let peer_mgr_b = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
// a is client, b is server
let (a_ring, b_ring) = create_ring_tunnel_pair();
let a_ring = Box::new(TunnelWithFilter::new(
a_ring,
DropSendTunnelFilter::new(2, 50000),
));
let a_mgr_copy = peer_mgr_a.clone();
tokio::spawn(async move {
a_mgr_copy.add_client_tunnel(a_ring).await.unwrap();
});
let b_mgr_copy = peer_mgr_b.clone();
tokio::spawn(async move {
b_mgr_copy.add_tunnel_as_server(b_ring, true).await.unwrap();
});
wait_for_condition(
|| async {
let peers = peer_mgr_a.list_peers().await;
peers.is_empty()
},
Duration::from_secs(10),
)
.await;
}
} }
+291 -221
View File
@@ -1,7 +1,6 @@
use std::{ use std::{
collections::BTreeSet, collections::BTreeSet,
fmt::Debug, fmt::Debug,
hash::RandomState,
net::Ipv4Addr, net::Ipv4Addr,
sync::{ sync::{
atomic::{AtomicBool, AtomicU32, Ordering}, atomic::{AtomicBool, AtomicU32, Ordering},
@@ -13,9 +12,10 @@ use std::{
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use dashmap::DashMap; use dashmap::DashMap;
use petgraph::{ use petgraph::{
algo::{all_simple_paths, astar, dijkstra}, algo::dijkstra,
graph::NodeIndex, graph::{Graph, NodeIndex},
Directed, Graph, visit::{EdgeRef, IntoNodeReferences},
Directed,
}; };
use prost::Message; use prost::Message;
use prost_reflect::{DynamicMessage, ReflectMessage}; use prost_reflect::{DynamicMessage, ReflectMessage};
@@ -49,6 +49,7 @@ use crate::{
}; };
use super::{ use super::{
graph_algo::dijkstra_with_first_hop,
peer_rpc::PeerRpcManager, peer_rpc::PeerRpcManager,
route_trait::{ route_trait::{
DefaultRouteCostCalculator, ForeignNetworkRouteInfoMap, NextHopPolicy, RouteCostCalculator, DefaultRouteCostCalculator, ForeignNetworkRouteInfoMap, NextHopPolicy, RouteCostCalculator,
@@ -60,7 +61,8 @@ use super::{
static SERVICE_ID: u32 = 7; static SERVICE_ID: u32 = 7;
static UPDATE_PEER_INFO_PERIOD: Duration = Duration::from_secs(3600); static UPDATE_PEER_INFO_PERIOD: Duration = Duration::from_secs(3600);
static REMOVE_DEAD_PEER_INFO_AFTER: Duration = Duration::from_secs(3660); static REMOVE_DEAD_PEER_INFO_AFTER: Duration = Duration::from_secs(3660);
static AVOID_RELAY_COST: i32 = i32::MAX / 512; // the cost (latency between two peers) is i32, i32::MAX is large enough.
static AVOID_RELAY_COST: usize = i32::MAX as usize;
type Version = u32; type Version = u32;
@@ -80,14 +82,13 @@ impl AtomicVersion {
self.0.store(version, Ordering::Relaxed); self.0.store(version, Ordering::Relaxed);
} }
fn inc(&self) { fn inc(&self) -> Version {
self.0.fetch_add(1, Ordering::Relaxed); self.0.fetch_add(1, Ordering::Relaxed) + 1
} }
fn set_if_larger(&self, version: Version) { fn set_if_larger(&self, version: Version) -> bool {
if self.get() < version { // return true if the version is set.
self.set(version); self.0.fetch_max(version, Ordering::Relaxed) < version
}
} }
} }
@@ -283,13 +284,25 @@ impl RouteConnBitmap {
type Error = SyncRouteInfoError; type Error = SyncRouteInfoError;
// constructed with all infos synced from all peers. // constructed with all infos synced from all peers.
#[derive(Debug)]
struct SyncedRouteInfo { struct SyncedRouteInfo {
peer_infos: DashMap<PeerId, RoutePeerInfo>, peer_infos: DashMap<PeerId, RoutePeerInfo>,
// prost doesn't support unknown fields, so we use DynamicMessage to store raw infos and progate them to other peers. // prost doesn't support unknown fields, so we use DynamicMessage to store raw infos and progate them to other peers.
raw_peer_infos: DashMap<PeerId, DynamicMessage>, raw_peer_infos: DashMap<PeerId, DynamicMessage>,
conn_map: DashMap<PeerId, (BTreeSet<PeerId>, AtomicVersion)>, conn_map: DashMap<PeerId, (BTreeSet<PeerId>, AtomicVersion)>,
foreign_network: DashMap<ForeignNetworkRouteInfoKey, ForeignNetworkRouteInfoEntry>, foreign_network: DashMap<ForeignNetworkRouteInfoKey, ForeignNetworkRouteInfoEntry>,
version: AtomicVersion,
}
impl Debug for SyncedRouteInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyncedRouteInfo")
.field("peer_infos", &self.peer_infos)
.field("conn_map", &self.conn_map)
.field("foreign_network", &self.foreign_network)
.field("version", &self.version.get())
.finish()
}
} }
impl SyncedRouteInfo { impl SyncedRouteInfo {
@@ -305,17 +318,24 @@ impl SyncedRouteInfo {
self.raw_peer_infos.remove(&peer_id); self.raw_peer_infos.remove(&peer_id);
self.conn_map.remove(&peer_id); self.conn_map.remove(&peer_id);
self.foreign_network.retain(|k, _| k.peer_id != peer_id); self.foreign_network.retain(|k, _| k.peer_id != peer_id);
self.version.inc();
} }
fn fill_empty_peer_info(&self, peer_ids: &BTreeSet<PeerId>) { fn fill_empty_peer_info(&self, peer_ids: &BTreeSet<PeerId>) {
let mut need_inc_version = false;
for peer_id in peer_ids { for peer_id in peer_ids {
self.peer_infos self.peer_infos.entry(*peer_id).or_insert_with(|| {
.entry(*peer_id) need_inc_version = true;
.or_insert_with(|| RoutePeerInfo::new()); RoutePeerInfo::new()
});
self.conn_map self.conn_map.entry(*peer_id).or_insert_with(|| {
.entry(*peer_id) need_inc_version = true;
.or_insert_with(|| (BTreeSet::new(), AtomicVersion::new())); (BTreeSet::new(), AtomicVersion::new())
});
}
if need_inc_version {
self.version.inc();
} }
} }
@@ -377,6 +397,7 @@ impl SyncedRouteInfo {
peer_infos: &Vec<RoutePeerInfo>, peer_infos: &Vec<RoutePeerInfo>,
raw_peer_infos: &Vec<DynamicMessage>, raw_peer_infos: &Vec<DynamicMessage>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut need_inc_version = false;
for (idx, route_info) in peer_infos.iter().enumerate() { for (idx, route_info) in peer_infos.iter().enumerate() {
let mut route_info = route_info.clone(); let mut route_info = route_info.clone();
let raw_route_info = &raw_peer_infos[idx]; let raw_route_info = &raw_peer_infos[idx];
@@ -410,22 +431,28 @@ impl SyncedRouteInfo {
self.raw_peer_infos self.raw_peer_infos
.insert(route_info.peer_id, raw_route_info.clone()); .insert(route_info.peer_id, raw_route_info.clone());
*old_entry = route_info.clone(); *old_entry = route_info.clone();
need_inc_version = true;
} }
}) })
.or_insert_with(|| { .or_insert_with(|| {
need_inc_version = true;
self.raw_peer_infos self.raw_peer_infos
.insert(route_info.peer_id, raw_route_info.clone()); .insert(route_info.peer_id, raw_route_info.clone());
route_info.clone() route_info.clone()
}); });
} }
if need_inc_version {
self.version.inc();
}
Ok(()) Ok(())
} }
fn update_conn_map(&self, conn_bitmap: &RouteConnBitmap) { fn update_conn_map(&self, conn_bitmap: &RouteConnBitmap) {
self.fill_empty_peer_info(&conn_bitmap.peer_ids.iter().map(|x| x.0).collect()); self.fill_empty_peer_info(&conn_bitmap.peer_ids.iter().map(|x| x.0).collect());
let mut need_inc_version = false;
for (peer_idx, (peer_id, version)) in conn_bitmap.peer_ids.iter().enumerate() { for (peer_idx, (peer_id, version)) in conn_bitmap.peer_ids.iter().enumerate() {
assert!(self.peer_infos.contains_key(peer_id));
let connceted_peers = conn_bitmap.get_connected_peers(peer_idx); let connceted_peers = conn_bitmap.get_connected_peers(peer_idx);
self.fill_empty_peer_info(&connceted_peers); self.fill_empty_peer_info(&connceted_peers);
@@ -433,17 +460,19 @@ impl SyncedRouteInfo {
.entry(*peer_id) .entry(*peer_id)
.and_modify(|(old_conn_bitmap, old_version)| { .and_modify(|(old_conn_bitmap, old_version)| {
if *version > old_version.get() { if *version > old_version.get() {
*old_conn_bitmap = conn_bitmap.get_connected_peers(peer_idx); *old_conn_bitmap = connceted_peers.clone();
need_inc_version = true;
old_version.set(*version); old_version.set(*version);
} }
}) })
.or_insert_with(|| { .or_insert_with(|| {
( need_inc_version = true;
conn_bitmap.get_connected_peers(peer_idx), (connceted_peers, version.clone().into())
version.clone().into(),
)
}); });
} }
if need_inc_version {
self.version.inc();
}
} }
fn update_foreign_network(&self, foreign_network: &RouteForeignNetworkInfos) { fn update_foreign_network(&self, foreign_network: &RouteForeignNetworkInfos) {
@@ -483,7 +512,12 @@ impl SyncedRouteInfo {
let old_version = old.version; let old_version = old.version;
*old = new; *old = new;
new_version != old_version if new_version != old_version {
self.version.inc();
true
} else {
false
}
} }
fn update_my_conn_info(&self, my_peer_id: PeerId, connected_peers: BTreeSet<PeerId>) -> bool { fn update_my_conn_info(&self, my_peer_id: PeerId, connected_peers: BTreeSet<PeerId>) -> bool {
@@ -499,6 +533,7 @@ impl SyncedRouteInfo {
} else { } else {
let _ = std::mem::replace(&mut my_conn_info.value_mut().0, connected_peers); let _ = std::mem::replace(&mut my_conn_info.value_mut().0, connected_peers);
my_conn_info.value().1.inc(); my_conn_info.value().1.inc();
self.version.inc();
true true
} }
} }
@@ -557,6 +592,10 @@ impl SyncedRouteInfo {
updated = true; updated = true;
} }
if updated {
self.version.inc();
}
updated updated
} }
@@ -573,13 +612,14 @@ impl SyncedRouteInfo {
} }
} }
type PeerGraph = Graph<PeerId, i32, Directed>; type PeerGraph = Graph<PeerId, usize, Directed>;
type PeerIdToNodexIdxMap = DashMap<PeerId, NodeIndex>; type PeerIdToNodexIdxMap = DashMap<PeerId, NodeIndex>;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct NextHopInfo { struct NextHopInfo {
next_hop_peer_id: PeerId, next_hop_peer_id: PeerId,
path_latency: i32, path_latency: i32,
path_len: usize, // path includes src and dst. path_len: usize, // path includes src and dst.
version: Version,
} }
// dst_peer_id -> (next_hop_peer_id, cost, path_len) // dst_peer_id -> (next_hop_peer_id, cost, path_len)
type NextHopMap = DashMap<PeerId, NextHopInfo>; type NextHopMap = DashMap<PeerId, NextHopInfo>;
@@ -591,6 +631,7 @@ struct RouteTable {
next_hop_map: NextHopMap, next_hop_map: NextHopMap,
ipv4_peer_id_map: DashMap<Ipv4Addr, PeerId>, ipv4_peer_id_map: DashMap<Ipv4Addr, PeerId>,
cidr_peer_id_map: DashMap<cidr::IpCidr, PeerId>, cidr_peer_id_map: DashMap<cidr::IpCidr, PeerId>,
next_hop_map_version: AtomicVersion,
} }
impl RouteTable { impl RouteTable {
@@ -600,15 +641,23 @@ impl RouteTable {
next_hop_map: DashMap::new(), next_hop_map: DashMap::new(),
ipv4_peer_id_map: DashMap::new(), ipv4_peer_id_map: DashMap::new(),
cidr_peer_id_map: DashMap::new(), cidr_peer_id_map: DashMap::new(),
next_hop_map_version: AtomicVersion::new(),
} }
} }
fn get_next_hop(&self, dst_peer_id: PeerId) -> Option<NextHopInfo> { fn get_next_hop(&self, dst_peer_id: PeerId) -> Option<NextHopInfo> {
self.next_hop_map.get(&dst_peer_id).map(|x| *x) let cur_version = self.next_hop_map_version.get();
self.next_hop_map.get(&dst_peer_id).and_then(|x| {
if x.version >= cur_version {
Some(*x)
} else {
None
}
})
} }
fn peer_reachable(&self, peer_id: PeerId) -> bool { fn peer_reachable(&self, peer_id: PeerId) -> bool {
self.next_hop_map.contains_key(&peer_id) self.get_next_hop(peer_id).is_some()
} }
fn get_nat_type(&self, peer_id: PeerId) -> Option<NatType> { fn get_nat_type(&self, peer_id: PeerId) -> Option<NatType> {
@@ -617,158 +666,16 @@ impl RouteTable {
.map(|x| NatType::try_from(x.udp_stun_info as i32).unwrap_or_default()) .map(|x| NatType::try_from(x.udp_stun_info as i32).unwrap_or_default())
} }
// return graph and start node index (node of my peer id).
fn build_peer_graph_from_synced_info<T: RouteCostCalculatorInterface>( fn build_peer_graph_from_synced_info<T: RouteCostCalculatorInterface>(
peers: Vec<PeerId>,
synced_info: &SyncedRouteInfo,
cost_calc: &mut T,
) -> (PeerGraph, PeerIdToNodexIdxMap) {
let mut graph: PeerGraph = Graph::new();
let peer_id_to_node_index = PeerIdToNodexIdxMap::new();
for peer_id in peers.iter() {
peer_id_to_node_index.insert(*peer_id, graph.add_node(*peer_id));
}
for peer_id in peers.iter() {
let connected_peers = synced_info
.get_connected_peers(*peer_id)
.unwrap_or(BTreeSet::new());
// if avoid relay, just set all outgoing edges to a large value: AVOID_RELAY_COST.
let peer_avoid_relay_data = synced_info.get_avoid_relay_data(*peer_id);
for dst_peer_id in connected_peers.iter() {
let Some(dst_idx) = peer_id_to_node_index.get(dst_peer_id) else {
continue;
};
graph.add_edge(
*peer_id_to_node_index.get(&peer_id).unwrap(),
*dst_idx,
if peer_avoid_relay_data {
AVOID_RELAY_COST
} else {
cost_calc.calculate_cost(*peer_id, *dst_peer_id)
},
);
}
}
(graph, peer_id_to_node_index)
}
fn gen_next_hop_map_with_least_hop<T: RouteCostCalculatorInterface>(
my_peer_id: PeerId,
graph: &PeerGraph,
idx_map: &PeerIdToNodexIdxMap,
cost_calc: &mut T,
) -> NextHopMap {
let res = dijkstra(&graph, *idx_map.get(&my_peer_id).unwrap(), None, |_| 1);
let next_hop_map = NextHopMap::new();
for (node_idx, cost) in res.iter() {
if *cost == 0 {
continue;
}
let mut all_paths = all_simple_paths::<Vec<_>, _, RandomState>(
graph,
*idx_map.get(&my_peer_id).unwrap(),
*node_idx,
*cost - 1,
Some(*cost + 1), // considering having avoid relay, the max cost could be a bit larger.
)
.collect::<Vec<_>>();
assert!(!all_paths.is_empty());
all_paths.sort_by(|a, b| a.len().cmp(&b.len()));
// find a path with least cost.
let mut min_cost = i32::MAX;
let mut min_path_len = usize::MAX;
let mut min_path = Vec::new();
for path in all_paths.iter() {
if min_path_len < path.len() && min_cost < AVOID_RELAY_COST {
// the min path does not contain avoid relay node.
break;
}
let mut cost = 0;
for i in 0..path.len() - 1 {
let src_peer_id = *graph.node_weight(path[i]).unwrap();
let dst_peer_id = *graph.node_weight(path[i + 1]).unwrap();
let edge_weight = *graph
.edge_weight(graph.find_edge(path[i], path[i + 1]).unwrap())
.unwrap();
if edge_weight != 1 {
// means avoid relay.
cost += edge_weight;
} else {
cost += cost_calc.calculate_cost(src_peer_id, dst_peer_id);
}
}
if cost <= min_cost {
min_cost = cost;
min_path = path.clone();
min_path_len = path.len();
}
}
next_hop_map.insert(
*graph.node_weight(*node_idx).unwrap(),
NextHopInfo {
next_hop_peer_id: *graph.node_weight(min_path[1]).unwrap(),
path_latency: min_cost,
path_len: min_path_len,
},
);
}
next_hop_map
}
fn gen_next_hop_map_with_least_cost(
my_peer_id: PeerId,
graph: &PeerGraph,
idx_map: &PeerIdToNodexIdxMap,
) -> NextHopMap {
let next_hop_map = NextHopMap::new();
for item in idx_map.iter() {
if *item.key() == my_peer_id {
continue;
}
let dst_peer_node_idx = *item.value();
let Some((cost, path)) = astar::astar(
graph,
*idx_map.get(&my_peer_id).unwrap(),
|node_idx| node_idx == dst_peer_node_idx,
|e| *e.weight(),
|_| 0,
) else {
continue;
};
next_hop_map.insert(
*item.key(),
NextHopInfo {
next_hop_peer_id: *graph.node_weight(path[1]).unwrap(),
path_latency: cost,
path_len: path.len(),
},
);
}
next_hop_map
}
fn build_from_synced_info<T: RouteCostCalculatorInterface>(
&self,
my_peer_id: PeerId, my_peer_id: PeerId,
synced_info: &SyncedRouteInfo, synced_info: &SyncedRouteInfo,
policy: NextHopPolicy, cost_calc: &T,
mut cost_calc: T, ) -> (PeerGraph, NodeIndex) {
) { let mut graph: PeerGraph = PeerGraph::new();
// build peer_infos
self.peer_infos.clear(); let mut start_node_idx = None;
let peer_id_to_node_index: PeerIdToNodexIdxMap = DashMap::new();
for item in synced_info.peer_infos.iter() { for item in synced_info.peer_infos.iter() {
let peer_id = item.key(); let peer_id = item.key();
let info = item.value(); let info = item.value();
@@ -777,49 +684,175 @@ impl RouteTable {
continue; continue;
} }
self.peer_infos.insert(*peer_id, info.clone()); let node_idx = graph.add_node(*peer_id);
peer_id_to_node_index.insert(*peer_id, node_idx);
if *peer_id == my_peer_id {
start_node_idx = Some(node_idx);
}
} }
if self.peer_infos.is_empty() { if start_node_idx.is_none() {
return (graph, NodeIndex::end());
}
for item in peer_id_to_node_index.iter() {
let src_peer_id = item.key();
let src_node_idx = item.value();
let connected_peers = synced_info
.get_connected_peers(*src_peer_id)
.unwrap_or(BTreeSet::new());
// if avoid relay, just set all outgoing edges to a large value: AVOID_RELAY_COST.
let peer_avoid_relay_data = synced_info.get_avoid_relay_data(*src_peer_id);
for dst_peer_id in connected_peers.iter() {
let Some(dst_node_idx) = peer_id_to_node_index.get(dst_peer_id) else {
continue;
};
let mut cost = cost_calc.calculate_cost(*src_peer_id, *dst_peer_id) as usize;
if peer_avoid_relay_data {
cost += AVOID_RELAY_COST;
}
graph.add_edge(*src_node_idx, *dst_node_idx, cost);
}
}
(graph, start_node_idx.unwrap())
}
fn clean_expired_route_info(&self) {
let cur_version = self.next_hop_map_version.get();
self.next_hop_map.retain(|_, v| {
// remove next hop map for peers we cannot reach.
v.version >= cur_version
});
self.peer_infos.retain(|k, _| {
// remove peer info for peers we cannot reach.
self.next_hop_map.contains_key(k)
});
self.ipv4_peer_id_map.retain(|_, v| {
// remove ipv4 map for peers we cannot reach.
self.next_hop_map.contains_key(v)
});
self.cidr_peer_id_map.retain(|_, v| {
// remove cidr map for peers we cannot reach.
self.next_hop_map.contains_key(v)
});
}
fn gen_next_hop_map_with_least_hop(
&self,
graph: &PeerGraph,
start_node: &NodeIndex,
version: Version,
) {
let normalize_edge_cost = |e: petgraph::graph::EdgeReference<usize>| {
if *e.weight() >= AVOID_RELAY_COST {
AVOID_RELAY_COST + 1
} else {
1
}
};
// Step 1: 第一次 Dijkstra - 计算最短跳数
let path_len_map = dijkstra(&graph, *start_node, None, normalize_edge_cost);
// Step 2: 构建最短跳数子图(只保留属于最短路径和 AVOID RELAY 的边)
let mut subgraph: PeerGraph = PeerGraph::new();
let mut start_node_idx = None;
for (node_idx, peer_id) in graph.node_references() {
let new_node_idx = subgraph.add_node(*peer_id);
if node_idx == *start_node {
start_node_idx = Some(new_node_idx);
}
}
for edge in graph.edge_references() {
let (src, tgt) = graph.edge_endpoints(edge.id()).unwrap();
let Some(src_path_len) = path_len_map.get(&src) else {
continue;
};
let Some(tgt_path_len) = path_len_map.get(&tgt) else {
continue;
};
if *src_path_len + normalize_edge_cost(edge) == *tgt_path_len {
subgraph.add_edge(src, tgt, *edge.weight());
}
}
// Step 3: 第二次 Dijkstra - 在子图上找代价最小的路径
self.gen_next_hop_map_with_least_cost(&subgraph, &start_node_idx.clone().unwrap(), version);
}
fn gen_next_hop_map_with_least_cost(
&self,
graph: &PeerGraph,
start_node: &NodeIndex,
version: Version,
) {
let (costs, next_hops) = dijkstra_with_first_hop(&graph, *start_node, |e| *e.weight());
for (dst, (next_hop, path_len)) in next_hops.iter() {
let info = NextHopInfo {
next_hop_peer_id: *graph.node_weight(*next_hop).unwrap(),
path_latency: (*costs.get(dst).unwrap() % AVOID_RELAY_COST) as i32,
path_len: *path_len as usize,
version,
};
let dst_peer_id = *graph.node_weight(*dst).unwrap();
self.next_hop_map
.entry(dst_peer_id)
.and_modify(|x| {
if x.version < version {
*x = info;
}
})
.or_insert(info);
}
self.next_hop_map_version.set_if_larger(version);
}
fn build_from_synced_info<T: RouteCostCalculatorInterface>(
&self,
my_peer_id: PeerId,
synced_info: &SyncedRouteInfo,
policy: NextHopPolicy,
cost_calc: &T,
) {
let version = synced_info.version.get();
// build next hop map
let (graph, start_node) =
Self::build_peer_graph_from_synced_info(my_peer_id, &synced_info, cost_calc);
if graph.node_count() == 0 {
tracing::warn!("no peer in graph, cannot build next hop map");
return; return;
} }
// build next hop map if matches!(policy, NextHopPolicy::LeastHop) {
self.next_hop_map.clear(); self.gen_next_hop_map_with_least_hop(&graph, &start_node, version);
self.next_hop_map.insert(
my_peer_id,
NextHopInfo {
next_hop_peer_id: my_peer_id,
path_latency: 0,
path_len: 1,
},
);
let (graph, idx_map) = Self::build_peer_graph_from_synced_info(
self.peer_infos.iter().map(|x| *x.key()).collect(),
&synced_info,
&mut cost_calc,
);
let next_hop_map = if matches!(policy, NextHopPolicy::LeastHop) {
Self::gen_next_hop_map_with_least_hop(my_peer_id, &graph, &idx_map, &mut cost_calc)
} else { } else {
Self::gen_next_hop_map_with_least_cost(my_peer_id, &graph, &idx_map) self.gen_next_hop_map_with_least_cost(&graph, &start_node, version);
}; };
for item in next_hop_map.iter() {
self.next_hop_map.insert(*item.key(), *item.value());
}
// build graph
// build ipv4_peer_id_map, cidr_peer_id_map // build peer_infos, ipv4_peer_id_map, cidr_peer_id_map
self.ipv4_peer_id_map.clear(); // only set map for peers we can reach.
self.cidr_peer_id_map.clear(); for item in self.next_hop_map.iter() {
for item in self.peer_infos.iter() { if item.version < version {
// only set ipv4 map for peers we can reach. // skip if the next hop entry is outdated. (peer is unreachable)
if !self.next_hop_map.contains_key(item.key()) {
continue; continue;
} }
let peer_id = item.key(); let peer_id = item.key();
let info = item.value(); let Some(info) = synced_info.peer_infos.get(peer_id) else {
continue;
};
self.peer_infos.insert(*peer_id, info.clone());
if let Some(ipv4_addr) = info.ipv4_addr { if let Some(ipv4_addr) = info.ipv4_addr {
self.ipv4_peer_id_map.insert(ipv4_addr.into(), *peer_id); self.ipv4_peer_id_map.insert(ipv4_addr.into(), *peer_id);
@@ -1022,12 +1055,13 @@ struct PeerRouteServiceImpl {
interface: Mutex<Option<RouteInterfaceBox>>, interface: Mutex<Option<RouteInterfaceBox>>,
cost_calculator: std::sync::Mutex<Option<RouteCostCalculator>>, cost_calculator: std::sync::RwLock<Option<RouteCostCalculator>>,
route_table: RouteTable, route_table: RouteTable,
route_table_with_cost: RouteTable, route_table_with_cost: RouteTable,
foreign_network_owner_map: DashMap<NetworkIdentity, Vec<PeerId>>, foreign_network_owner_map: DashMap<NetworkIdentity, Vec<PeerId>>,
synced_route_info: SyncedRouteInfo, synced_route_info: SyncedRouteInfo,
cached_local_conn_map: std::sync::Mutex<RouteConnBitmap>, cached_local_conn_map: std::sync::Mutex<RouteConnBitmap>,
cached_local_conn_map_version: AtomicVersion,
last_update_my_foreign_network: AtomicCell<Option<std::time::Instant>>, last_update_my_foreign_network: AtomicCell<Option<std::time::Instant>>,
@@ -1063,7 +1097,7 @@ impl PeerRouteServiceImpl {
interface: Mutex::new(None), interface: Mutex::new(None),
cost_calculator: std::sync::Mutex::new(Some(Box::new(DefaultRouteCostCalculator))), cost_calculator: std::sync::RwLock::new(Some(Box::new(DefaultRouteCostCalculator))),
route_table: RouteTable::new(), route_table: RouteTable::new(),
route_table_with_cost: RouteTable::new(), route_table_with_cost: RouteTable::new(),
@@ -1074,8 +1108,10 @@ impl PeerRouteServiceImpl {
raw_peer_infos: DashMap::new(), raw_peer_infos: DashMap::new(),
conn_map: DashMap::new(), conn_map: DashMap::new(),
foreign_network: DashMap::new(), foreign_network: DashMap::new(),
version: AtomicVersion::new(),
}, },
cached_local_conn_map: std::sync::Mutex::new(RouteConnBitmap::new()), cached_local_conn_map: std::sync::Mutex::new(RouteConnBitmap::new()),
cached_local_conn_map_version: AtomicVersion::new(),
last_update_my_foreign_network: AtomicCell::new(None), last_update_my_foreign_network: AtomicCell::new(None),
@@ -1171,23 +1207,37 @@ impl PeerRouteServiceImpl {
} }
fn update_route_table(&self) { fn update_route_table(&self) {
let mut calc_locked = self.cost_calculator.lock().unwrap(); self.cost_calculator
.write()
.unwrap()
.as_mut()
.unwrap()
.begin_update();
let calc_locked = self.cost_calculator.read().unwrap();
calc_locked.as_mut().unwrap().begin_update();
self.route_table.build_from_synced_info( self.route_table.build_from_synced_info(
self.my_peer_id, self.my_peer_id,
&self.synced_route_info, &self.synced_route_info,
NextHopPolicy::LeastHop, NextHopPolicy::LeastHop,
calc_locked.as_mut().unwrap(), calc_locked.as_ref().unwrap(),
); );
self.route_table_with_cost.build_from_synced_info( self.route_table_with_cost.build_from_synced_info(
self.my_peer_id, self.my_peer_id,
&self.synced_route_info, &self.synced_route_info,
NextHopPolicy::LeastCost, NextHopPolicy::LeastCost,
calc_locked.as_mut().unwrap(), calc_locked.as_ref().unwrap(),
); );
calc_locked.as_mut().unwrap().end_update();
drop(calc_locked);
self.cost_calculator
.write()
.unwrap()
.as_mut()
.unwrap()
.end_update();
} }
fn update_foreign_network_owner_map(&self) { fn update_foreign_network_owner_map(&self) {
@@ -1221,7 +1271,7 @@ impl PeerRouteServiceImpl {
fn cost_calculator_need_update(&self) -> bool { fn cost_calculator_need_update(&self) -> bool {
self.cost_calculator self.cost_calculator
.lock() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
.map(|x| x.need_update()) .map(|x| x.need_update())
@@ -1234,6 +1284,8 @@ impl PeerRouteServiceImpl {
// update route table first because we want to filter out unreachable peers. // update route table first because we want to filter out unreachable peers.
self.update_route_table(); self.update_route_table();
let synced_version = self.synced_route_info.version.get();
// the conn_bitmap should contain complete list of directly connected peers. // the conn_bitmap should contain complete list of directly connected peers.
// use union of dst peers can preserve this property. // use union of dst peers can preserve this property.
let all_dst_peer_ids = self let all_dst_peer_ids = self
@@ -1259,7 +1311,9 @@ impl PeerRouteServiceImpl {
let all_peer_ids = &conn_bitmap.peer_ids; let all_peer_ids = &conn_bitmap.peer_ids;
for (peer_idx, (peer_id, _)) in all_peer_ids.iter().enumerate() { for (peer_idx, (peer_id, _)) in all_peer_ids.iter().enumerate() {
let connected = self.synced_route_info.conn_map.get(peer_id).unwrap(); let Some(connected) = self.synced_route_info.conn_map.get(peer_id) else {
continue;
};
for (idx, (other_peer_id, _)) in all_peer_ids.iter().enumerate() { for (idx, (other_peer_id, _)) in all_peer_ids.iter().enumerate() {
if connected.0.contains(other_peer_id) { if connected.0.contains(other_peer_id) {
@@ -1269,7 +1323,13 @@ impl PeerRouteServiceImpl {
} }
} }
*self.cached_local_conn_map.lock().unwrap() = conn_bitmap; let mut locked = self.cached_local_conn_map.lock().unwrap();
if self
.cached_local_conn_map_version
.set_if_larger(synced_version)
{
*locked = conn_bitmap;
}
} }
fn build_route_info(&self, session: &SyncRouteSession) -> Option<Vec<RoutePeerInfo>> { fn build_route_info(&self, session: &SyncRouteSession) -> Option<Vec<RoutePeerInfo>> {
@@ -1411,6 +1471,9 @@ impl PeerRouteServiceImpl {
for p in to_remove.iter() { for p in to_remove.iter() {
self.synced_route_info.foreign_network.remove(p); self.synced_route_info.foreign_network.remove(p);
} }
self.route_table.clean_expired_route_info();
self.route_table_with_cost.clean_expired_route_info();
} }
fn build_sync_route_raw_req( fn build_sync_route_raw_req(
@@ -2022,6 +2085,7 @@ impl PeerRoute {
if service_impl.cost_calculator_need_update() { if service_impl.cost_calculator_need_update() {
tracing::debug!("cost_calculator_need_update"); tracing::debug!("cost_calculator_need_update");
service_impl.synced_route_info.version.inc();
service_impl.update_route_table(); service_impl.update_route_table();
} }
@@ -2136,7 +2200,7 @@ impl Route for PeerRoute {
let next_hop_peer_latency_first = route_table_with_cost.get_next_hop(*item.key()); let next_hop_peer_latency_first = route_table_with_cost.get_next_hop(*item.key());
let mut route: crate::proto::cli::Route = item.value().clone().into(); let mut route: crate::proto::cli::Route = item.value().clone().into();
route.next_hop_peer_id = next_hop_peer.next_hop_peer_id; route.next_hop_peer_id = next_hop_peer.next_hop_peer_id;
route.cost = (next_hop_peer.path_len - 1) as i32; route.cost = next_hop_peer.path_len as i32;
route.path_latency = next_hop_peer.path_latency; route.path_latency = next_hop_peer.path_latency;
route.next_hop_peer_id_latency_first = route.next_hop_peer_id_latency_first =
@@ -2166,7 +2230,8 @@ impl Route for PeerRoute {
} }
async fn set_route_cost_fn(&self, _cost_fn: RouteCostCalculator) { async fn set_route_cost_fn(&self, _cost_fn: RouteCostCalculator) {
*self.service_impl.cost_calculator.lock().unwrap() = Some(_cost_fn); *self.service_impl.cost_calculator.write().unwrap() = Some(_cost_fn);
self.service_impl.synced_route_info.version.inc();
self.service_impl.update_route_table(); self.service_impl.update_route_table();
} }
@@ -2307,7 +2372,10 @@ mod tests {
for r in vec![r_a.clone(), r_b.clone()].iter() { for r in vec![r_a.clone(), r_b.clone()].iter() {
wait_for_condition( wait_for_condition(
|| async { r.list_routes().await.len() == 1 }, || async {
println!("route: {:?}", r.list_routes().await);
r.list_routes().await.len() == 1
},
Duration::from_secs(5), Duration::from_secs(5),
) )
.await; .await;
@@ -2348,6 +2416,8 @@ mod tests {
assert_eq!(i_a.0, i_b.1); assert_eq!(i_a.0, i_b.1);
assert_eq!(i_b.0, i_a.1); assert_eq!(i_b.0, i_a.1);
println!("after drop p_b, r_b");
drop(r_b); drop(r_b);
drop(p_b); drop(p_b);
+1
View File
@@ -30,6 +30,7 @@ message PeerConnInfo {
float loss_rate = 7; float loss_rate = 7;
bool is_client = 8; bool is_client = 8;
string network_name = 9; string network_name = 9;
bool is_closed = 10;
} }
message PeerInfo { message PeerInfo {
+2
View File
@@ -33,6 +33,8 @@ message FlagsInConfig {
// enable magic dns or not // enable magic dns or not
bool accept_dns = 22; bool accept_dns = 22;
// enable private mode
bool private_mode = 23;
} }
message RpcDescriptor { message RpcDescriptor {
+1
View File
@@ -65,6 +65,7 @@ message NetworkConfig {
repeated string mapped_listeners = 41; repeated string mapped_listeners = 41;
optional bool enable_magic_dns = 42; optional bool enable_magic_dns = 42;
optional bool enable_private_mode = 43;
} }
message MyNodeInfo { message MyNodeInfo {
+1 -1
View File
@@ -234,7 +234,7 @@ pub struct AesGcmTail {
} }
pub const AES_GCM_ENCRYPTION_RESERVED: usize = std::mem::size_of::<AesGcmTail>(); pub const AES_GCM_ENCRYPTION_RESERVED: usize = std::mem::size_of::<AesGcmTail>();
#[derive(AsBytes, FromZeroes, Clone, Debug, Copy)] #[derive(AsBytes, FromZeroes, Clone, Debug, Copy, PartialEq, Hash, Eq)]
#[repr(u8)] #[repr(u8)]
pub enum CompressorAlgo { pub enum CompressorAlgo {
None = 0, None = 0,
+42 -16
View File
@@ -1,4 +1,7 @@
use std::sync::atomic::{AtomicU32, Ordering::Relaxed}; use std::{
cell::UnsafeCell,
sync::atomic::{AtomicU32, Ordering::Relaxed},
};
pub struct WindowLatency { pub struct WindowLatency {
latency_us_window: Vec<AtomicU32>, latency_us_window: Vec<AtomicU32>,
@@ -58,13 +61,38 @@ impl WindowLatency {
} }
} }
#[derive(Default, Copy, Clone, Debug)] #[derive(Debug)]
pub struct Throughput { pub struct Throughput {
tx_bytes: u64, tx_bytes: UnsafeCell<u64>,
rx_bytes: u64, rx_bytes: UnsafeCell<u64>,
tx_packets: UnsafeCell<u64>,
rx_packets: UnsafeCell<u64>,
}
tx_packets: u64, impl Clone for Throughput {
rx_packets: u64, fn clone(&self) -> Self {
Self {
tx_bytes: UnsafeCell::new(unsafe { *self.tx_bytes.get() }),
rx_bytes: UnsafeCell::new(unsafe { *self.rx_bytes.get() }),
tx_packets: UnsafeCell::new(unsafe { *self.tx_packets.get() }),
rx_packets: UnsafeCell::new(unsafe { *self.rx_packets.get() }),
}
}
}
// add sync::Send and sync::Sync traits to Throughput
unsafe impl Send for Throughput {}
unsafe impl Sync for Throughput {}
impl Default for Throughput {
fn default() -> Self {
Self {
tx_bytes: UnsafeCell::new(0),
rx_bytes: UnsafeCell::new(0),
tx_packets: UnsafeCell::new(0),
rx_packets: UnsafeCell::new(0),
}
}
} }
impl Throughput { impl Throughput {
@@ -73,34 +101,32 @@ impl Throughput {
} }
pub fn tx_bytes(&self) -> u64 { pub fn tx_bytes(&self) -> u64 {
self.tx_bytes unsafe { *self.tx_bytes.get() }
} }
pub fn rx_bytes(&self) -> u64 { pub fn rx_bytes(&self) -> u64 {
self.rx_bytes unsafe { *self.rx_bytes.get() }
} }
pub fn tx_packets(&self) -> u64 { pub fn tx_packets(&self) -> u64 {
self.tx_packets unsafe { *self.tx_packets.get() }
} }
pub fn rx_packets(&self) -> u64 { pub fn rx_packets(&self) -> u64 {
self.rx_packets unsafe { *self.rx_packets.get() }
} }
pub fn record_tx_bytes(&self, bytes: u64) { pub fn record_tx_bytes(&self, bytes: u64) {
#[allow(invalid_reference_casting)]
unsafe { unsafe {
*(&self.tx_bytes as *const u64 as *mut u64) += bytes; *self.tx_bytes.get() += bytes;
*(&self.tx_packets as *const u64 as *mut u64) += 1; *self.tx_packets.get() += 1;
} }
} }
pub fn record_rx_bytes(&self, bytes: u64) { pub fn record_rx_bytes(&self, bytes: u64) {
#[allow(invalid_reference_casting)]
unsafe { unsafe {
*(&self.rx_bytes as *const u64 as *mut u64) += bytes; *self.rx_bytes.get() += bytes;
*(&self.rx_packets as *const u64 as *mut u64) += 1; *self.rx_packets.get() += 1;
} }
} }
} }
+5 -9
View File
@@ -183,8 +183,6 @@ impl WSTunnelConnector {
) -> Result<Box<dyn Tunnel>, TunnelError> { ) -> Result<Box<dyn Tunnel>, TunnelError> {
let is_wss = is_wss(&addr)?; let is_wss = is_wss(&addr)?;
let socket_addr = SocketAddr::from_url(addr.clone(), ip_version).await?; let socket_addr = SocketAddr::from_url(addr.clone(), ip_version).await?;
let domain = addr.domain();
let host = socket_addr.ip();
let stream = tcp_socket.connect(socket_addr).await?; let stream = tcp_socket.connect(socket_addr).await?;
let info = TunnelInfo { let info = TunnelInfo {
@@ -204,13 +202,11 @@ impl WSTunnelConnector {
init_crypto_provider(); init_crypto_provider();
let tls_conn = let tls_conn =
tokio_rustls::TlsConnector::from(Arc::new(get_insecure_tls_client_config())); tokio_rustls::TlsConnector::from(Arc::new(get_insecure_tls_client_config()));
let domain_or_ip = match domain { // Modify SNI logic: always use "localhost" as SNI to avoid IP blocking.
None => host.to_string(), let sni = "localhost";
Some(domain) => domain.to_string(), let server_name = rustls::pki_types::ServerName::try_from(sni)
}; .map_err(|_| TunnelError::InvalidProtocol("Invalid SNI".to_string()))?;
let stream = tls_conn let stream = tls_conn.connect(server_name, stream).await?;
.connect(domain_or_ip.try_into().unwrap(), stream)
.await?;
MaybeTlsStream::Rustls(stream) MaybeTlsStream::Rustls(stream)
} else { } else {
MaybeTlsStream::Plain(stream) MaybeTlsStream::Plain(stream)
+1 -1
View File
@@ -205,7 +205,7 @@ pub fn setup_panic_handler() {
// backtrace is risky, so use it last // backtrace is risky, so use it last
let backtrace = backtrace::Backtrace::force_capture(); let backtrace = backtrace::Backtrace::force_capture();
write_err(format!("backtrace: {:?}", backtrace)); write_err(format!("backtrace: {:#?}", backtrace));
std::process::exit(1); std::process::exit(1);
})); }));