Compare commits
10 Commits
make_ospf_
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b44053f496 | ||
|
|
5b9ac65477 | ||
|
|
d726d46a00 | ||
|
|
1273426009 | ||
|
|
b50744690e | ||
|
|
55b93454dc | ||
|
|
89cc75f674 | ||
|
|
6bb2fd9a15 | ||
|
|
8ab98bba8f | ||
|
|
26d002bc2b |
4
.github/workflows/ohos.yml
vendored
4
.github/workflows/ohos.yml
vendored
@@ -122,6 +122,8 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: easytier-ohos
|
||||
path: ./easytier-contrib/easytier-ohrs/easytier-ohrs.har
|
||||
path: |
|
||||
./easytier-contrib/easytier-ohrs/easytier-ohrs.har
|
||||
./easytier-contrib/easytier-ohrs/dist/arm64-v8a/libeasytier_ohrs.so
|
||||
retention-days: 5
|
||||
if-no-files-found: error
|
||||
|
||||
157
Cargo.lock
generated
157
Cargo.lock
generated
@@ -8,15 +8,6 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
@@ -737,21 +728,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide 0.7.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base62"
|
||||
version = "2.0.2"
|
||||
@@ -2004,7 +1980,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.0",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2171,7 +2147,6 @@ dependencies = [
|
||||
"nix 0.29.0",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
"ordered_hash_map",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"petgraph 0.8.1",
|
||||
@@ -2200,7 +2175,7 @@ dependencies = [
|
||||
"service-manager",
|
||||
"sha2",
|
||||
"smoltcp",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"stun_codec",
|
||||
"sys-locale",
|
||||
"tabled",
|
||||
@@ -2328,6 +2303,7 @@ dependencies = [
|
||||
"easytier",
|
||||
"futures",
|
||||
"jsonwebtoken",
|
||||
"mimalloc",
|
||||
"mockall",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
@@ -2370,6 +2346,7 @@ dependencies = [
|
||||
"image 0.24.9",
|
||||
"imageproc",
|
||||
"maxminddb",
|
||||
"mimalloc",
|
||||
"once_cell",
|
||||
"password-auth",
|
||||
"rand 0.8.5",
|
||||
@@ -2810,9 +2787,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -2820,9 +2797,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
@@ -2848,9 +2825,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@@ -2867,9 +2844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2878,15 +2855,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
@@ -2896,9 +2873,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -3121,12 +3098,6 @@ dependencies = [
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.18.4"
|
||||
@@ -3736,7 +3707,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -4069,7 +4040,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"widestring",
|
||||
"windows-sys 0.48.0",
|
||||
"winreg 0.50.0",
|
||||
@@ -5432,15 +5403,6 @@ dependencies = [
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -5547,15 +5509,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered_hash_map"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6c699f8a30f345785be969deed7eee4c73a5de58c7faf61d6a3251ef798ff61"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.8.2"
|
||||
@@ -6448,7 +6401,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -6486,7 +6439,7 @@ checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -7058,12 +7011,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.0"
|
||||
@@ -7933,6 +7880,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "softbuffer"
|
||||
version = "0.4.5"
|
||||
@@ -8344,9 +8301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
@@ -8853,7 +8810,7 @@ dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9046,28 +9003,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.1"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"socket2 0.6.1",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9299,7 +9255,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
@@ -10410,7 +10366,7 @@ dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.2",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
@@ -10453,7 +10409,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
@@ -10465,7 +10421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
@@ -10519,6 +10475,12 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
@@ -10526,7 +10488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10555,7 +10517,7 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10585,7 +10547,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10633,6 +10595,15 @@ dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
@@ -10701,7 +10672,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -280,8 +280,6 @@ sudo easytier-core --network-name mysharednode --network-secret mysharednode
|
||||
|
||||
- [ZeroTier](https://www.zerotier.com/): A global virtual network for connecting devices.
|
||||
- [TailScale](https://tailscale.com/): A VPN solution aimed at simplifying network configuration.
|
||||
- [vpncloud](https://github.com/dswd/vpncloud): A P2P Mesh VPN
|
||||
- [Candy](https://github.com/lanthora/candy): A reliable, low-latency, and anti-censorship virtual private network
|
||||
|
||||
### Contact Us
|
||||
|
||||
|
||||
@@ -281,8 +281,6 @@ sudo easytier-core --network-name mysharednode --network-secret mysharednode
|
||||
|
||||
- [ZeroTier](https://www.zerotier.com/):用于连接设备的全球虚拟网络。
|
||||
- [TailScale](https://tailscale.com/):旨在简化网络配置的 VPN 解决方案。
|
||||
- [vpncloud](https://github.com/dswd/vpncloud):一个 P2P 网状 VPN
|
||||
- [Candy](https://github.com/lanthora/candy):一个可靠、低延迟、反审查的虚拟专用网络
|
||||
|
||||
### 联系我们
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ use std::sync::Mutex;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use easytier::{
|
||||
common::config::{ConfigLoader as _, TomlConfigLoader},
|
||||
common::config::{ConfigFileControl, ConfigLoader as _, TomlConfigLoader},
|
||||
instance_manager::NetworkInstanceManager,
|
||||
launcher::ConfigSource,
|
||||
};
|
||||
|
||||
static INSTANCE_NAME_ID_MAP: once_cell::sync::Lazy<DashMap<String, uuid::Uuid>> =
|
||||
@@ -129,13 +128,14 @@ pub unsafe extern "C" fn run_network_instance(cfg_str: *const std::ffi::c_char)
|
||||
return -1;
|
||||
}
|
||||
|
||||
let instance_id = match INSTANCE_MANAGER.run_network_instance(cfg, ConfigSource::FFI) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
set_error_msg(&format!("failed to start instance: {}", e));
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
let instance_id =
|
||||
match INSTANCE_MANAGER.run_network_instance(cfg, false, ConfigFileControl::STATIC_CONFIG) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
set_error_msg(&format!("failed to start instance: {}", e));
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
INSTANCE_NAME_ID_MAP.insert(inst_name, instance_id);
|
||||
|
||||
|
||||
91
easytier-contrib/easytier-ohrs/Cargo.lock
generated
91
easytier-contrib/easytier-ohrs/Cargo.lock
generated
@@ -133,16 +133,6 @@ version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "async-event"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1222afd3d2bce3995035054046a279ae7aa154d70d0766cea050073f3fd7ddf"
|
||||
dependencies = [
|
||||
"loom 0.5.6",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
@@ -487,9 +477,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cidr"
|
||||
version = "0.2.3"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdf600c45bd958cf2945c445264471cca8b6c8e67bc87b71affd6d7e5682621"
|
||||
checksum = "bd1b64030216239a2e7c364b13cd96a2097ebf0dfe5025f2dedee14a23f2ab60"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -946,16 +936,6 @@ dependencies = [
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diatomic-waker"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28025fb55a9d815acf7b0877555f437254f373036eec6ed265116c7a5c0825e9"
|
||||
dependencies = [
|
||||
"loom 0.5.6",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -1015,8 +995,7 @@ checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055"
|
||||
|
||||
[[package]]
|
||||
name = "easytier"
|
||||
version = "2.4.4"
|
||||
source = "git+https://github.com/EasyTier/EasyTier.git#4445916ba72a8340259d65f0c55f50af325c51d2"
|
||||
version = "2.4.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -1056,6 +1035,7 @@ dependencies = [
|
||||
"http_req",
|
||||
"humansize",
|
||||
"humantime-serde",
|
||||
"idna",
|
||||
"kcp-sys",
|
||||
"machine-uid",
|
||||
"multimap",
|
||||
@@ -1071,6 +1051,7 @@ dependencies = [
|
||||
"petgraph 0.8.2",
|
||||
"pin-project-lite",
|
||||
"pnet",
|
||||
"prefix-trie",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"prost-reflect",
|
||||
@@ -1095,7 +1076,6 @@ dependencies = [
|
||||
"stun_codec",
|
||||
"sys-locale",
|
||||
"tabled",
|
||||
"tachyonix",
|
||||
"thiserror 1.0.69",
|
||||
"thunk-rs",
|
||||
"time",
|
||||
@@ -1454,19 +1434,6 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.8.7"
|
||||
@@ -2262,7 +2229,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "kcp-sys"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/EasyTier/kcp-sys?rev=0f0a0558391ba391c089806c23f369651f6c9eeb#0f0a0558391ba391c089806c23f369651f6c9eeb"
|
||||
source = "git+https://github.com/EasyTier/kcp-sys?rev=71eff18c573a4a71bf99c7fabc6a8b9f211c84c1#71eff18c573a4a71bf99c7fabc6a8b9f211c84c1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"auto_impl",
|
||||
@@ -2398,19 +2365,6 @@ version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator 0.7.5",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.7.2"
|
||||
@@ -2418,7 +2372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator 0.8.7",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -2517,7 +2471,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"loom 0.7.2",
|
||||
"loom",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rustc_version",
|
||||
@@ -3141,6 +3095,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85cf4c7c25f1dd66c76b451e9041a8cfce26e4ca754934fa7aed8d5a59a01d20"
|
||||
dependencies = [
|
||||
"cidr",
|
||||
"ipnet",
|
||||
"num-traits",
|
||||
]
|
||||
@@ -4169,19 +4124,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tachyonix"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86c3eafa053bbcc63bb4bfc5eb26362a33ea0bc2e589f28bce00287d1c167d45"
|
||||
dependencies = [
|
||||
"async-event",
|
||||
"crossbeam-utils",
|
||||
"diatomic-waker",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
@@ -4785,12 +4727,6 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@@ -5030,15 +4966,6 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -8,7 +8,7 @@ crate-type=["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ohos-hilog-binding = {version = "*", features = ["redirect"]}
|
||||
easytier = { git = "https://github.com/EasyTier/EasyTier.git" }
|
||||
easytier = { path = "../../easytier" }
|
||||
napi-derive-ohos = "1.1"
|
||||
napi-ohos = { version = "1.1", default-features = false, features = [
|
||||
"serde-json",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
mod native_log;
|
||||
|
||||
use easytier::common::config::{ConfigLoader, TomlConfigLoader};
|
||||
use easytier::common::config::{ConfigFileControl, ConfigLoader, TomlConfigLoader};
|
||||
use easytier::common::constants::EASYTIER_VERSION;
|
||||
use easytier::instance_manager::NetworkInstanceManager;
|
||||
use easytier::launcher::ConfigSource;
|
||||
use napi_derive_ohos::napi;
|
||||
use ohos_hilog_binding::{hilog_debug, hilog_error};
|
||||
use std::format;
|
||||
@@ -17,6 +17,11 @@ pub struct KeyValuePair {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn easytier_version() -> String {
|
||||
EASYTIER_VERSION.to_string()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set_tun_fd(inst_id: String, fd: i32) -> bool {
|
||||
match Uuid::try_parse(&inst_id) {
|
||||
@@ -71,7 +76,7 @@ pub fn run_network_instance(cfg_str: String) -> bool {
|
||||
return false;
|
||||
}
|
||||
INSTANCE_MANAGER
|
||||
.run_network_instance(cfg, ConfigSource::FFI)
|
||||
.run_network_instance(cfg, false, ConfigFileControl::STATIC_CONFIG)
|
||||
.unwrap();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ once_cell = "1.19"
|
||||
# EasyTier core
|
||||
easytier = { path = "../../easytier" }
|
||||
|
||||
mimalloc = { version = "*" }
|
||||
|
||||
# Testing
|
||||
[dev-dependencies]
|
||||
mockall = "0.12"
|
||||
|
||||
@@ -8,12 +8,11 @@ use anyhow::Context as _;
|
||||
use dashmap::DashMap;
|
||||
use easytier::{
|
||||
common::{
|
||||
config::{ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader},
|
||||
config::{ConfigFileControl, ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader},
|
||||
scoped_task::ScopedTask,
|
||||
},
|
||||
defer,
|
||||
instance_manager::NetworkInstanceManager,
|
||||
launcher::ConfigSource,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::any;
|
||||
@@ -392,7 +391,7 @@ impl HealthChecker {
|
||||
.delete_network_instance(vec![cfg.get_id()]);
|
||||
});
|
||||
self.instance_mgr
|
||||
.run_network_instance(cfg.clone(), ConfigSource::FFI)
|
||||
.run_network_instance(cfg.clone(), false, ConfigFileControl::STATIC_CONFIG)
|
||||
.with_context(|| "failed to run network instance")?;
|
||||
|
||||
let now = Instant::now();
|
||||
@@ -436,7 +435,7 @@ impl HealthChecker {
|
||||
);
|
||||
|
||||
self.instance_mgr
|
||||
.run_network_instance(cfg.clone(), ConfigSource::Web)
|
||||
.run_network_instance(cfg.clone(), true, ConfigFileControl::STATIC_CONFIG)
|
||||
.with_context(|| "failed to run network instance")?;
|
||||
self.inst_id_map.insert(node_id, cfg.get_id());
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::db::cleanup::{CleanupConfig, CleanupManager};
|
||||
|
||||
use mimalloc::MiMalloc;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL_MIMALLOC: MiMalloc = MiMalloc;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
@@ -31,7 +36,7 @@ struct Args {
|
||||
admin_password: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// 加载配置
|
||||
let config = AppConfig::default();
|
||||
|
||||
@@ -78,7 +78,11 @@ fn generate_network_config(toml_config: String) -> Result<NetworkConfig, String>
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn run_network_instance(app: AppHandle, cfg: NetworkConfig) -> Result<(), String> {
|
||||
async fn run_network_instance(
|
||||
app: AppHandle,
|
||||
cfg: NetworkConfig,
|
||||
save: bool,
|
||||
) -> Result<(), String> {
|
||||
let instance_id = cfg.instance_id().to_string();
|
||||
|
||||
app.emit("pre_run_network_instance", cfg.instance_id())
|
||||
@@ -97,7 +101,7 @@ async fn run_network_instance(app: AppHandle, cfg: NetworkConfig) -> Result<(),
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.handle_run_network_instance(app.clone(), cfg)
|
||||
.handle_run_network_instance(app.clone(), cfg, save)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -240,12 +244,17 @@ async fn get_config(app: AppHandle, instance_id: String) -> Result<NetworkConfig
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn load_configs(configs: Vec<NetworkConfig>, enabled_networks: Vec<String>) -> Result<(), String> {
|
||||
async fn load_configs(
|
||||
app: AppHandle,
|
||||
configs: Vec<NetworkConfig>,
|
||||
enabled_networks: Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.storage
|
||||
.load_configs(configs, enabled_networks)
|
||||
.load_configs(app, configs, enabled_networks)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -304,7 +313,8 @@ mod manager {
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use easytier::launcher::{ConfigSource, NetworkConfig};
|
||||
use easytier::launcher::NetworkConfig;
|
||||
use easytier::proto::api::manage::RunNetworkInstanceRequest;
|
||||
use easytier::proto::rpc_impl::bidirect::BidirectRpcManager;
|
||||
use easytier::proto::rpc_types::controller::BaseController;
|
||||
use easytier::rpc_service::remote_client::PersistentConfig;
|
||||
@@ -334,8 +344,9 @@ mod manager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn load_configs(
|
||||
pub(super) async fn load_configs(
|
||||
&self,
|
||||
app: AppHandle,
|
||||
configs: Vec<NetworkConfig>,
|
||||
enabled_networks: Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -349,21 +360,31 @@ mod manager {
|
||||
}
|
||||
|
||||
self.enabled_networks.clear();
|
||||
INSTANCE_MANAGER
|
||||
.filter_network_instance(|_, _| true)
|
||||
.into_iter()
|
||||
.for_each(|id| {
|
||||
self.enabled_networks.insert(id);
|
||||
});
|
||||
INSTANCE_MANAGER.iter().for_each(|v| {
|
||||
self.enabled_networks.insert(*v.key());
|
||||
});
|
||||
for id in enabled_networks {
|
||||
if let Ok(uuid) = id.parse() {
|
||||
if !self.enabled_networks.contains(&uuid) {
|
||||
let config = self
|
||||
.network_configs
|
||||
.get(&uuid)
|
||||
.map(|i| i.value().1.gen_config())
|
||||
.ok_or_else(|| anyhow::anyhow!("Config not found"))??;
|
||||
INSTANCE_MANAGER.run_network_instance(config, ConfigSource::GUI)?;
|
||||
.map(|i| i.value().1.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("Config not found"))?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.get_rpc_client(app.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("RPC client not found"))?
|
||||
.run_network_instance(
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config: Some(config),
|
||||
overwrite: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.enabled_networks.insert(uuid);
|
||||
}
|
||||
}
|
||||
@@ -434,18 +455,14 @@ mod manager {
|
||||
app: AppHandle,
|
||||
network_inst_id: Uuid,
|
||||
disabled: bool,
|
||||
) -> Result<GUIConfig, anyhow::Error> {
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if disabled {
|
||||
self.enabled_networks.remove(&network_inst_id);
|
||||
} else {
|
||||
self.enabled_networks.insert(network_inst_id);
|
||||
}
|
||||
self.save_enabled_networks(&app)?;
|
||||
let cfg = self
|
||||
.network_configs
|
||||
.get(&network_inst_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Config not found"))?;
|
||||
Ok(cfg.value().clone())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_network_configs(
|
||||
|
||||
@@ -15,8 +15,8 @@ export async function generateNetworkConfig(tomlConfig: string) {
|
||||
return invoke<NetworkConfig>('generate_network_config', { tomlConfig })
|
||||
}
|
||||
|
||||
export async function runNetworkInstance(cfg: NetworkConfig) {
|
||||
return invoke('run_network_instance', { cfg })
|
||||
export async function runNetworkInstance(cfg: NetworkConfig, save: boolean) {
|
||||
return invoke('run_network_instance', { cfg, save })
|
||||
}
|
||||
|
||||
export async function collectNetworkInfo(instanceId: string) {
|
||||
|
||||
@@ -160,7 +160,7 @@ export async function onNetworkInstanceChange(instanceId: string) {
|
||||
}
|
||||
catch (e) {
|
||||
console.error('start vpn service failed, stop all other network insts.', e)
|
||||
await runNetworkInstance(config);
|
||||
await runNetworkInstance(config, true); //on android config should always be saved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ export class GUIRemoteClient implements Api.RemoteClient {
|
||||
async validate_config(config: NetworkTypes.NetworkConfig): Promise<Api.ValidateConfigResponse> {
|
||||
return backend.validateConfig(config);
|
||||
}
|
||||
async run_network(config: NetworkTypes.NetworkConfig): Promise<undefined> {
|
||||
await backend.runNetworkInstance(config);
|
||||
async run_network(config: NetworkTypes.NetworkConfig, save: boolean): Promise<undefined> {
|
||||
await backend.runNetworkInstance(config, save);
|
||||
}
|
||||
async get_network_info(inst_id: string): Promise<NetworkTypes.NetworkInstanceRunningInfo | undefined> {
|
||||
return backend.collectNetworkInfo(inst_id).then(infos => infos.info.map[inst_id]);
|
||||
|
||||
@@ -64,6 +64,8 @@ uuid = { version = "1.5.0", features = [
|
||||
|
||||
chrono = { version = "0.4.37", features = ["serde"] }
|
||||
|
||||
mimalloc = { version = "*" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
embed = ["dep:axum-embed"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import InputGroup from 'primevue/inputgroup'
|
||||
import InputGroupAddon from 'primevue/inputgroupaddon'
|
||||
import { SelectButton, Checkbox, InputText, InputNumber, AutoComplete, Panel, Divider, ToggleButton, Button, Password } from 'primevue'
|
||||
import { SelectButton, Checkbox, InputText, InputNumber, AutoComplete, Panel, Divider, ToggleButton, Button, Password, Dialog } from 'primevue'
|
||||
import {
|
||||
addRow,
|
||||
DEFAULT_NETWORK_CONFIG,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
NetworkingMethod,
|
||||
removeRow
|
||||
} from '../types/network'
|
||||
import { defineProps, defineEmits, ref, } from 'vue'
|
||||
import { defineProps, defineEmits, ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -157,6 +157,7 @@ const bool_flags: BoolFlag[] = [
|
||||
{ field: 'enable_quic_proxy', help: 'enable_quic_proxy_help' },
|
||||
{ field: 'disable_quic_input', help: 'disable_quic_input_help' },
|
||||
{ field: 'disable_p2p', help: 'disable_p2p_help' },
|
||||
{ field: 'p2p_only', help: 'p2p_only_help' },
|
||||
{ field: 'bind_device', help: 'bind_device_help' },
|
||||
{ field: 'no_tun', help: 'no_tun_help' },
|
||||
{ field: 'enable_exit_node', help: 'enable_exit_node_help' },
|
||||
@@ -172,6 +173,49 @@ const bool_flags: BoolFlag[] = [
|
||||
|
||||
const portForwardProtocolOptions = ref(["tcp", "udp"]);
|
||||
|
||||
const editingPortForward = ref(false);
|
||||
const editingPortForwardIndex = ref(-1);
|
||||
const editingPortForwardData = ref();
|
||||
|
||||
function openPortForwardEditor(index: number) {
|
||||
editingPortForwardIndex.value = index;
|
||||
// deep copy
|
||||
editingPortForwardData.value = JSON.parse(JSON.stringify(curNetwork.value.port_forwards[index]));
|
||||
editingPortForward.value = true;
|
||||
}
|
||||
|
||||
function addPortForward() {
|
||||
addRow(curNetwork.value.port_forwards)
|
||||
if (isCompact.value) {
|
||||
openPortForwardEditor(curNetwork.value.port_forwards.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
function savePortForward() {
|
||||
curNetwork.value.port_forwards[editingPortForwardIndex.value] = editingPortForwardData.value;
|
||||
editingPortForward.value = false;
|
||||
}
|
||||
|
||||
const portForwardContainer = ref<HTMLElement | null>(null);
|
||||
const isCompact = ref(false);
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
if (portForwardContainer.value) {
|
||||
let resizeObserver = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
isCompact.value = entry.contentRect.width < 540;
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(portForwardContainer.value);
|
||||
|
||||
return () => {
|
||||
if (resizeObserver && portForwardContainer.value) {
|
||||
resizeObserver.unobserve(portForwardContainer.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -410,14 +454,15 @@ const portForwardProtocolOptions = ref(["tcp", "udp"]);
|
||||
<Divider />
|
||||
|
||||
<Panel :header="t('port_forwards')" toggleable collapsed>
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<div ref="portForwardContainer" class="flex flex-col gap-y-2">
|
||||
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
||||
<div class="flex flex-col gap-2 grow p-fluid">
|
||||
<div class="flex">
|
||||
<label for="port_forwards">{{ t('port_forwards_help') }}</label>
|
||||
</div>
|
||||
<div v-for="(row, index) in curNetwork.port_forwards" class="form-row">
|
||||
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
|
||||
<div v-for="(row, index) in curNetwork.port_forwards" :key="index" class="form-row">
|
||||
<!-- Wide screen view -->
|
||||
<div v-if="!isCompact" class="flex gap-2 items-end">
|
||||
<SelectButton v-model="row.proto" :options="portForwardProtocolOptions" :allow-empty="false" />
|
||||
<div style="flex-grow: 4;">
|
||||
<InputGroup>
|
||||
@@ -444,11 +489,52 @@ const portForwardProtocolOptions = ref(["tcp", "udp"]);
|
||||
rounded @click="removeRow(index, curNetwork.port_forwards)" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Small screen view -->
|
||||
<div v-else class="flex justify-between items-center p-2 border-b">
|
||||
<span>{{ row.proto }}://{{ row.bind_ip }}:{{ row.bind_port }}/{{ row.dst_ip }}:{{
|
||||
row.dst_port }}</span>
|
||||
<div class="flex gap-2">
|
||||
<Button icon="pi pi-pencil" class="p-button-sm" @click="openPortForwardEditor(index)" />
|
||||
<Button icon="pi pi-trash" class="p-button-sm p-button-danger"
|
||||
@click="removeRow(index, curNetwork.port_forwards)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-content-end mt-4">
|
||||
<Button icon="pi pi-plus" :label="t('port_forwards_add_btn')" severity="success"
|
||||
@click="addRow(curNetwork.port_forwards)" />
|
||||
@click="addPortForward" />
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="editingPortForward" modal :header="t('edit_port_forward')"
|
||||
:style="{ width: '90vw', maxWidth: '600px' }">
|
||||
<div v-if="editingPortForwardData" class="flex flex-col gap-4">
|
||||
<SelectButton v-model="editingPortForwardData.proto" :options="portForwardProtocolOptions"
|
||||
:allow-empty="false" />
|
||||
<InputGroup>
|
||||
<InputText v-model="editingPortForwardData.bind_ip"
|
||||
:placeholder="t('port_forwards_bind_addr')" />
|
||||
<InputGroupAddon>
|
||||
<span style="font-weight: bold">:</span>
|
||||
</InputGroupAddon>
|
||||
<InputNumber v-model="editingPortForwardData.bind_port" :format="false" :step="1" mode="decimal"
|
||||
:min="1" :max="65535" class="max-w-20" />
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputText v-model="editingPortForwardData.dst_ip" :placeholder="t('port_forwards_dst_addr')" />
|
||||
<InputGroupAddon>
|
||||
<span style="font-weight: bold">:</span>
|
||||
</InputGroupAddon>
|
||||
<InputNumber v-model="editingPortForwardData.dst_port" :format="false" :step="1" mode="decimal"
|
||||
:min="1" :max="65535" class="max-w-20" />
|
||||
</InputGroup>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button :label="t('web.common.cancel')" icon="pi pi-times" @click="editingPortForward = false"
|
||||
text />
|
||||
<Button :label="t('web.common.save')" icon="pi pi-save" @click="savePortForward" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -56,6 +56,23 @@ const onLazyLoadNetworkMetas = async (event: VirtualScrollerLazyEvent) => {
|
||||
.map(item => item.uuid);
|
||||
await loadNetworkMetas(instanceIds);
|
||||
};
|
||||
const currentNetworkMeta = computed(() => {
|
||||
if (!instanceId.value) {
|
||||
return undefined;
|
||||
}
|
||||
return networkMetaCache.value[instanceId.value];
|
||||
});
|
||||
const currentNetworkControl = {
|
||||
remoteSave: computed(() => {
|
||||
return Api.ConfigFilePermission.isRemoveSaveable(currentNetworkMeta.value?.config_permission ?? 0);
|
||||
}),
|
||||
editable: computed(() => {
|
||||
return Api.ConfigFilePermission.isEditable(currentNetworkMeta.value?.config_permission ?? 0);
|
||||
}),
|
||||
deletable: computed(() => {
|
||||
return Api.ConfigFilePermission.isDeletable(currentNetworkMeta.value?.config_permission ?? 0);
|
||||
})
|
||||
}
|
||||
|
||||
const instanceList = ref<Array<{ uuid: string; meta?: Api.NetworkMeta }>>([]);
|
||||
const updateInstanceList = () => {
|
||||
@@ -150,17 +167,12 @@ const loadCurrentNetworkConfig = async () => {
|
||||
currentNetworkConfig.value = ret;
|
||||
}
|
||||
|
||||
const updateNetworkState = async (disabled: boolean) => {
|
||||
const stopNetwork = async () => {
|
||||
if (!selectedInstanceId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disabled || !currentNetworkConfig.value) {
|
||||
await props.api.update_network_instance_state(selectedInstanceId.value.uuid, disabled);
|
||||
} else if (currentNetworkConfig.value) {
|
||||
await props.api.delete_network(currentNetworkConfig.value.instance_id);
|
||||
await props.api.run_network(currentNetworkConfig.value);
|
||||
}
|
||||
await props.api.update_network_instance_state(selectedInstanceId.value.uuid, true);
|
||||
await loadNetworkInstanceIds();
|
||||
}
|
||||
|
||||
@@ -199,7 +211,7 @@ const saveAndRunNewNetwork = async () => {
|
||||
}
|
||||
try {
|
||||
await props.api.delete_network(instanceId.value!);
|
||||
let ret = await props.api.run_network(currentNetworkConfig.value);
|
||||
let ret = await props.api.run_network(currentNetworkConfig.value, currentNetworkControl.remoteSave.value);
|
||||
console.debug("saveAndRunNewNetwork", ret);
|
||||
|
||||
delete networkMetaCache.value[currentNetworkConfig.value.instance_id];
|
||||
@@ -377,7 +389,7 @@ const actionMenu: Ref<MenuItem[]> = ref([
|
||||
{
|
||||
label: t('web.device_management.edit_network'),
|
||||
icon: 'pi pi-pencil',
|
||||
visible: () => !(networkIsDisabled.value ?? true),
|
||||
visible: () => !(networkIsDisabled.value ?? true) && currentNetworkControl.editable.value,
|
||||
command: () => editNetwork()
|
||||
},
|
||||
{
|
||||
@@ -389,6 +401,7 @@ const actionMenu: Ref<MenuItem[]> = ref([
|
||||
label: t('web.device_management.delete_network'),
|
||||
icon: 'pi pi-trash',
|
||||
class: 'p-error',
|
||||
visible: () => currentNetworkControl.deletable.value,
|
||||
command: () => confirmDeleteNetwork(new Event('click'))
|
||||
}
|
||||
]);
|
||||
@@ -443,7 +456,7 @@ onUnmounted(() => {
|
||||
<span class="truncate block">
|
||||
|
||||
<span v-if="slotProps.value.meta">
|
||||
{{ slotProps.value.meta.instance_name }} ({{ slotProps.value.uuid }})
|
||||
{{ slotProps.value.meta.network_name }} ({{ slotProps.value.uuid }})
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ slotProps.value.uuid }}
|
||||
@@ -463,7 +476,7 @@ onUnmounted(() => {
|
||||
<div class="flex items-center min-w-0">
|
||||
<div class="mr-4 min-w-0 flex-1">
|
||||
<span class="truncate block">{{ t('network_name') }}: {{
|
||||
slotProps.option.meta.instance_name }}</span>
|
||||
slotProps.option.meta.network_name }}</span>
|
||||
</div>
|
||||
<Tag class="my-auto leading-3 shrink-0"
|
||||
:severity="isRunning(slotProps.option.uuid) ? 'success' : 'info'"
|
||||
@@ -544,8 +557,9 @@ onUnmounted(() => {
|
||||
<Message v-else severity="error" class="mb-4">{{ curNetworkInfo?.error_msg }}</Message>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<Button @click="updateNetworkState(true)" :label="t('web.device_management.disable_network')"
|
||||
severity="warning" icon="pi pi-power-off" iconPos="left" />
|
||||
<Button @click="stopNetwork" :disabled="!currentNetworkControl.deletable.value"
|
||||
:label="t('web.device_management.disable_network')" severity="danger" icon="pi pi-power-off"
|
||||
iconPos="left" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -106,6 +106,9 @@ disable_quic_input_help: 禁用 QUIC 入站流量,其他开启 QUIC 代理的
|
||||
disable_p2p: 禁用 P2P
|
||||
disable_p2p_help: 禁用 P2P 模式,所有流量通过手动指定的服务器中转。
|
||||
|
||||
p2p_only: 仅 P2P
|
||||
p2p_only_help: 仅与已经建立P2P连接的对等节点通信,不通过其他节点中转。
|
||||
|
||||
bind_device: 仅使用物理网卡
|
||||
bind_device_help: 仅使用物理网卡,避免 EasyTier 通过其他虚拟网建立连接。
|
||||
|
||||
@@ -164,6 +167,7 @@ port_forwards_help: "将本地端口转发到虚拟网络中的远程端口。
|
||||
port_forwards_bind_addr: "绑定地址,如:0.0.0.0"
|
||||
port_forwards_dst_addr: "目标地址,如:10.126.126.1"
|
||||
port_forwards_add_btn: "添加"
|
||||
edit_port_forward: "编辑端口转发"
|
||||
|
||||
mtu: MTU
|
||||
mtu_help: |
|
||||
@@ -335,4 +339,4 @@ web:
|
||||
confirm_password: 确认新密码
|
||||
language: 语言
|
||||
theme: 主题
|
||||
logout: 退出登录
|
||||
logout: 退出登录
|
||||
|
||||
@@ -105,6 +105,9 @@ disable_quic_input_help: Disable inbound QUIC traffic, while nodes with QUIC pro
|
||||
disable_p2p: Disable P2P
|
||||
disable_p2p_help: Disable P2P mode; route all traffic through a manually specified relay server.
|
||||
|
||||
p2p_only: P2P Only
|
||||
p2p_only_help: Only communicate with peers that have already established P2P connections, do not relay through other nodes.
|
||||
|
||||
bind_device: Bind to Physical Device Only
|
||||
bind_device_help: Use only the physical network interface to prevent EasyTier from connecting via virtual networks.
|
||||
|
||||
@@ -164,6 +167,7 @@ port_forwards_help: "forward local port to remote port in virtual network. e.g.:
|
||||
port_forwards_bind_addr: "Bind address, e.g.: 0.0.0.0"
|
||||
port_forwards_dst_addr: "Destination address, e.g.: 10.126.126.1"
|
||||
port_forwards_add_btn: "Add"
|
||||
edit_port_forward: "Edit Port Forward"
|
||||
|
||||
mtu: MTU
|
||||
mtu_help: |
|
||||
@@ -335,4 +339,4 @@ web:
|
||||
confirm_password: Confirm New Password
|
||||
language: Language
|
||||
theme: Theme
|
||||
logout: Logout
|
||||
logout: Logout
|
||||
|
||||
@@ -26,8 +26,27 @@ export interface CollectNetworkInfoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ConfigFilePermission {
|
||||
export type Flags = number;
|
||||
export const READ_ONLY: Flags = 1 << 0;
|
||||
export const NO_DELETE: Flags = 1 << 1;
|
||||
export function hasPermission(perm: Flags, flag: Flags): boolean {
|
||||
return (perm & flag) === flag;
|
||||
}
|
||||
export function isRemoveSaveable(perm: Flags): boolean {
|
||||
return !hasPermission(perm, NO_DELETE);
|
||||
}
|
||||
export function isEditable(perm: Flags): boolean {
|
||||
return !hasPermission(perm, READ_ONLY);
|
||||
}
|
||||
export function isDeletable(perm: Flags): boolean {
|
||||
return !hasPermission(perm, NO_DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
export interface NetworkMeta {
|
||||
instance_name: string;
|
||||
network_name: string;
|
||||
config_permission: ConfigFilePermission.Flags;
|
||||
}
|
||||
|
||||
export interface GetNetworkMetasResponse {
|
||||
@@ -36,7 +55,7 @@ export interface GetNetworkMetasResponse {
|
||||
|
||||
export interface RemoteClient {
|
||||
validate_config(config: NetworkConfig): Promise<ValidateConfigResponse>;
|
||||
run_network(config: NetworkConfig): Promise<undefined>;
|
||||
run_network(config: NetworkConfig, save: boolean): Promise<undefined>;
|
||||
get_network_info(inst_id: string): Promise<NetworkInstanceRunningInfo | undefined>;
|
||||
list_network_instance_ids(): Promise<ListNetworkInstanceIdResponse>;
|
||||
delete_network(inst_id: string): Promise<undefined>;
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface NetworkConfig {
|
||||
enable_quic_proxy?: boolean
|
||||
disable_quic_input?: boolean
|
||||
disable_p2p?: boolean
|
||||
p2p_only?: boolean
|
||||
bind_device?: boolean
|
||||
no_tun?: boolean
|
||||
enable_exit_node?: boolean
|
||||
@@ -111,6 +112,7 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||
enable_quic_proxy: false,
|
||||
disable_quic_input: false,
|
||||
disable_p2p: false,
|
||||
p2p_only: false,
|
||||
bind_device: true,
|
||||
no_tun: false,
|
||||
enable_exit_node: false,
|
||||
|
||||
@@ -193,9 +193,10 @@ class WebRemoteClient implements Api.RemoteClient {
|
||||
});
|
||||
return response;
|
||||
}
|
||||
async run_network(config: NetworkTypes.NetworkConfig): Promise<undefined> {
|
||||
async run_network(config: NetworkTypes.NetworkConfig, save: boolean): Promise<undefined> {
|
||||
await this.client.post<string>(`/machines/${this.machine_id}/networks`, {
|
||||
config: config,
|
||||
save: save
|
||||
});
|
||||
}
|
||||
async get_network_info(inst_id: string): Promise<NetworkTypes.NetworkInstanceRunningInfo | undefined> {
|
||||
|
||||
@@ -280,6 +280,7 @@ impl Session {
|
||||
config: Some(
|
||||
serde_json::from_str::<NetworkConfig>(&c.network_config).unwrap(),
|
||||
),
|
||||
overwrite: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -155,7 +155,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
||||
(user_id, _): (UserIdInDb, Uuid),
|
||||
network_inst_id: Uuid,
|
||||
disabled: bool,
|
||||
) -> Result<user_running_network_configs::Model, DbErr> {
|
||||
) -> Result<(), DbErr> {
|
||||
use entity::user_running_network_configs as urnc;
|
||||
|
||||
urnc::Entity::update_many()
|
||||
@@ -169,15 +169,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
||||
.exec(self.orm_db())
|
||||
.await?;
|
||||
|
||||
urnc::Entity::find()
|
||||
.filter(urnc::Column::UserId.eq(user_id))
|
||||
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id.to_string()))
|
||||
.one(self.orm_db())
|
||||
.await?
|
||||
.ok_or(DbErr::RecordNotFound(format!(
|
||||
"Network config not found for user {} and network instance {}",
|
||||
user_id, network_inst_id
|
||||
)))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_network_configs(
|
||||
|
||||
@@ -19,6 +19,8 @@ use easytier::{
|
||||
utils::{init_logger, setup_panic_handler},
|
||||
};
|
||||
|
||||
use mimalloc::MiMalloc;
|
||||
|
||||
mod client_manager;
|
||||
mod db;
|
||||
mod migrator;
|
||||
@@ -27,6 +29,9 @@ mod restful;
|
||||
#[cfg(feature = "embed")]
|
||||
mod web;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL_MIMALLOC: MiMalloc = MiMalloc;
|
||||
|
||||
rust_i18n::i18n!("locales", fallback = "en");
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -161,7 +166,7 @@ async fn get_dual_stack_listener(
|
||||
Ok((v6_listener, v4_listener))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn main() {
|
||||
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||
rust_i18n::set_locale(&locale);
|
||||
|
||||
@@ -59,6 +59,7 @@ struct SaveNetworkJsonReq {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct RunNetworkJsonReq {
|
||||
config: NetworkConfig,
|
||||
save: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
@@ -132,6 +133,7 @@ impl NetworkApi {
|
||||
.handle_run_network_instance(
|
||||
(Self::get_user_id(&auth_session)?, machine_id),
|
||||
payload.config,
|
||||
payload.save,
|
||||
)
|
||||
.await
|
||||
.map_err(convert_error)?;
|
||||
|
||||
@@ -143,7 +143,6 @@ network-interface = "2.0"
|
||||
# for ospf route
|
||||
petgraph = "0.8.1"
|
||||
hashbrown = "0.15.3"
|
||||
ordered_hash_map = "0.5.0"
|
||||
|
||||
# for wireguard
|
||||
boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true }
|
||||
@@ -297,7 +296,7 @@ thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = f
|
||||
[dev-dependencies]
|
||||
serial_test = "3.0.0"
|
||||
rstest = "0.25.0"
|
||||
futures-util = "0.3.30"
|
||||
futures-util = "0.3.31"
|
||||
maplit = "1.0.2"
|
||||
tempfile = "3.22.0"
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ core_clap:
|
||||
config_file:
|
||||
en: "path to the config file, NOTE: the options set by cmdline args will override options in config file"
|
||||
zh-CN: "配置文件路径,注意:命令行中的配置的选项会覆盖配置文件中的选项"
|
||||
config_dir:
|
||||
en: "Load all .toml files in the directory to start network instances, and store the received configurations in this directory."
|
||||
zh-CN: "加载目录中的所有 .toml 文件以启动网络实例,并将下发的配置保存在此目录中。"
|
||||
generate_completions:
|
||||
en: "generate shell completions"
|
||||
zh-CN: "生成 shell 补全脚本"
|
||||
@@ -148,6 +151,9 @@ core_clap:
|
||||
disable_p2p:
|
||||
en: "disable p2p communication, will only relay packets with peers specified by --peers"
|
||||
zh-CN: "禁用P2P通信,只通过--peers指定的节点转发数据包"
|
||||
p2p_only:
|
||||
en: "only communicate with peers that already establish p2p connection"
|
||||
zh-CN: "仅与已经建立P2P连接的对等节点通信"
|
||||
disable_udp_hole_punching:
|
||||
en: "disable udp hole punching"
|
||||
zh-CN: "禁用UDP打洞功能"
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
|
||||
use anyhow::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
|
||||
use crate::{
|
||||
common::stun::StunInfoCollector,
|
||||
@@ -34,6 +35,7 @@ pub fn gen_default_flags() -> Flags {
|
||||
use_smoltcp: false,
|
||||
relay_network_whitelist: "*".to_string(),
|
||||
disable_p2p: false,
|
||||
p2p_only: false,
|
||||
relay_all_peer_rpc: false,
|
||||
disable_udp_hole_punching: false,
|
||||
multi_thread: true,
|
||||
@@ -829,6 +831,157 @@ impl ConfigLoader for TomlConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct ConfigFilePermission(u8);
|
||||
impl ConfigFilePermission {
|
||||
pub const READ_ONLY: u8 = 1 << 0;
|
||||
pub const NO_DELETE: u8 = 1 << 1;
|
||||
|
||||
pub fn with_flag(self, flag: u8) -> Self {
|
||||
Self(self.0 | flag)
|
||||
}
|
||||
pub fn remove_flag(self, flag: u8) -> Self {
|
||||
Self(self.0 & !flag)
|
||||
}
|
||||
pub fn has_flag(&self, flag: u8) -> bool {
|
||||
(self.0 & flag) != 0
|
||||
}
|
||||
}
|
||||
impl From<u8> for ConfigFilePermission {
|
||||
fn from(value: u8) -> Self {
|
||||
ConfigFilePermission(value)
|
||||
}
|
||||
}
|
||||
impl From<u32> for ConfigFilePermission {
|
||||
fn from(value: u32) -> Self {
|
||||
ConfigFilePermission(value as u8)
|
||||
}
|
||||
}
|
||||
impl From<ConfigFilePermission> for u8 {
|
||||
fn from(value: ConfigFilePermission) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<ConfigFilePermission> for u32 {
|
||||
fn from(value: ConfigFilePermission) -> Self {
|
||||
value.0 as u32
|
||||
}
|
||||
}
|
||||
impl std::fmt::Debug for ConfigFilePermission {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut flags = vec![];
|
||||
if self.has_flag(ConfigFilePermission::READ_ONLY) {
|
||||
flags.push("READ_ONLY");
|
||||
} else {
|
||||
flags.push("EDITABLE");
|
||||
}
|
||||
if self.has_flag(ConfigFilePermission::NO_DELETE) {
|
||||
flags.push("NO_DELETE");
|
||||
} else {
|
||||
flags.push("DELETABLE");
|
||||
}
|
||||
write!(f, "{}", flags.join("|"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigFileControl {
|
||||
pub path: Option<PathBuf>,
|
||||
pub permission: ConfigFilePermission,
|
||||
}
|
||||
|
||||
impl ConfigFileControl {
|
||||
pub const STATIC_CONFIG: ConfigFileControl = Self {
|
||||
path: None,
|
||||
permission: ConfigFilePermission(
|
||||
ConfigFilePermission::READ_ONLY | ConfigFilePermission::NO_DELETE,
|
||||
),
|
||||
};
|
||||
|
||||
pub fn new(path: Option<PathBuf>, permission: ConfigFilePermission) -> Self {
|
||||
ConfigFileControl { path, permission }
|
||||
}
|
||||
|
||||
pub async fn from_path(path: PathBuf) -> Self {
|
||||
let read_only = if let Ok(metadata) = tokio::fs::metadata(&path).await {
|
||||
metadata.permissions().readonly()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
Self::new(
|
||||
Some(path),
|
||||
if read_only {
|
||||
ConfigFilePermission(ConfigFilePermission::READ_ONLY)
|
||||
} else {
|
||||
ConfigFilePermission(0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_read_only(&self) -> bool {
|
||||
self.permission.has_flag(ConfigFilePermission::READ_ONLY)
|
||||
}
|
||||
pub fn set_read_only(&mut self, read_only: bool) {
|
||||
if read_only {
|
||||
self.permission = self.permission.with_flag(ConfigFilePermission::READ_ONLY);
|
||||
} else {
|
||||
self.permission = self.permission.remove_flag(ConfigFilePermission::READ_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_no_delete(&self) -> bool {
|
||||
self.permission.has_flag(ConfigFilePermission::NO_DELETE)
|
||||
}
|
||||
pub fn set_no_delete(&mut self, no_delete: bool) {
|
||||
if no_delete {
|
||||
self.permission = self.permission.with_flag(ConfigFilePermission::NO_DELETE);
|
||||
} else {
|
||||
self.permission = self.permission.remove_flag(ConfigFilePermission::NO_DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_deletable(&self) -> bool {
|
||||
!self.is_no_delete()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_config_from_file(
|
||||
config_file: &PathBuf,
|
||||
config_dir: Option<&PathBuf>,
|
||||
) -> Result<(TomlConfigLoader, ConfigFileControl), anyhow::Error> {
|
||||
if config_file.as_os_str() == "-" {
|
||||
let mut stdin = String::new();
|
||||
_ = tokio::io::stdin()
|
||||
.read_to_string(&mut stdin)
|
||||
.await
|
||||
.context("failed to read config from stdin")?;
|
||||
let config = TomlConfigLoader::new_from_str(&stdin)?;
|
||||
return Ok((config, ConfigFileControl::STATIC_CONFIG));
|
||||
}
|
||||
let config = TomlConfigLoader::new(config_file)
|
||||
.with_context(|| format!("failed to load config file: {:?}", config_file))?;
|
||||
let mut control = ConfigFileControl::from_path(config_file.clone()).await;
|
||||
if control.is_read_only() {
|
||||
control.set_no_delete(true);
|
||||
} else if let Some(config_dir) = config_dir {
|
||||
if let Some(config_file_dir) = config_file.parent() {
|
||||
// if the config file is in the config dir and named as the instance id, it can be saved remotely
|
||||
if config_file_dir == config_dir
|
||||
&& config_file.file_stem() == Some(config.get_id().to_string().as_ref())
|
||||
&& config_file.extension() == Some(std::ffi::OsStr::new("toml"))
|
||||
{
|
||||
control.set_no_delete(false);
|
||||
} else {
|
||||
control.set_no_delete(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
control.set_no_delete(true);
|
||||
}
|
||||
|
||||
Ok((config, control))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -68,7 +68,7 @@ pub async fn socket_addrs(
|
||||
url: &url::Url,
|
||||
default_port_number: impl Fn() -> Option<u16>,
|
||||
) -> Result<Vec<SocketAddr>, Error> {
|
||||
let host = url.host_str().ok_or(Error::InvalidUrl(url.to_string()))?;
|
||||
let host = url.host().ok_or(Error::InvalidUrl(url.to_string()))?;
|
||||
let port = url
|
||||
.port()
|
||||
.or_else(default_port_number)
|
||||
@@ -84,9 +84,12 @@ pub async fn socket_addrs(
|
||||
};
|
||||
|
||||
// if host is an ip address, return it directly
|
||||
if let Ok(ip) = host.parse::<std::net::IpAddr>() {
|
||||
return Ok(vec![SocketAddr::new(ip, port)]);
|
||||
match host {
|
||||
url::Host::Ipv4(ip) => return Ok(vec![SocketAddr::new(std::net::IpAddr::V4(ip), port)]),
|
||||
url::Host::Ipv6(ip) => return Ok(vec![SocketAddr::new(std::net::IpAddr::V6(ip), port)]),
|
||||
_ => {}
|
||||
}
|
||||
let host = host.to_string();
|
||||
|
||||
if ALLOW_USE_SYSTEM_DNS_RESOLVER.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let socket_addr = format!("{}:{}", host, port);
|
||||
@@ -103,7 +106,7 @@ pub async fn socket_addrs(
|
||||
}
|
||||
|
||||
// use hickory_resolver
|
||||
let ret = RESOLVER.lookup_ip(host).await.with_context(|| {
|
||||
let ret = RESOLVER.lookup_ip(&host).await.with_context(|| {
|
||||
format!(
|
||||
"hickory dns lookup_ip failed, host: {}, port: {}",
|
||||
host, port
|
||||
|
||||
@@ -84,6 +84,7 @@ pub struct GlobalCtx {
|
||||
enable_exit_node: bool,
|
||||
proxy_forward_by_system: bool,
|
||||
no_tun: bool,
|
||||
p2p_only: bool,
|
||||
|
||||
feature_flags: AtomicCell<PeerFeatureFlag>,
|
||||
|
||||
@@ -138,6 +139,7 @@ impl GlobalCtx {
|
||||
let enable_exit_node = config_fs.get_flags().enable_exit_node || cfg!(target_env = "ohos");
|
||||
let proxy_forward_by_system = config_fs.get_flags().proxy_forward_by_system;
|
||||
let no_tun = config_fs.get_flags().no_tun;
|
||||
let p2p_only = config_fs.get_flags().p2p_only;
|
||||
|
||||
let feature_flags = PeerFeatureFlag {
|
||||
kcp_input: !config_fs.get_flags().disable_kcp_input,
|
||||
@@ -172,6 +174,7 @@ impl GlobalCtx {
|
||||
enable_exit_node,
|
||||
proxy_forward_by_system,
|
||||
no_tun,
|
||||
p2p_only,
|
||||
|
||||
feature_flags: AtomicCell::new(feature_flags),
|
||||
quic_proxy_port: AtomicCell::new(None),
|
||||
@@ -421,6 +424,15 @@ impl GlobalCtx {
|
||||
.and_then(|acl_v1| acl_v1.group)
|
||||
.map_or_else(Vec::new, |group| group.declares.to_vec())
|
||||
}
|
||||
|
||||
pub fn p2p_only(&self) -> bool {
|
||||
self.p2p_only
|
||||
}
|
||||
|
||||
pub fn latency_first(&self) -> bool {
|
||||
// NOTICE: p2p only is conflict with latency first
|
||||
self.config.get_flags().latency_first && !self.p2p_only
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,210 +1,70 @@
|
||||
use idna::domain_to_ascii;
|
||||
pub fn convert_idn_to_ascii(url_str: &str) -> Result<String, String> {
|
||||
if !url_str.is_ascii() {
|
||||
let mut url_parts = url_str.splitn(2, "://");
|
||||
let scheme = url_parts.next().unwrap_or("");
|
||||
let rest = url_parts.next().unwrap_or(url_str);
|
||||
let (host_part, port_part, path_part) = {
|
||||
let mut path_and_rest = rest.splitn(2, '/');
|
||||
let host_port_part = path_and_rest.next().unwrap_or("");
|
||||
let path_part = path_and_rest
|
||||
.next()
|
||||
.map(|s| format!("/{}", s))
|
||||
.unwrap_or_default();
|
||||
if host_port_part.starts_with('[') {
|
||||
if let Some(end_bracket_pos) = host_port_part.find(']') {
|
||||
let host_part = &host_port_part[..end_bracket_pos + 1];
|
||||
let remaining = &host_port_part[end_bracket_pos + 1..];
|
||||
if remaining.starts_with(':') {
|
||||
if let Some(port_str) = remaining.strip_prefix(':') {
|
||||
if port_str.chars().all(|c| c.is_ascii_digit()) {
|
||||
(host_part, format!(":{}", port_str), path_part)
|
||||
} else {
|
||||
(host_part, String::new(), path_part)
|
||||
}
|
||||
} else {
|
||||
(host_part, String::new(), path_part)
|
||||
}
|
||||
} else {
|
||||
(host_part, String::new(), path_part)
|
||||
}
|
||||
} else {
|
||||
(host_port_part, String::new(), path_part)
|
||||
}
|
||||
} else {
|
||||
let (host_part, port_part) = if let Some(pos) = host_port_part.rfind(':') {
|
||||
let port_str = &host_port_part[pos + 1..];
|
||||
if port_str.chars().all(|c| c.is_ascii_digit()) {
|
||||
(&host_port_part[..pos], format!(":{}", port_str))
|
||||
} else {
|
||||
(host_port_part, String::new())
|
||||
}
|
||||
} else {
|
||||
(host_port_part, String::new())
|
||||
};
|
||||
(host_part, port_part, path_part)
|
||||
}
|
||||
};
|
||||
use percent_encoding::percent_decode_str;
|
||||
|
||||
if !host_part.is_ascii() {
|
||||
let ascii_host = domain_to_ascii(host_part)
|
||||
.map_err(|e| format!("Failed to convert IDN to ASCII: {}", e))?;
|
||||
let result = format!("{}://{}{}{}", scheme, ascii_host, port_part, path_part);
|
||||
Ok(result)
|
||||
} else {
|
||||
Ok(url_str.to_string())
|
||||
}
|
||||
} else {
|
||||
Ok(url_str.to_string())
|
||||
pub fn convert_idn_to_ascii(mut url: url::Url) -> anyhow::Result<url::Url> {
|
||||
if url.is_special() {
|
||||
return Ok(url);
|
||||
}
|
||||
}
|
||||
pub fn safe_convert_idn_to_ascii(url_str: &str) -> String {
|
||||
convert_idn_to_ascii(url_str).unwrap_or_else(|_| url_str.to_string())
|
||||
if let Some(domain) = url.domain() {
|
||||
let domain = percent_decode_str(domain).decode_utf8()?;
|
||||
let domain = domain_to_ascii(&domain)?;
|
||||
url.set_host(Some(&domain))?;
|
||||
}
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_ascii_only_urls() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://example.com").unwrap(),
|
||||
"https://example.com"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("http://test.org:8080/path").unwrap(),
|
||||
"http://test.org:8080/path"
|
||||
);
|
||||
}
|
||||
#[rstest]
|
||||
// test_ascii_only_urls
|
||||
#[case("example.com", "example.com")]
|
||||
#[case("test.org:8080/path", "test.org:8080/path")]
|
||||
// test_unicode_domains
|
||||
#[case("räksmörgås.nu", "xn--rksmrgs-5wao1o.nu")]
|
||||
#[case("中文.测试", "xn--fiq228c.xn--0zwm56d")]
|
||||
// test_unicode_domains_with_port
|
||||
#[case("räksmörgås.nu:8080", "xn--rksmrgs-5wao1o.nu:8080")]
|
||||
// test_unicode_domains_with_port_and_path
|
||||
#[case("例子.测试/path", "xn--fsqu00a.xn--0zwm56d/path")]
|
||||
#[case("中文.测试:9000/api", "xn--fiq228c.xn--0zwm56d:9000/api")]
|
||||
#[case("räksmörgås.nu:8080/path", "xn--rksmrgs-5wao1o.nu:8080/path")]
|
||||
// test_unicode_domains_with_port_and_unicode_path
|
||||
#[case(
|
||||
"中文.测试:8000/用户/管理",
|
||||
"xn--fiq228c.xn--0zwm56d:8000/%E7%94%A8%E6%88%B7/%E7%AE%A1%E7%90%86"
|
||||
)]
|
||||
// test_ipv6_literals & test_ipv6_with_unicode_path
|
||||
#[case("[2001:db8::1]:8080", "[2001:db8::1]:8080")]
|
||||
#[case("[2001:db8::1]/path", "[2001:db8::1]/path")]
|
||||
#[case(
|
||||
"[2001:db8::1]/路径/资源",
|
||||
"[2001:db8::1]/%E8%B7%AF%E5%BE%84/%E8%B5%84%E6%BA%90"
|
||||
)]
|
||||
fn test_convert_idn_to_ascii_cases(
|
||||
#[case] host_part: &str,
|
||||
#[case] expected_host_part: &str,
|
||||
#[values("tcp", "udp", "ws", "wss", "wg", "quic", "http", "https")] protocol: &str,
|
||||
#[values(false, true)] dual_convert: bool,
|
||||
) {
|
||||
let input = url::Url::parse(&format!("{}://{}", protocol, host_part)).unwrap();
|
||||
let input = if dual_convert {
|
||||
// in case url is serialized/deserialized as string somewhere else
|
||||
input.to_string().parse().unwrap()
|
||||
} else {
|
||||
input
|
||||
};
|
||||
let actual = convert_idn_to_ascii(input.clone()).unwrap().to_string();
|
||||
|
||||
#[test]
|
||||
fn test_unicode_domains() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://räksmörgås.nu").unwrap(),
|
||||
"https://xn--rksmrgs-5wao1o.nu"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://例子.测试").unwrap(),
|
||||
"https://xn--fsqu00a.xn--0zwm56d"
|
||||
);
|
||||
}
|
||||
let mut expected = format!("{}://{}", protocol, expected_host_part);
|
||||
|
||||
#[test]
|
||||
fn test_chinese_domains() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://中文.测试").unwrap(),
|
||||
"https://xn--fiq228c.xn--0zwm56d"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://公司.中国").unwrap(),
|
||||
"https://xn--55qx5d.xn--fiqs8s"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://网络.测试").unwrap(),
|
||||
"https://xn--io0a7i.xn--0zwm56d"
|
||||
);
|
||||
}
|
||||
// ws and wss protocols may automatically add a trailing slash if there's no path after host/port
|
||||
if input.is_special() && actual.ends_with("/") && !expected_host_part.ends_with("/") {
|
||||
expected.push('/');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_domains_with_port() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://räksmörgås.nu:8080").unwrap(),
|
||||
"https://xn--rksmrgs-5wao1o.nu:8080"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("http://例子.测试:3000/path").unwrap(),
|
||||
"http://xn--fsqu00a.xn--0zwm56d:3000/path"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://中文.测试:9000/api").unwrap(),
|
||||
"https://xn--fiq228c.xn--0zwm56d:9000/api"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_domains_with_path() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://räksmörgås.nu/path/to/resource").unwrap(),
|
||||
"https://xn--rksmrgs-5wao1o.nu/path/to/resource"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("http://例子.测试/api/v1").unwrap(),
|
||||
"http://xn--fsqu00a.xn--0zwm56d/api/v1"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://中文.测试/api/users").unwrap(),
|
||||
"https://xn--fiq228c.xn--0zwm56d/api/users"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_domains_with_port_and_path() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://räksmörgås.nu:8080/path/to/resource").unwrap(),
|
||||
"https://xn--rksmrgs-5wao1o.nu:8080/path/to/resource"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("http://例子.测试:9000/api/v1/users").unwrap(),
|
||||
"http://xn--fsqu00a.xn--0zwm56d:9000/api/v1/users"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://中文.测试:8000/用户/管理").unwrap(),
|
||||
"https://xn--fiq228c.xn--0zwm56d:8000/用户/管理"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipv6_literals() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://[2001:db8::1]:8080").unwrap(),
|
||||
"https://[2001:db8::1]:8080"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://[2001:db8::1]/path").unwrap(),
|
||||
"https://[2001:db8::1]/path"
|
||||
);
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://[2001:db8::1]/路径/资源").unwrap(),
|
||||
"https://[2001:db8::1]/路径/资源"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_port_format() {
|
||||
let result = convert_idn_to_ascii("https://räksmörgås.nu:notaport").unwrap();
|
||||
assert!(result.contains("xn--") && result.contains(":notaport"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_safe_conversion() {
|
||||
assert_eq!(
|
||||
safe_convert_idn_to_ascii("https://example.com"),
|
||||
"https://example.com"
|
||||
);
|
||||
assert_eq!(
|
||||
safe_convert_idn_to_ascii("https://中文.测试"),
|
||||
"https://xn--fiq228c.xn--0zwm56d"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_cases() {
|
||||
// Without scheme '://', entire string is treated as host part
|
||||
let result = convert_idn_to_ascii("räksmörgås.nu").unwrap();
|
||||
assert_eq!(result, "räksmörgås.nu://xn--rksmrgs-5wao1o.nu");
|
||||
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://test.例子.com").unwrap(),
|
||||
"https://test.xn--fsqu00a.com"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ipv6_with_unicode_path() {
|
||||
assert_eq!(
|
||||
convert_idn_to_ascii("https://[2001:db8::1]/路径/资源").unwrap(),
|
||||
"https://[2001:db8::1]/路径/资源"
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,13 +67,10 @@ impl IfConfiguerTrait for MacIfConfiger {
|
||||
}
|
||||
|
||||
async fn remove_ip(&self, name: &str, ip: Option<Ipv4Inet>) -> Result<(), Error> {
|
||||
if ip.is_none() {
|
||||
run_shell_cmd(format!("ifconfig {} inet delete", name).as_str()).await
|
||||
if let Some(ip) = ip {
|
||||
run_shell_cmd(format!("ifconfig {} inet {} delete", name, ip.address()).as_str()).await
|
||||
} else {
|
||||
run_shell_cmd(
|
||||
format!("ifconfig {} inet {} delete", name, ip.unwrap().address()).as_str(),
|
||||
)
|
||||
.await
|
||||
run_shell_cmd(format!("ifconfig {} inet delete", name).as_str()).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,11 +130,8 @@ impl DNSTunnelConnector {
|
||||
for record in response.iter() {
|
||||
let parsed_record = Self::handle_one_srv_record(record, protocol);
|
||||
tracing::info!(?parsed_record, ?srv_domain, "parsed_record");
|
||||
if parsed_record.is_err() {
|
||||
eprintln!(
|
||||
"got invalid srv record {:?}",
|
||||
parsed_record.as_ref().unwrap_err()
|
||||
);
|
||||
if let Err(e) = &parsed_record {
|
||||
eprintln!("got invalid srv record {:?}", e);
|
||||
continue;
|
||||
}
|
||||
responses.insert(parsed_record.unwrap());
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::tunnel::quic::QUICTunnelConnector;
|
||||
#[cfg(feature = "wireguard")]
|
||||
use crate::tunnel::wireguard::{WgConfig, WgTunnelConnector};
|
||||
use crate::{
|
||||
common::{error::Error, global_ctx::ArcGlobalCtx, network::IPCollector},
|
||||
common::{error::Error, global_ctx::ArcGlobalCtx, idn, network::IPCollector},
|
||||
tunnel::{
|
||||
check_scheme_and_get_socket_addr, ring::RingTunnelConnector, tcp::TcpTunnelConnector,
|
||||
udp::UdpTunnelConnector, IpVersion, TunnelConnector,
|
||||
@@ -58,6 +58,7 @@ pub async fn create_connector_by_url(
|
||||
ip_version: IpVersion,
|
||||
) -> Result<Box<dyn TunnelConnector + 'static>, Error> {
|
||||
let url = url::Url::parse(url).map_err(|_| Error::InvalidUrl(url.to_owned()))?;
|
||||
let url = idn::convert_idn_to_ascii(url)?;
|
||||
let mut connector: Box<dyn TunnelConnector + 'static> = match url.scheme() {
|
||||
"tcp" => {
|
||||
let dst_addr =
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{
|
||||
|
||||
use anyhow::Context;
|
||||
use cidr::Ipv4Inet;
|
||||
use clap::{command, Args, CommandFactory, Parser, Subcommand};
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
use clap_complete::Shell;
|
||||
use dashmap::DashMap;
|
||||
use humansize::format_size;
|
||||
|
||||
@@ -17,22 +17,18 @@ use clap_complete::Shell;
|
||||
use easytier::{
|
||||
common::{
|
||||
config::{
|
||||
get_avaliable_encrypt_methods, ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig,
|
||||
LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader,
|
||||
VpnPortalConfig,
|
||||
get_avaliable_encrypt_methods, load_config_from_file, ConfigFileControl, ConfigLoader,
|
||||
ConsoleLoggerConfig, FileLoggerConfig, LoggingConfigLoader, NetworkIdentity,
|
||||
PeerConfig, PortForwardConfig, TomlConfigLoader, VpnPortalConfig,
|
||||
},
|
||||
constants::EASYTIER_VERSION,
|
||||
global_ctx::GlobalCtx,
|
||||
set_default_machine_id,
|
||||
stun::MockStunInfoCollector,
|
||||
},
|
||||
connector::create_connector_by_url,
|
||||
defer,
|
||||
instance_manager::NetworkInstanceManager,
|
||||
launcher::{add_proxy_network_to_config, ConfigSource},
|
||||
proto::common::{CompressionAlgoPb, NatType},
|
||||
launcher::add_proxy_network_to_config,
|
||||
proto::common::CompressionAlgoPb,
|
||||
rpc_service::ApiRpcServer,
|
||||
tunnel::{IpVersion, PROTO_PORT_OFFSET},
|
||||
tunnel::PROTO_PORT_OFFSET,
|
||||
utils::{init_logger, setup_panic_handler},
|
||||
web_client,
|
||||
};
|
||||
@@ -136,6 +132,13 @@ struct Cli {
|
||||
)]
|
||||
config_file: Option<Vec<PathBuf>>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_CONFIG_DIR",
|
||||
help = t!("core_clap.config_dir").to_string()
|
||||
)]
|
||||
config_dir: Option<PathBuf>,
|
||||
|
||||
#[command(flatten)]
|
||||
network_options: NetworkOptions,
|
||||
|
||||
@@ -152,7 +155,7 @@ struct Cli {
|
||||
check_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[derive(Parser, Debug, Default, PartialEq, Eq)]
|
||||
struct NetworkOptions {
|
||||
#[arg(
|
||||
long,
|
||||
@@ -417,6 +420,15 @@ struct NetworkOptions {
|
||||
)]
|
||||
disable_p2p: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_P2P_ONLY",
|
||||
help = t!("core_clap.p2p_only").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
p2p_only: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_UDP_HOLE_PUNCHING",
|
||||
@@ -707,6 +719,9 @@ impl Cli {
|
||||
|
||||
impl NetworkOptions {
|
||||
fn can_merge(&self, cfg: &TomlConfigLoader, config_file_count: usize) -> bool {
|
||||
if (*self) == NetworkOptions::default() {
|
||||
return false;
|
||||
}
|
||||
if config_file_count == 1 {
|
||||
return true;
|
||||
}
|
||||
@@ -928,6 +943,7 @@ impl NetworkOptions {
|
||||
f.relay_network_whitelist = wl.join(" ");
|
||||
}
|
||||
f.disable_p2p = self.disable_p2p.unwrap_or(f.disable_p2p);
|
||||
f.p2p_only = self.p2p_only.unwrap_or(f.p2p_only);
|
||||
f.disable_udp_hole_punching = self
|
||||
.disable_udp_hole_punching
|
||||
.unwrap_or(f.disable_udp_hole_punching);
|
||||
@@ -1141,7 +1157,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
defer!(dump_profile(0););
|
||||
init_logger(&cli.logging_options, true)?;
|
||||
|
||||
let manager = Arc::new(NetworkInstanceManager::new());
|
||||
let manager = Arc::new(NetworkInstanceManager::new().with_config_path(cli.config_dir.clone()));
|
||||
|
||||
let _rpc_server = ApiRpcServer::new(
|
||||
cli.rpc_portal_options.rpc_portal,
|
||||
@@ -1151,90 +1167,77 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
.serve()
|
||||
.await?;
|
||||
|
||||
if cli.config_server.is_some() {
|
||||
set_default_machine_id(cli.machine_id);
|
||||
let config_server_url_s = cli.config_server.clone().unwrap();
|
||||
let config_server_url = match url::Url::parse(&config_server_url_s) {
|
||||
Ok(u) => u,
|
||||
Err(_) => format!(
|
||||
"udp://config-server.easytier.cn:22020/{}",
|
||||
config_server_url_s
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
};
|
||||
let _web_client = if let Some(config_server_url_s) = cli.config_server.as_ref() {
|
||||
let wc = web_client::run_web_client(
|
||||
config_server_url_s,
|
||||
cli.machine_id.clone(),
|
||||
cli.network_options.hostname.clone(),
|
||||
manager.clone(),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| {
|
||||
println!(
|
||||
"Web client started successfully...\nserver: {}",
|
||||
config_server_url_s,
|
||||
);
|
||||
|
||||
let mut c_url = config_server_url.clone();
|
||||
c_url.set_path("");
|
||||
let token = config_server_url
|
||||
.path_segments()
|
||||
.and_then(|mut x| x.next())
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default();
|
||||
println!("Official config website: https://easytier.cn/web");
|
||||
})?;
|
||||
|
||||
Some(wc)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut config_files = if let Some(v) = cli.config_file {
|
||||
v.clone()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
if let Some(config_dir) = cli.config_dir.as_ref() {
|
||||
if !config_dir.is_dir() {
|
||||
anyhow::bail!("config_dir {} is not a directory", config_dir.display());
|
||||
}
|
||||
|
||||
for entry in std::fs::read_dir(config_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
let Some(ext) = path.extension() else {
|
||||
continue;
|
||||
};
|
||||
if ext != "toml" {
|
||||
continue;
|
||||
}
|
||||
config_files.push(path);
|
||||
}
|
||||
}
|
||||
let config_file_count = config_files.len();
|
||||
let mut crate_cli_network = (config_file_count == 0 && cli.config_server.is_none())
|
||||
|| cli.network_options.network_name.is_some();
|
||||
for config_file in config_files {
|
||||
let (mut cfg, mut control) =
|
||||
load_config_from_file(&config_file, cli.config_dir.as_ref()).await?;
|
||||
|
||||
if cli.network_options.can_merge(&cfg, config_file_count) {
|
||||
cli.network_options
|
||||
.merge_into(&mut cfg)
|
||||
.with_context(|| format!("failed to merge config from cli: {:?}", config_file))?;
|
||||
crate_cli_network = false;
|
||||
control.set_read_only(true);
|
||||
control.set_no_delete(true);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Entering config client mode...\n server: {}\n token: {}",
|
||||
c_url, token,
|
||||
"Starting easytier from config file {:?}({:?}) with config:",
|
||||
config_file, control.permission
|
||||
);
|
||||
|
||||
println!("Official config website: https://easytier.cn/web");
|
||||
|
||||
if token.is_empty() {
|
||||
panic!("empty token");
|
||||
}
|
||||
|
||||
let config = TomlConfigLoader::default();
|
||||
let global_ctx = Arc::new(GlobalCtx::new(config));
|
||||
global_ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||
udp_nat_type: NatType::Unknown,
|
||||
}));
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.bind_device = false;
|
||||
global_ctx.set_flags(flags);
|
||||
let hostname = match cli.network_options.hostname {
|
||||
None => gethostname::gethostname().to_string_lossy().to_string(),
|
||||
Some(hostname) => hostname.to_string(),
|
||||
};
|
||||
let _wc = web_client::WebClient::new(
|
||||
create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?,
|
||||
token.to_string(),
|
||||
hostname,
|
||||
manager,
|
||||
);
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
let mut crate_cli_network =
|
||||
cli.config_file.is_none() || cli.network_options.network_name.is_some();
|
||||
if let Some(config_files) = cli.config_file {
|
||||
let config_file_count = config_files.len();
|
||||
for config_file in config_files {
|
||||
let mut cfg = if config_file == PathBuf::from("-") {
|
||||
let mut stdin = String::new();
|
||||
_ = tokio::io::stdin().read_to_string(&mut stdin).await?;
|
||||
TomlConfigLoader::new_from_str(stdin.as_str())
|
||||
.with_context(|| "failed to load config from stdin")?
|
||||
} else {
|
||||
TomlConfigLoader::new(&config_file)
|
||||
.with_context(|| format!("failed to load config file: {:?}", config_file))?
|
||||
};
|
||||
|
||||
if cli.network_options.can_merge(&cfg, config_file_count) {
|
||||
cli.network_options.merge_into(&mut cfg).with_context(|| {
|
||||
format!("failed to merge config from cli: {:?}", config_file)
|
||||
})?;
|
||||
crate_cli_network = false;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Starting easytier from config file {:?} with config:",
|
||||
config_file
|
||||
);
|
||||
println!("############### TOML ###############\n");
|
||||
println!("{}", cfg.dump());
|
||||
println!("-----------------------------------");
|
||||
manager.run_network_instance(cfg, ConfigSource::File)?;
|
||||
}
|
||||
println!("############### TOML ###############\n");
|
||||
println!("{}", cfg.dump());
|
||||
println!("-----------------------------------");
|
||||
manager.run_network_instance(cfg, true, control)?;
|
||||
}
|
||||
|
||||
if crate_cli_network {
|
||||
@@ -1246,7 +1249,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
println!("############### TOML ###############\n");
|
||||
println!("{}", cfg.dump());
|
||||
println!("-----------------------------------");
|
||||
manager.run_network_instance(cfg, ConfigSource::Cli)?;
|
||||
manager.run_network_instance(cfg, true, ConfigFileControl::STATIC_CONFIG)?;
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
|
||||
@@ -12,7 +12,7 @@ use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
use tokio::net::lookup_host;
|
||||
|
||||
use tracing::{debug, error};
|
||||
use tracing::debug;
|
||||
|
||||
/// SOCKS5 reply code
|
||||
#[derive(Error, Debug)]
|
||||
|
||||
@@ -275,7 +275,7 @@ impl IcmpProxy {
|
||||
}
|
||||
|
||||
let peer_manager = self.peer_manager.clone();
|
||||
let is_latency_first = self.global_ctx.get_flags().latency_first;
|
||||
let is_latency_first = self.global_ctx.latency_first();
|
||||
self.tasks.lock().await.spawn(
|
||||
async move {
|
||||
while let Some(mut msg) = receiver.recv().await {
|
||||
|
||||
@@ -437,7 +437,7 @@ impl UdpProxy {
|
||||
// forward packets to peer manager
|
||||
let mut receiver = self.receiver.lock().await.take().unwrap();
|
||||
let peer_manager = self.peer_manager.clone();
|
||||
let is_latency_first = self.global_ctx.get_flags().latency_first;
|
||||
let is_latency_first = self.global_ctx.latency_first();
|
||||
self.tasks.lock().await.spawn(async move {
|
||||
while let Some(mut msg) = receiver.recv().await {
|
||||
let hdr = msg.mut_peer_manager_header().unwrap();
|
||||
|
||||
@@ -492,16 +492,18 @@ impl MagicDnsServerInstance {
|
||||
let mut dns_server = Server::new(dns_config);
|
||||
dns_server.run().await?;
|
||||
|
||||
if !tun_inet.contains(&fake_ip) && tun_dev.is_some() {
|
||||
let cost = if cfg!(target_os = "windows") {
|
||||
Some(4)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ifcfg = IfConfiger {};
|
||||
ifcfg
|
||||
.add_ipv4_route(tun_dev.as_ref().unwrap(), fake_ip, 32, cost)
|
||||
.await?;
|
||||
if !tun_inet.contains(&fake_ip) {
|
||||
if let Some(tun_dev_name) = &tun_dev {
|
||||
let cost = if cfg!(target_os = "windows") {
|
||||
Some(4)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ifcfg = IfConfiger {};
|
||||
ifcfg
|
||||
.add_ipv4_route(tun_dev_name, fake_ip, 32, cost)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let data = Arc::new(MagicDnsServerInstanceData {
|
||||
@@ -544,13 +546,14 @@ impl MagicDnsServerInstance {
|
||||
if let Err(e) = ret {
|
||||
tracing::error!("Failed to close system config: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.tun_inet.contains(&self.data.fake_ip) && self.data.tun_dev.is_some() {
|
||||
let ifcfg = IfConfiger {};
|
||||
let _ = ifcfg
|
||||
.remove_ipv4_route(self.data.tun_dev.as_ref().unwrap(), self.data.fake_ip, 32)
|
||||
.await;
|
||||
if !self.tun_inet.contains(&self.data.fake_ip) {
|
||||
if let Some(tun_dev_name) = &self.data.tun_dev {
|
||||
let ifcfg = IfConfiger {};
|
||||
let _ = ifcfg
|
||||
.remove_ipv4_route(tun_dev_name, self.data.fake_ip, 32)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self
|
||||
|
||||
@@ -12,8 +12,8 @@ const MAC_RESOLVER_FILE_HEADER: &str = "# Added by easytier\n";
|
||||
const ETC_RESOLVER: &str = "/etc/resolver";
|
||||
const ETC_RESOLV_CONF: &str = "/etc/resolv.conf";
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DarwinConfigurator {}
|
||||
|
||||
impl DarwinConfigurator {
|
||||
pub fn new() -> Self {
|
||||
DarwinConfigurator {}
|
||||
|
||||
@@ -102,7 +102,7 @@ async fn test_magic_dns_server_instance() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let routes = vec![
|
||||
let routes = [
|
||||
Route {
|
||||
hostname: "test1".to_string(),
|
||||
ipv4_addr: Some(Ipv4Inet::from_str("8.8.8.8/24").unwrap().into()),
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
config::{ConfigLoader, TomlConfigLoader},
|
||||
config::{ConfigFileControl, ConfigLoader, TomlConfigLoader},
|
||||
global_ctx::{EventBusSubscriber, GlobalCtxEvent},
|
||||
scoped_task::ScopedTask,
|
||||
},
|
||||
launcher::{ConfigSource, NetworkInstance, NetworkInstanceRunningInfo},
|
||||
launcher::{NetworkInstance, NetworkInstanceRunningInfo},
|
||||
proto::{self},
|
||||
rpc_service::InstanceRpcService,
|
||||
};
|
||||
|
||||
pub(crate) struct WebClientGuard {
|
||||
guard: Option<Arc<()>>,
|
||||
stop_check_notifier: Arc<tokio::sync::Notify>,
|
||||
}
|
||||
impl Drop for WebClientGuard {
|
||||
fn drop(&mut self) {
|
||||
drop(self.guard.take());
|
||||
self.stop_check_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkInstanceManager {
|
||||
instance_map: Arc<DashMap<uuid::Uuid, NetworkInstance>>,
|
||||
instance_stop_tasks: Arc<DashMap<uuid::Uuid, ScopedTask<()>>>,
|
||||
stop_check_notifier: Arc<tokio::sync::Notify>,
|
||||
instance_error_messages: Arc<DashMap<uuid::Uuid, String>>,
|
||||
config_dir: Option<PathBuf>,
|
||||
web_client_counter: Arc<()>,
|
||||
}
|
||||
|
||||
impl Default for NetworkInstanceManager {
|
||||
@@ -33,36 +46,29 @@ impl NetworkInstanceManager {
|
||||
instance_stop_tasks: Arc::new(DashMap::new()),
|
||||
stop_check_notifier: Arc::new(tokio::sync::Notify::new()),
|
||||
instance_error_messages: Arc::new(DashMap::new()),
|
||||
config_dir: None,
|
||||
web_client_counter: Arc::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_config_path(mut self, config_dir: Option<PathBuf>) -> Self {
|
||||
self.config_dir = config_dir;
|
||||
self
|
||||
}
|
||||
|
||||
fn start_instance_task(&self, instance_id: uuid::Uuid) -> Result<(), anyhow::Error> {
|
||||
if tokio::runtime::Handle::try_current().is_err() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"tokio runtime not found, cannot start instance task"
|
||||
));
|
||||
}
|
||||
|
||||
let instance = self
|
||||
.instance_map
|
||||
.get(&instance_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("instance {} not found", instance_id))?;
|
||||
|
||||
match instance.get_config_source() {
|
||||
ConfigSource::FFI | ConfigSource::GUI => {
|
||||
// FFI and GUI have no tokio runtime, so we don't need to spawn a task
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
if tokio::runtime::Handle::try_current().is_err() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"tokio runtime not found, cannot start instance task"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instance_stop_notifier = instance.get_stop_notifier();
|
||||
let instance_event_receiver = match instance.get_config_source() {
|
||||
ConfigSource::Cli | ConfigSource::File | ConfigSource::Web => {
|
||||
Some(instance.subscribe_event())
|
||||
}
|
||||
ConfigSource::GUI | ConfigSource::FFI => None,
|
||||
};
|
||||
let instance_event_receiver = instance.subscribe_event();
|
||||
|
||||
let instance_map = self.instance_map.clone();
|
||||
let instance_stop_tasks = self.instance_stop_tasks.clone();
|
||||
@@ -76,7 +82,6 @@ impl NetworkInstanceManager {
|
||||
return;
|
||||
};
|
||||
let _t = instance_event_receiver
|
||||
.flatten()
|
||||
.map(|event| ScopedTask::from(handle_event(instance_id, event)));
|
||||
instance_stop_notifier.notified().await;
|
||||
if let Some(instance) = instance_map.get(&instance_id) {
|
||||
@@ -97,18 +102,21 @@ impl NetworkInstanceManager {
|
||||
pub fn run_network_instance(
|
||||
&self,
|
||||
cfg: TomlConfigLoader,
|
||||
source: ConfigSource,
|
||||
watch_event: bool,
|
||||
config_file_control: ConfigFileControl,
|
||||
) -> Result<uuid::Uuid, anyhow::Error> {
|
||||
let instance_id = cfg.get_id();
|
||||
if self.instance_map.contains_key(&instance_id) {
|
||||
anyhow::bail!("instance {} already exists", instance_id);
|
||||
}
|
||||
|
||||
let mut instance = NetworkInstance::new(cfg, source);
|
||||
let mut instance = NetworkInstance::new(cfg, config_file_control);
|
||||
instance.start()?;
|
||||
|
||||
self.instance_map.insert(instance_id, instance);
|
||||
self.start_instance_task(instance_id)?;
|
||||
if watch_event {
|
||||
self.start_instance_task(instance_id)?;
|
||||
}
|
||||
Ok(instance_id)
|
||||
}
|
||||
|
||||
@@ -187,18 +195,20 @@ impl NetworkInstanceManager {
|
||||
pub fn get_network_instance_name(&self, instance_id: &uuid::Uuid) -> Option<String> {
|
||||
self.instance_map
|
||||
.get(instance_id)
|
||||
.map(|instance| instance.value().get_inst_name())
|
||||
.map(|instance| instance.value().get_network_name())
|
||||
}
|
||||
|
||||
pub fn filter_network_instance(
|
||||
pub fn iter(&self) -> dashmap::iter::Iter<'_, uuid::Uuid, NetworkInstance> {
|
||||
self.instance_map.iter()
|
||||
}
|
||||
|
||||
pub fn get_instance_config_control(
|
||||
&self,
|
||||
filter: impl Fn(&uuid::Uuid, &NetworkInstance) -> bool,
|
||||
) -> Vec<uuid::Uuid> {
|
||||
instance_id: &uuid::Uuid,
|
||||
) -> Option<ConfigFileControl> {
|
||||
self.instance_map
|
||||
.iter()
|
||||
.filter(|item| filter(item.key(), item.value()))
|
||||
.map(|item| *item.key())
|
||||
.collect()
|
||||
.get(instance_id)
|
||||
.map(|instance| instance.value().get_config_file_control().clone())
|
||||
}
|
||||
|
||||
pub fn get_instance_service(
|
||||
@@ -219,12 +229,33 @@ impl NetworkInstanceManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_config_dir(&self) -> Option<&PathBuf> {
|
||||
self.config_dir.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn register_web_client(&self) -> WebClientGuard {
|
||||
WebClientGuard {
|
||||
guard: Some(self.web_client_counter.clone()),
|
||||
stop_check_notifier: self.stop_check_notifier.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn notify_stop_check(&self) {
|
||||
self.stop_check_notifier.notify_one();
|
||||
}
|
||||
|
||||
pub async fn wait(&self) {
|
||||
while self
|
||||
.instance_map
|
||||
.iter()
|
||||
.any(|item| item.value().is_easytier_running())
|
||||
{
|
||||
loop {
|
||||
let local_instance_running = self
|
||||
.instance_map
|
||||
.iter()
|
||||
.any(|item| item.value().is_easytier_running());
|
||||
let web_client_running = Arc::strong_count(&self.web_client_counter) > 1;
|
||||
|
||||
if !local_instance_running && !web_client_running {
|
||||
break;
|
||||
}
|
||||
|
||||
self.stop_check_notifier.notified().await;
|
||||
}
|
||||
}
|
||||
@@ -429,31 +460,36 @@ mod tests {
|
||||
c.set_listeners(vec![format!("tcp://0.0.0.0:{}", port).parse().unwrap()]);
|
||||
})
|
||||
.unwrap(),
|
||||
ConfigSource::Cli,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id2 = manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::File,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id3 = manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::GUI,
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id4 = manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::Web,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id5 = manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::FFI,
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -491,13 +527,15 @@ mod tests {
|
||||
assert!(manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::Cli,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_err());
|
||||
assert!(manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::File,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_err());
|
||||
assert!(manager
|
||||
@@ -507,19 +545,22 @@ mod tests {
|
||||
c.set_listeners(vec![format!("tcp://0.0.0.0:{}", port).parse().unwrap()]);
|
||||
})
|
||||
.unwrap(),
|
||||
ConfigSource::GUI,
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_ok());
|
||||
assert!(manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::Web,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_err());
|
||||
assert!(manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
ConfigSource::FFI,
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
@@ -546,7 +587,8 @@ mod tests {
|
||||
let free_tcp_port =
|
||||
crate::utils::find_free_tcp_port(10012..65534).expect("no free tcp port found");
|
||||
|
||||
for config_source in [ConfigSource::Cli, ConfigSource::File] {
|
||||
// Test with event watching enabled (for CLI/File/RPC usage) - instance should auto-stop on error
|
||||
for watch_event in [true] {
|
||||
let _port_holder =
|
||||
std::net::TcpListener::bind(format!("0.0.0.0:{}", free_tcp_port)).unwrap();
|
||||
|
||||
@@ -561,7 +603,8 @@ mod tests {
|
||||
manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
config_source.clone(),
|
||||
watch_event,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -570,11 +613,14 @@ mod tests {
|
||||
assert_eq!(manager.list_network_instance_ids().len(), 1);
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(5)) => {
|
||||
panic!("instance manager with single failed instance({:?}) should not running", config_source);
|
||||
panic!("instance manager with single failed instance({:?}) should not running", watch_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
for config_source in [ConfigSource::Web, ConfigSource::GUI, ConfigSource::FFI] {
|
||||
|
||||
// Test without event watching (for FFI usage) - instance should remain even if failed
|
||||
{
|
||||
let watch_event = false;
|
||||
let _port_holder =
|
||||
std::net::TcpListener::bind(format!("0.0.0.0:{}", free_tcp_port)).unwrap();
|
||||
|
||||
@@ -589,7 +635,8 @@ mod tests {
|
||||
manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
config_source.clone(),
|
||||
watch_event,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -616,7 +663,8 @@ mod tests {
|
||||
manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
ConfigSource::Cli,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -625,7 +673,8 @@ mod tests {
|
||||
manager
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
ConfigSource::Cli,
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::common::config::PortForwardConfig;
|
||||
use crate::common::config::{ConfigFileControl, PortForwardConfig};
|
||||
use crate::proto::api::{self, manage};
|
||||
use crate::proto::rpc_types::controller::BaseController;
|
||||
use crate::rpc_service::InstanceRpcService;
|
||||
@@ -10,7 +10,6 @@ use crate::{
|
||||
},
|
||||
constants::EASYTIER_VERSION,
|
||||
global_ctx::{EventBusSubscriber, GlobalCtxEvent},
|
||||
idn::safe_convert_idn_to_ascii,
|
||||
},
|
||||
instance::instance::Instance,
|
||||
proto::api::instance::list_peer_route_pair,
|
||||
@@ -282,35 +281,21 @@ impl Drop for EasyTierLauncher {
|
||||
|
||||
pub type NetworkInstanceRunningInfo = crate::proto::api::manage::NetworkInstanceRunningInfo;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConfigSource {
|
||||
Cli,
|
||||
File,
|
||||
Web,
|
||||
GUI,
|
||||
FFI,
|
||||
}
|
||||
|
||||
pub struct NetworkInstance {
|
||||
config: TomlConfigLoader,
|
||||
launcher: Option<EasyTierLauncher>,
|
||||
|
||||
config_source: ConfigSource,
|
||||
config_file_control: ConfigFileControl,
|
||||
}
|
||||
|
||||
impl NetworkInstance {
|
||||
pub fn new(config: TomlConfigLoader, source: ConfigSource) -> Self {
|
||||
pub fn new(config: TomlConfigLoader, config_file_control: ConfigFileControl) -> Self {
|
||||
Self {
|
||||
config,
|
||||
launcher: None,
|
||||
config_source: source,
|
||||
config_file_control,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_source(&self) -> ConfigSource {
|
||||
self.config_source.clone()
|
||||
}
|
||||
|
||||
pub fn is_easytier_running(&self) -> bool {
|
||||
self.launcher.is_some() && self.launcher.as_ref().unwrap().running()
|
||||
}
|
||||
@@ -404,6 +389,10 @@ impl NetworkInstance {
|
||||
self.config.get_inst_name()
|
||||
}
|
||||
|
||||
pub fn get_network_name(&self) -> String {
|
||||
self.config.get_network_identity().network_name
|
||||
}
|
||||
|
||||
pub fn set_tun_fd(&mut self, tun_fd: i32) {
|
||||
if let Some(launcher) = self.launcher.as_ref() {
|
||||
launcher.data.tun_fd.write().unwrap().replace(tun_fd);
|
||||
@@ -439,6 +428,10 @@ impl NetworkInstance {
|
||||
.map(|launcher| launcher.data.instance_stop_notifier.clone())
|
||||
}
|
||||
|
||||
pub fn get_config_file_control(&self) -> &ConfigFileControl {
|
||||
&self.config_file_control
|
||||
}
|
||||
|
||||
pub fn get_latest_error_msg(&self) -> Option<String> {
|
||||
if let Some(launcher) = self.launcher.as_ref() {
|
||||
launcher.error_msg.read().unwrap().clone()
|
||||
@@ -525,13 +518,9 @@ impl NetworkConfig {
|
||||
{
|
||||
NetworkingMethod::PublicServer => {
|
||||
let public_server_url = self.public_server_url.clone().unwrap_or_default();
|
||||
let converted_public_server_url = safe_convert_idn_to_ascii(&public_server_url);
|
||||
cfg.set_peers(vec![PeerConfig {
|
||||
uri: converted_public_server_url.parse().with_context(|| {
|
||||
format!(
|
||||
"failed to parse public server uri: {}",
|
||||
converted_public_server_url
|
||||
)
|
||||
uri: public_server_url.parse().with_context(|| {
|
||||
format!("failed to parse public server uri: {}", public_server_url)
|
||||
})?,
|
||||
}]);
|
||||
}
|
||||
@@ -541,11 +530,10 @@ impl NetworkConfig {
|
||||
if peer_url.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let converted_peer_url = safe_convert_idn_to_ascii(peer_url);
|
||||
peers.push(PeerConfig {
|
||||
uri: converted_peer_url.parse().with_context(|| {
|
||||
format!("failed to parse peer uri: {}", converted_peer_url)
|
||||
})?,
|
||||
uri: peer_url
|
||||
.parse()
|
||||
.with_context(|| format!("failed to parse peer uri: {}", peer_url))?,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -559,10 +547,11 @@ impl NetworkConfig {
|
||||
if listener_url.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let converted_listener_url = safe_convert_idn_to_ascii(listener_url);
|
||||
listener_urls.push(converted_listener_url.parse().with_context(|| {
|
||||
format!("failed to parse listener uri: {}", converted_listener_url)
|
||||
})?);
|
||||
listener_urls.push(
|
||||
listener_url
|
||||
.parse()
|
||||
.with_context(|| format!("failed to parse listener uri: {}", listener_url))?,
|
||||
);
|
||||
}
|
||||
cfg.set_listeners(listener_urls);
|
||||
|
||||
@@ -656,12 +645,8 @@ impl NetworkConfig {
|
||||
self.mapped_listeners
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let converted_s = safe_convert_idn_to_ascii(s);
|
||||
converted_s
|
||||
.parse()
|
||||
.with_context(|| {
|
||||
format!("mapped listener is not a valid url: {}", converted_s)
|
||||
})
|
||||
s.parse()
|
||||
.with_context(|| format!("mapped listener is not a valid url: {}", s))
|
||||
.unwrap()
|
||||
})
|
||||
.map(|s: url::Url| {
|
||||
@@ -715,6 +700,10 @@ impl NetworkConfig {
|
||||
flags.disable_p2p = disable_p2p;
|
||||
}
|
||||
|
||||
if let Some(p2p_only) = self.p2p_only {
|
||||
flags.p2p_only = p2p_only;
|
||||
}
|
||||
|
||||
if let Some(bind_device) = self.bind_device {
|
||||
flags.bind_device = bind_device;
|
||||
}
|
||||
@@ -889,6 +878,7 @@ impl NetworkConfig {
|
||||
result.disable_quic_input = Some(flags.disable_quic_input);
|
||||
result.quic_listen_port = Some(flags.quic_listen_port as i32);
|
||||
result.disable_p2p = Some(flags.disable_p2p);
|
||||
result.p2p_only = Some(flags.p2p_only);
|
||||
result.bind_device = Some(flags.bind_device);
|
||||
result.no_tun = Some(flags.no_tun);
|
||||
result.enable_exit_node = Some(flags.enable_exit_node);
|
||||
@@ -897,6 +887,7 @@ impl NetworkConfig {
|
||||
result.proxy_forward_by_system = Some(flags.proxy_forward_by_system);
|
||||
result.disable_encryption = Some(!flags.enable_encryption);
|
||||
result.disable_udp_hole_punching = Some(flags.disable_udp_hole_punching);
|
||||
result.disable_sym_hole_punching = Some(flags.disable_sym_hole_punching);
|
||||
result.enable_magic_dns = Some(flags.accept_dns);
|
||||
result.mtu = Some(flags.mtu as i32);
|
||||
result.enable_private_mode = Some(flags.private_mode);
|
||||
@@ -1123,6 +1114,7 @@ mod tests {
|
||||
flags.enable_quic_proxy = rng.gen_bool(0.5);
|
||||
flags.disable_quic_input = rng.gen_bool(0.3);
|
||||
flags.disable_p2p = rng.gen_bool(0.2);
|
||||
flags.p2p_only = rng.gen_bool(0.2);
|
||||
flags.bind_device = rng.gen_bool(0.3);
|
||||
flags.no_tun = rng.gen_bool(0.1);
|
||||
flags.enable_exit_node = rng.gen_bool(0.4);
|
||||
|
||||
@@ -997,11 +997,20 @@ impl PeerManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_p2p_only_before_send(&self, dst_peer_id: PeerId) -> Result<(), Error> {
|
||||
if self.global_ctx.p2p_only() && !self.peers.has_peer(dst_peer_id) {
|
||||
return Err(Error::RouteError(None));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_msg_for_proxy(
|
||||
&self,
|
||||
mut msg: ZCPacket,
|
||||
dst_peer_id: PeerId,
|
||||
) -> Result<(), Error> {
|
||||
self.check_p2p_only_before_send(dst_peer_id)?;
|
||||
|
||||
self.self_tx_counters
|
||||
.compress_tx_bytes_before
|
||||
.add(msg.buf_len() as u64);
|
||||
@@ -1199,7 +1208,7 @@ impl PeerManager {
|
||||
.compress_tx_bytes_after
|
||||
.add(msg.buf_len() as u64);
|
||||
|
||||
let is_latency_first = self.global_ctx.get_flags().latency_first;
|
||||
let is_latency_first = self.global_ctx.latency_first();
|
||||
msg.mut_peer_manager_header()
|
||||
.unwrap()
|
||||
.set_latency_first(is_latency_first)
|
||||
@@ -1209,6 +1218,11 @@ impl PeerManager {
|
||||
let mut msg = Some(msg);
|
||||
let total_dst_peers = dst_peers.len();
|
||||
for (i, peer_id) in dst_peers.iter().enumerate() {
|
||||
if let Err(e) = self.check_p2p_only_before_send(*peer_id) {
|
||||
errs.push(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut msg = if i == total_dst_peers - 1 {
|
||||
msg.take().unwrap()
|
||||
} else {
|
||||
@@ -1218,10 +1232,13 @@ impl PeerManager {
|
||||
let hdr = msg.mut_peer_manager_header().unwrap();
|
||||
hdr.to_peer_id.set(*peer_id);
|
||||
|
||||
if not_send_to_self && *peer_id == self.my_peer_id {
|
||||
// the packet may be sent to vpn portal, so we just set flags instead of drop it
|
||||
hdr.set_not_send_to_tun(true);
|
||||
hdr.set_no_proxy(true);
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
{
|
||||
if not_send_to_self && *peer_id == self.my_peer_id {
|
||||
// the packet may be sent to vpn portal, so we just set flags instead of drop it
|
||||
hdr.set_not_send_to_tun(true);
|
||||
hdr.set_no_proxy(true);
|
||||
}
|
||||
}
|
||||
|
||||
self.self_tx_counters
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap, VecDeque},
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
fmt::Debug,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
sync::{
|
||||
@@ -13,8 +13,6 @@ use arc_swap::ArcSwap;
|
||||
use cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr};
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use dashmap::DashMap;
|
||||
use ordered_hash_map::OrderedHashMap;
|
||||
use parking_lot::{lock_api::RwLockUpgradableReadGuard, RwLock};
|
||||
use petgraph::{
|
||||
algo::dijkstra,
|
||||
graph::{Graph, NodeIndex},
|
||||
@@ -43,7 +41,7 @@ use crate::{
|
||||
route_foreign_network_infos, route_foreign_network_summary,
|
||||
sync_route_info_request::ConnInfo, ForeignNetworkRouteInfoEntry,
|
||||
ForeignNetworkRouteInfoKey, OspfRouteRpc, OspfRouteRpcClientFactory,
|
||||
OspfRouteRpcServer, PeerGroupInfo, PeerIdVersion, RouteForeignNetworkInfos,
|
||||
OspfRouteRpcServer, PeerIdVersion, RouteForeignNetworkInfos,
|
||||
RouteForeignNetworkSummary, RoutePeerInfo, RoutePeerInfos, SyncRouteInfoError,
|
||||
SyncRouteInfoRequest, SyncRouteInfoResponse,
|
||||
},
|
||||
@@ -72,12 +70,6 @@ static REMOVE_DEAD_PEER_INFO_AFTER: Duration = Duration::from_secs(3660);
|
||||
static AVOID_RELAY_COST: usize = i32::MAX as usize;
|
||||
static FORCE_USE_CONN_LIST: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
// if a peer is unreachable for `REMOVE_UNREACHABLE_PEER_INFO_AFTER` time, we can remove it because
|
||||
// 1. all the ospf sessions between two zone are already destroy, new created session will resend the peer info.
|
||||
// 2. all the dst_saved_peer_info_version in all sessions already remove the peer info, the peer info will be propagated
|
||||
// in another zone when two zone restore the conneciton.
|
||||
static REMOVE_UNREACHABLE_PEER_INFO_AFTER: Duration = Duration::from_secs(90);
|
||||
|
||||
type Version = u32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -132,9 +124,7 @@ impl RoutePeerInfo {
|
||||
proxy_cidrs: Vec::new(),
|
||||
hostname: None,
|
||||
udp_stun_info: 0,
|
||||
// ensure this is updated when the peer_infos/conn_info/foreign_network lock is acquired.
|
||||
// else we may assign a older timestamp than iterate time.
|
||||
last_update: None,
|
||||
last_update: Some(SystemTime::now().into()),
|
||||
version: 0,
|
||||
easytier_version: EASYTIER_VERSION.to_string(),
|
||||
feature_flag: None,
|
||||
@@ -146,12 +136,13 @@ impl RoutePeerInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_updated_self(
|
||||
pub fn update_self(
|
||||
&self,
|
||||
my_peer_id: PeerId,
|
||||
peer_route_id: u64,
|
||||
global_ctx: &ArcGlobalCtx,
|
||||
) -> Self {
|
||||
Self {
|
||||
let mut new = Self {
|
||||
peer_id: my_peer_id,
|
||||
inst_id: Some(global_ctx.get_id().into()),
|
||||
cost: 0,
|
||||
@@ -169,10 +160,9 @@ impl RoutePeerInfo {
|
||||
.get_stun_info_collector()
|
||||
.get_stun_info()
|
||||
.udp_nat_type,
|
||||
|
||||
// these two fields should not participate in comparison.
|
||||
last_update: None,
|
||||
version: 0,
|
||||
// following fields do not participate in comparison.
|
||||
last_update: self.last_update,
|
||||
version: self.version,
|
||||
|
||||
easytier_version: EASYTIER_VERSION.to_string(),
|
||||
feature_flag: Some(global_ctx.get_feature_flags()),
|
||||
@@ -186,28 +176,22 @@ impl RoutePeerInfo {
|
||||
ipv6_addr: global_ctx.get_ipv6().map(|x| x.into()),
|
||||
|
||||
groups: global_ctx.get_acl_groups(my_peer_id),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn try_update_new_peer_info(old: &RoutePeerInfo, new: &mut RoutePeerInfo) -> bool {
|
||||
let need_update_periodically = if let Ok(Ok(d)) =
|
||||
SystemTime::try_from(old.last_update.unwrap_or_default()).map(|x| x.elapsed())
|
||||
SystemTime::try_from(new.last_update.unwrap_or_default()).map(|x| x.elapsed())
|
||||
{
|
||||
d > UPDATE_PEER_INFO_PERIOD
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
// these two fields should not participate in comparison.
|
||||
new.version = old.version;
|
||||
new.last_update = old.last_update;
|
||||
|
||||
if *new != *old || need_update_periodically {
|
||||
if new != *self || need_update_periodically {
|
||||
new.last_update = Some(SystemTime::now().into());
|
||||
new.version += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +261,7 @@ type Error = SyncRouteInfoError;
|
||||
|
||||
// constructed with all infos synced from all peers.
|
||||
struct SyncedRouteInfo {
|
||||
peer_infos: RwLock<OrderedHashMap<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.
|
||||
raw_peer_infos: DashMap<PeerId, DynamicMessage>,
|
||||
conn_map: DashMap<PeerId, (BTreeSet<PeerId>, AtomicVersion)>,
|
||||
@@ -309,13 +293,14 @@ impl SyncedRouteInfo {
|
||||
|
||||
fn remove_peer(&self, peer_id: PeerId) {
|
||||
tracing::warn!(?peer_id, "remove_peer from synced_route_info");
|
||||
self.peer_infos.write().remove(&peer_id);
|
||||
self.peer_infos.remove(&peer_id);
|
||||
self.raw_peer_infos.remove(&peer_id);
|
||||
self.conn_map.remove(&peer_id);
|
||||
self.foreign_network.retain(|k, _| k.peer_id != peer_id);
|
||||
self.group_trust_map.remove(&peer_id);
|
||||
self.group_trust_map_cache.remove(&peer_id);
|
||||
|
||||
shrink_dashmap(&self.peer_infos, None);
|
||||
shrink_dashmap(&self.raw_peer_infos, None);
|
||||
shrink_dashmap(&self.conn_map, None);
|
||||
shrink_dashmap(&self.foreign_network, None);
|
||||
@@ -328,16 +313,10 @@ impl SyncedRouteInfo {
|
||||
fn fill_empty_peer_info(&self, peer_ids: &BTreeSet<PeerId>) {
|
||||
let mut need_inc_version = false;
|
||||
for peer_id in peer_ids {
|
||||
let guard = self.peer_infos.upgradable_read();
|
||||
if !guard.contains_key(peer_id) {
|
||||
let mut peer_info = RoutePeerInfo::new();
|
||||
let mut guard = RwLockUpgradableReadGuard::upgrade(guard);
|
||||
peer_info.last_update = Some(SystemTime::now().into());
|
||||
guard.insert(*peer_id, peer_info);
|
||||
self.peer_infos.entry(*peer_id).or_insert_with(|| {
|
||||
need_inc_version = true;
|
||||
} else {
|
||||
drop(guard);
|
||||
}
|
||||
RoutePeerInfo::new()
|
||||
});
|
||||
|
||||
self.conn_map.entry(*peer_id).or_insert_with(|| {
|
||||
need_inc_version = true;
|
||||
@@ -351,7 +330,6 @@ impl SyncedRouteInfo {
|
||||
|
||||
fn get_peer_info_version_with_default(&self, peer_id: PeerId) -> Version {
|
||||
self.peer_infos
|
||||
.read()
|
||||
.get(&peer_id)
|
||||
.map(|x| x.version)
|
||||
.unwrap_or(0)
|
||||
@@ -360,9 +338,8 @@ impl SyncedRouteInfo {
|
||||
fn get_avoid_relay_data(&self, peer_id: PeerId) -> bool {
|
||||
// if avoid relay, just set all outgoing edges to a large value: AVOID_RELAY_COST.
|
||||
self.peer_infos
|
||||
.read()
|
||||
.get(&peer_id)
|
||||
.and_then(|x| x.feature_flag)
|
||||
.and_then(|x| x.value().feature_flag)
|
||||
.map(|x| x.avoid_relay_data)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -418,10 +395,7 @@ impl SyncedRouteInfo {
|
||||
my_peer_route_id,
|
||||
dst_peer_id,
|
||||
if route_info.peer_id == dst_peer_id {
|
||||
self.peer_infos
|
||||
.read()
|
||||
.get(&dst_peer_id)
|
||||
.map(|x| x.peer_route_id)
|
||||
self.peer_infos.get(&dst_peer_id).map(|x| x.peer_route_id)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -435,19 +409,26 @@ impl SyncedRouteInfo {
|
||||
.unwrap();
|
||||
assert_eq!(peer_id_raw, route_info.peer_id);
|
||||
|
||||
let mut guard = self.peer_infos.write();
|
||||
// time between peers may not be synchronized, so update last_update to local now.
|
||||
// note only last_update with larger version will be updated to local saved peer info.
|
||||
route_info.last_update = Some(SystemTime::now().into());
|
||||
if guard
|
||||
.get_mut(&route_info.peer_id)
|
||||
.is_none_or(|old| route_info.version > old.version)
|
||||
{
|
||||
self.raw_peer_infos
|
||||
.insert(route_info.peer_id, raw_route_info.clone());
|
||||
guard.insert(route_info.peer_id, route_info);
|
||||
need_inc_version = true;
|
||||
}
|
||||
|
||||
self.peer_infos
|
||||
.entry(route_info.peer_id)
|
||||
.and_modify(|old_entry| {
|
||||
if route_info.version > old_entry.version {
|
||||
self.raw_peer_infos
|
||||
.insert(route_info.peer_id, raw_route_info.clone());
|
||||
*old_entry = route_info.clone();
|
||||
need_inc_version = true;
|
||||
}
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
need_inc_version = true;
|
||||
self.raw_peer_infos
|
||||
.insert(route_info.peer_id, raw_route_info.clone());
|
||||
route_info.clone()
|
||||
});
|
||||
}
|
||||
if need_inc_version {
|
||||
self.version.inc();
|
||||
@@ -554,34 +535,15 @@ impl SyncedRouteInfo {
|
||||
my_peer_route_id: u64,
|
||||
global_ctx: &ArcGlobalCtx,
|
||||
) -> bool {
|
||||
let mut new = RoutePeerInfo::new_updated_self(my_peer_id, my_peer_route_id, global_ctx);
|
||||
let mut guard = self.peer_infos.upgradable_read();
|
||||
let old = guard.get(&my_peer_id);
|
||||
let new_version = old.map(|x| x.version).unwrap_or(0) + 1;
|
||||
let need_insert_new = if let Some(old) = old {
|
||||
RoutePeerInfo::try_update_new_peer_info(old, &mut new)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if need_insert_new {
|
||||
let acl_groups = if old.map(|x| x.groups != new.groups).unwrap_or(true) {
|
||||
Some(new.groups.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
guard.with_upgraded(|peer_infos| {
|
||||
new.last_update = Some(SystemTime::now().into());
|
||||
new.version = new_version;
|
||||
peer_infos.insert(my_peer_id, new)
|
||||
});
|
||||
drop(guard);
|
||||
|
||||
if let Some(acl_groups) = acl_groups {
|
||||
self.update_my_group_trusts(my_peer_id, &acl_groups);
|
||||
}
|
||||
let mut old = self.peer_infos.entry(my_peer_id).or_default();
|
||||
let new = old.update_self(my_peer_id, my_peer_route_id, global_ctx);
|
||||
let new_version = new.version;
|
||||
let old_version = old.version;
|
||||
*old = new;
|
||||
drop(old);
|
||||
|
||||
if new_version != old_version {
|
||||
self.update_my_group_trusts(my_peer_id);
|
||||
self.version.inc();
|
||||
true
|
||||
} else {
|
||||
@@ -672,28 +634,6 @@ impl SyncedRouteInfo {
|
||||
updated
|
||||
}
|
||||
|
||||
fn get_next_last_sync_succ_timestamp(&self) -> SystemTime {
|
||||
let _peer_info_lock = self.peer_infos.read();
|
||||
// TODO: add conn and foreign network lock
|
||||
|
||||
SystemTime::now()
|
||||
}
|
||||
|
||||
fn check_peer_info_last_update_monotonic_increasing(&self) {
|
||||
let mut last_update: Option<prost_types::Timestamp> = None;
|
||||
for peer_info in self.peer_infos.read().values() {
|
||||
if let Some(last_update) = last_update {
|
||||
let cur_last_update = peer_info.last_update.unwrap();
|
||||
assert!(
|
||||
cur_last_update.seconds > last_update.seconds
|
||||
|| cur_last_update.nanos >= last_update.nanos,
|
||||
"peer info last update not monotonic increasing"
|
||||
);
|
||||
}
|
||||
last_update = peer_info.last_update;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_peer_bidirectly_connected(&self, src_peer_id: PeerId, dst_peer_id: PeerId) -> bool {
|
||||
self.conn_map
|
||||
.get(&src_peer_id)
|
||||
@@ -785,15 +725,13 @@ impl SyncedRouteInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_my_group_trusts(&self, my_peer_id: PeerId, groups: &[PeerGroupInfo]) {
|
||||
fn update_my_group_trusts(&self, my_peer_id: PeerId) {
|
||||
let mut my_group_map = HashMap::new();
|
||||
let mut my_group_names = Vec::new();
|
||||
|
||||
for group in groups.iter() {
|
||||
for group in self.peer_infos.entry(my_peer_id).or_default().groups.iter() {
|
||||
my_group_map.insert(group.group_name.clone(), group.group_proof.clone());
|
||||
my_group_names.push(group.group_name.clone());
|
||||
}
|
||||
|
||||
self.group_trust_map.insert(my_peer_id, my_group_map);
|
||||
self.group_trust_map_cache
|
||||
.insert(my_peer_id, Arc::new(my_group_names));
|
||||
@@ -811,16 +749,21 @@ struct NextHopInfo {
|
||||
}
|
||||
// dst_peer_id -> (next_hop_peer_id, cost, path_len)
|
||||
type NextHopMap = DashMap<PeerId, NextHopInfo>;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct PeerIdAndVersion {
|
||||
peer_id: PeerId,
|
||||
version: Version,
|
||||
}
|
||||
|
||||
// computed with SyncedRouteInfo. used to get next hop.
|
||||
#[derive(Debug)]
|
||||
struct RouteTable {
|
||||
peer_infos: DashMap<PeerId, RoutePeerInfo>,
|
||||
next_hop_map: NextHopMap,
|
||||
ipv4_peer_id_map: DashMap<Ipv4Addr, PeerIdVersion>,
|
||||
ipv6_peer_id_map: DashMap<Ipv6Addr, PeerIdVersion>,
|
||||
cidr_peer_id_map: ArcSwap<PrefixMap<Ipv4Cidr, PeerIdVersion>>,
|
||||
cidr_v6_peer_id_map: ArcSwap<PrefixMap<Ipv6Cidr, PeerIdVersion>>,
|
||||
ipv4_peer_id_map: DashMap<Ipv4Addr, PeerIdAndVersion>,
|
||||
ipv6_peer_id_map: DashMap<Ipv6Addr, PeerIdAndVersion>,
|
||||
cidr_peer_id_map: ArcSwap<PrefixMap<Ipv4Cidr, PeerIdAndVersion>>,
|
||||
cidr_v6_peer_id_map: ArcSwap<PrefixMap<Ipv6Cidr, PeerIdAndVersion>>,
|
||||
next_hop_map_version: AtomicVersion,
|
||||
}
|
||||
|
||||
@@ -868,17 +811,18 @@ impl RouteTable {
|
||||
|
||||
let mut start_node_idx = None;
|
||||
let peer_id_to_node_index: PeerIdToNodexIdxMap = DashMap::new();
|
||||
for (peer_id, info) in synced_info.peer_infos.read().iter() {
|
||||
let peer_id = *peer_id;
|
||||
for item in synced_info.peer_infos.iter() {
|
||||
let peer_id = item.key();
|
||||
let info = item.value();
|
||||
|
||||
if info.version == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let node_idx = graph.add_node(peer_id);
|
||||
let node_idx = graph.add_node(*peer_id);
|
||||
|
||||
peer_id_to_node_index.insert(peer_id, node_idx);
|
||||
if peer_id == my_peer_id {
|
||||
peer_id_to_node_index.insert(*peer_id, node_idx);
|
||||
if *peer_id == my_peer_id {
|
||||
start_node_idx = Some(node_idx);
|
||||
}
|
||||
}
|
||||
@@ -1047,18 +991,18 @@ impl RouteTable {
|
||||
}
|
||||
|
||||
let peer_id = item.key();
|
||||
let Some(info) = synced_info.peer_infos.read().get(peer_id).cloned() else {
|
||||
let Some(info) = synced_info.peer_infos.get(peer_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.peer_infos.insert(*peer_id, info.clone());
|
||||
|
||||
let peer_id_and_version = PeerIdVersion {
|
||||
let peer_id_and_version = PeerIdAndVersion {
|
||||
peer_id: *peer_id,
|
||||
version,
|
||||
};
|
||||
|
||||
let is_new_peer_better = |old_peer: &PeerIdVersion| -> bool {
|
||||
let is_new_peer_better = |old_peer: &PeerIdAndVersion| -> bool {
|
||||
if peer_id_and_version.version > old_peer.version {
|
||||
return true;
|
||||
}
|
||||
@@ -1250,11 +1194,6 @@ struct SyncRouteSession {
|
||||
dst_saved_conn_info_version: DashMap<PeerId, VersionAndTouchTime>,
|
||||
dst_saved_foreign_network_versions: DashMap<ForeignNetworkRouteInfoKey, VersionAndTouchTime>,
|
||||
|
||||
// we don't want to send unreachable peer infos to peer, so we keep track of them.
|
||||
unreachable_peers: parking_lot::Mutex<VecDeque<PeerIdVersion>>,
|
||||
|
||||
last_sync_succ_timestamp: AtomicCell<Option<SystemTime>>,
|
||||
|
||||
my_session_id: AtomicSessionId,
|
||||
dst_session_id: AtomicSessionId,
|
||||
|
||||
@@ -1279,10 +1218,6 @@ impl SyncRouteSession {
|
||||
dst_saved_conn_info_version: DashMap::new(),
|
||||
dst_saved_foreign_network_versions: DashMap::new(),
|
||||
|
||||
unreachable_peers: parking_lot::Mutex::new(VecDeque::new()),
|
||||
|
||||
last_sync_succ_timestamp: AtomicCell::new(None),
|
||||
|
||||
my_session_id: AtomicSessionId::new(rand::random()),
|
||||
dst_session_id: AtomicSessionId::new(0),
|
||||
|
||||
@@ -1428,15 +1363,12 @@ impl SyncRouteSession {
|
||||
self.need_sync_initiator_info.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// return whether session id is updated
|
||||
fn update_dst_session_id(&self, session_id: SessionId) {
|
||||
if session_id != self.dst_session_id.load(Ordering::Relaxed) {
|
||||
tracing::warn!(?self, ?session_id, "session id mismatch, clear saved info.");
|
||||
self.dst_session_id.store(session_id, Ordering::Relaxed);
|
||||
self.dst_saved_conn_info_version.clear();
|
||||
self.dst_saved_peer_info_versions.clear();
|
||||
self.last_sync_succ_timestamp.store(None);
|
||||
self.unreachable_peers.lock().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1454,16 +1386,6 @@ impl SyncRouteSession {
|
||||
self.dst_saved_foreign_network_versions.shrink_to_fit();
|
||||
}
|
||||
|
||||
fn update_last_sync_succ_timestamp(&self, next_last_sync_succ_timestamp: SystemTime) {
|
||||
let _ = self.last_sync_succ_timestamp.fetch_update(|x| {
|
||||
if x.is_none_or(|old| old < next_last_sync_succ_timestamp) {
|
||||
Some(Some(next_last_sync_succ_timestamp))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn short_debug_string(&self) -> String {
|
||||
format!(
|
||||
"session_dst_peer: {:?}, my_session_id: {:?}, dst_session_id: {:?}, we_are_initiator: {:?}, dst_is_initiator: {:?}, rpc_tx_count: {:?}, rpc_rx_count: {:?}, task: {:?}",
|
||||
@@ -1548,7 +1470,7 @@ impl PeerRouteServiceImpl {
|
||||
foreign_network_my_peer_id_map: DashMap::new(),
|
||||
|
||||
synced_route_info: SyncedRouteInfo {
|
||||
peer_infos: RwLock::new(OrderedHashMap::new()),
|
||||
peer_infos: DashMap::new(),
|
||||
raw_peer_infos: DashMap::new(),
|
||||
conn_map: DashMap::new(),
|
||||
foreign_network: DashMap::new(),
|
||||
@@ -1792,72 +1714,21 @@ impl PeerRouteServiceImpl {
|
||||
}
|
||||
|
||||
fn build_route_info(&self, session: &SyncRouteSession) -> Option<Vec<RoutePeerInfo>> {
|
||||
self.synced_route_info
|
||||
.check_peer_info_last_update_monotonic_increasing();
|
||||
let mut route_infos = Vec::new();
|
||||
let peer_infos = self.synced_route_info.peer_infos.read();
|
||||
let last_sync_succ_timestamp = session.last_sync_succ_timestamp.load();
|
||||
for (peer_id, peer_info) in peer_infos.iter().rev() {
|
||||
// stop iter if last_update of peer info is older than session.latest_scanned_peer_info_timestamp
|
||||
if let Some(last_update) = peer_info.last_update {
|
||||
let last_update = TryInto::<SystemTime>::try_into(last_update).unwrap();
|
||||
if last_sync_succ_timestamp.is_some_and(|t| last_update < t) {
|
||||
tracing::debug!(
|
||||
"ignore peer_info {:?} because last_update: {:?} is older than last_sync_succ_timestamp: {:?}, peer_infos_count: {}, my_peer_id: {:?}, session: {:?}",
|
||||
peer_info,
|
||||
last_update,
|
||||
last_sync_succ_timestamp,
|
||||
peer_infos.len(),
|
||||
self.my_peer_id,
|
||||
session
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if session.check_saved_peer_info_update_to_date(peer_info.peer_id, peer_info.version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for item in self.synced_route_info.peer_infos.iter() {
|
||||
// do not send unreachable peer info to dst peer.
|
||||
if !self.route_table.peer_reachable(*peer_id) {
|
||||
session.unreachable_peers.lock().push_front(PeerIdVersion {
|
||||
peer_id: *peer_id,
|
||||
version: peer_info.version,
|
||||
});
|
||||
if !self.route_table.peer_reachable(*item.key()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
route_infos.push(peer_info.clone());
|
||||
}
|
||||
|
||||
let mut unreachable_peers = session.unreachable_peers.lock();
|
||||
let cur_len = unreachable_peers.len();
|
||||
for _ in 0..cur_len {
|
||||
let peer_id_version = unreachable_peers.pop_back().unwrap();
|
||||
let peer_id = peer_id_version.peer_id;
|
||||
let version = peer_id_version.version;
|
||||
if session.check_saved_peer_info_update_to_date(peer_id, version) {
|
||||
// already up-to-date, skip
|
||||
if session
|
||||
.check_saved_peer_info_update_to_date(item.value().peer_id, item.value().version)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let reachable = self.route_table.peer_reachable(peer_id);
|
||||
match self.synced_route_info.peer_infos.read().get(&peer_id) {
|
||||
Some(route_info) => {
|
||||
if reachable {
|
||||
route_infos.push(route_info.clone());
|
||||
}
|
||||
// this round rpc may fail, so keep it and remove the id only when it's in dst_saved_map
|
||||
unreachable_peers.push_front(peer_id_version);
|
||||
}
|
||||
None => {
|
||||
// if not found in peer info map, forget this peer id.
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unreachable_peers.shrink_to_fit();
|
||||
route_infos.push(item.value().clone());
|
||||
}
|
||||
|
||||
if route_infos.is_empty() {
|
||||
None
|
||||
@@ -1989,7 +1860,6 @@ impl PeerRouteServiceImpl {
|
||||
let dst_supports_peer_list = self
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.get(&dst_peer_id)
|
||||
.and_then(|p| p.feature_flag)
|
||||
.map(|x| x.support_conn_list_sync)
|
||||
@@ -2018,13 +1888,11 @@ impl PeerRouteServiceImpl {
|
||||
fn clear_expired_peer(&self) {
|
||||
let now = SystemTime::now();
|
||||
let mut to_remove = Vec::new();
|
||||
for (peer_id, peer_info) in self.synced_route_info.peer_infos.read().iter() {
|
||||
if let Ok(d) = now.duration_since(peer_info.last_update.unwrap().try_into().unwrap()) {
|
||||
if d > REMOVE_DEAD_PEER_INFO_AFTER
|
||||
|| (d > REMOVE_UNREACHABLE_PEER_INFO_AFTER
|
||||
&& !self.route_table.peer_reachable(*peer_id))
|
||||
{
|
||||
to_remove.push(*peer_id);
|
||||
for item in self.synced_route_info.peer_infos.iter() {
|
||||
if let Ok(d) = now.duration_since(item.value().last_update.unwrap().try_into().unwrap())
|
||||
{
|
||||
if d > REMOVE_DEAD_PEER_INFO_AFTER {
|
||||
to_remove.push(*item.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2103,8 +1971,6 @@ impl PeerRouteServiceImpl {
|
||||
|
||||
let my_peer_id = self.my_peer_id;
|
||||
|
||||
let next_last_sync_succ_timestamp =
|
||||
self.synced_route_info.get_next_last_sync_succ_timestamp();
|
||||
let (peer_infos, conn_info, foreign_network) =
|
||||
self.build_sync_request(&session, dst_peer_id);
|
||||
if peer_infos.is_none()
|
||||
@@ -2154,11 +2020,6 @@ impl PeerRouteServiceImpl {
|
||||
.sync_route_info(ctrl, SyncRouteInfoRequest::default())
|
||||
.await;
|
||||
|
||||
tracing::debug!(
|
||||
"sync_route_info resp: {:?}, req: {:?}, session: {:?}, my_info: {:?}, next_last_sync_succ_timestamp: {:?}",
|
||||
ret, sync_route_info_req, session, self.global_ctx.network, next_last_sync_succ_timestamp
|
||||
);
|
||||
|
||||
if let Err(e) = &ret {
|
||||
tracing::error!(
|
||||
?ret,
|
||||
@@ -2205,8 +2066,6 @@ impl PeerRouteServiceImpl {
|
||||
if let Some(foreign_network) = &foreign_network {
|
||||
session.update_dst_saved_foreign_network_version(foreign_network, dst_peer_id);
|
||||
}
|
||||
|
||||
session.update_last_sync_succ_timestamp(next_last_sync_succ_timestamp);
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -2297,9 +2156,9 @@ impl OspfRouteRpc for RouteSessionManager {
|
||||
let peer_infos = request.peer_infos.map(|x| x.items);
|
||||
let conn_info = request.conn_info;
|
||||
let foreign_network = request.foreign_network_infos;
|
||||
let raw_peer_infos = if peer_infos.is_some() {
|
||||
let raw_peer_infos = if let Some(peer_infos_ref) = &peer_infos {
|
||||
let r = get_raw_peer_infos(&mut ctrl.get_raw_input().unwrap()).unwrap();
|
||||
assert_eq!(r.len(), peer_infos.as_ref().unwrap().len());
|
||||
assert_eq!(r.len(), peer_infos_ref.len());
|
||||
Some(r)
|
||||
} else {
|
||||
None
|
||||
@@ -2981,7 +2840,7 @@ mod tests {
|
||||
peers::{
|
||||
create_packet_recv_chan,
|
||||
peer_manager::{PeerManager, RouteAlgoType},
|
||||
peer_ospf_route::{PeerIdVersion, PeerRouteServiceImpl, FORCE_USE_CONN_LIST},
|
||||
peer_ospf_route::{PeerIdAndVersion, PeerRouteServiceImpl, FORCE_USE_CONN_LIST},
|
||||
route_trait::{NextHopPolicy, Route, RouteCostCalculatorInterface},
|
||||
tests::{connect_peer_manager, create_mock_peer_manager, wait_route_appear},
|
||||
},
|
||||
@@ -3064,14 +2923,8 @@ mod tests {
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||
|
||||
assert_eq!(
|
||||
2,
|
||||
r_a.service_impl.synced_route_info.peer_infos.read().len()
|
||||
);
|
||||
assert_eq!(
|
||||
2,
|
||||
r_b.service_impl.synced_route_info.peer_infos.read().len()
|
||||
);
|
||||
assert_eq!(2, r_a.service_impl.synced_route_info.peer_infos.len());
|
||||
assert_eq!(2, r_b.service_impl.synced_route_info.peer_infos.len());
|
||||
|
||||
for s in r_a.service_impl.sessions.iter() {
|
||||
assert!(s.value().task.is_running());
|
||||
@@ -3081,7 +2934,6 @@ mod tests {
|
||||
r_a.service_impl
|
||||
.synced_route_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.get(&p_a.my_peer_id())
|
||||
.unwrap()
|
||||
.version,
|
||||
@@ -3138,7 +2990,7 @@ mod tests {
|
||||
|
||||
for r in [r_a.clone(), r_b.clone(), r_c.clone()].iter() {
|
||||
wait_for_condition(
|
||||
|| async { r.service_impl.synced_route_info.peer_infos.read().len() == 3 },
|
||||
|| async { r.service_impl.synced_route_info.peer_infos.len() == 3 },
|
||||
Duration::from_secs(5),
|
||||
)
|
||||
.await;
|
||||
@@ -3243,9 +3095,7 @@ mod tests {
|
||||
// check peer infos
|
||||
let peer_info = synced_info
|
||||
.peer_infos
|
||||
.read()
|
||||
.get(&routable_peer.my_peer_id())
|
||||
.cloned()
|
||||
.unwrap();
|
||||
assert_eq!(peer_info.peer_id, routable_peer.my_peer_id());
|
||||
}
|
||||
@@ -3275,7 +3125,7 @@ mod tests {
|
||||
|
||||
for r in [r_a.clone(), r_b.clone(), r_c.clone()].iter() {
|
||||
wait_for_condition(
|
||||
|| async { r.service_impl.synced_route_info.peer_infos.read().len() == 3 },
|
||||
|| async { r.service_impl.synced_route_info.peer_infos.len() == 3 },
|
||||
Duration::from_secs(5),
|
||||
)
|
||||
.await;
|
||||
@@ -3542,10 +3392,10 @@ mod tests {
|
||||
let proxy_cidr: Ipv4Cidr = "192.168.100.0/24".parse().unwrap();
|
||||
let test_ip = proxy_cidr.first_address();
|
||||
|
||||
let mut cidr_peer_id_map: PrefixMap<Ipv4Cidr, PeerIdVersion> = PrefixMap::new();
|
||||
let mut cidr_peer_id_map: PrefixMap<Ipv4Cidr, PeerIdAndVersion> = PrefixMap::new();
|
||||
cidr_peer_id_map.insert(
|
||||
proxy_cidr,
|
||||
PeerIdVersion {
|
||||
PeerIdAndVersion {
|
||||
peer_id: p_c.my_peer_id(),
|
||||
version: 0,
|
||||
},
|
||||
|
||||
@@ -76,6 +76,8 @@ message NetworkConfig {
|
||||
repeated PortForwardConfig port_forwards = 48;
|
||||
|
||||
optional bool disable_sym_hole_punching = 49;
|
||||
|
||||
optional bool p2p_only = 51;
|
||||
}
|
||||
|
||||
message PortForwardConfig {
|
||||
@@ -112,6 +114,12 @@ message NetworkInstanceRunningInfoMap {
|
||||
map<string, NetworkInstanceRunningInfo> map = 1;
|
||||
}
|
||||
|
||||
message NetworkMeta {
|
||||
common.UUID inst_id = 1;
|
||||
string network_name = 2;
|
||||
uint32 config_permission = 3;
|
||||
}
|
||||
|
||||
message ValidateConfigRequest { NetworkConfig config = 1; }
|
||||
|
||||
message ValidateConfigResponse { string toml_config = 1; }
|
||||
@@ -119,6 +127,7 @@ message ValidateConfigResponse { string toml_config = 1; }
|
||||
message RunNetworkInstanceRequest {
|
||||
common.UUID inst_id = 1;
|
||||
NetworkConfig config = 2;
|
||||
bool overwrite = 3;
|
||||
}
|
||||
|
||||
message RunNetworkInstanceResponse { common.UUID inst_id = 1; }
|
||||
@@ -143,6 +152,14 @@ message DeleteNetworkInstanceResponse {
|
||||
repeated common.UUID remain_inst_ids = 1;
|
||||
}
|
||||
|
||||
message GetNetworkInstanceConfigRequest { common.UUID inst_id = 1; }
|
||||
|
||||
message GetNetworkInstanceConfigResponse { NetworkConfig config = 1; }
|
||||
|
||||
message ListNetworkInstanceMetaRequest { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message ListNetworkInstanceMetaResponse { repeated NetworkMeta metas = 1; }
|
||||
|
||||
service WebClientService {
|
||||
rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse) {}
|
||||
rpc RunNetworkInstance(RunNetworkInstanceRequest)
|
||||
@@ -155,4 +172,8 @@ service WebClientService {
|
||||
returns (ListNetworkInstanceResponse) {}
|
||||
rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest)
|
||||
returns (DeleteNetworkInstanceResponse) {}
|
||||
rpc GetNetworkInstanceConfig(GetNetworkInstanceConfigRequest)
|
||||
returns (GetNetworkInstanceConfigResponse) {}
|
||||
rpc ListNetworkInstanceMeta(ListNetworkInstanceMetaRequest)
|
||||
returns (ListNetworkInstanceMetaResponse) {}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ message FlagsInConfig {
|
||||
|
||||
// tld dns zone for magic dns
|
||||
string tld_dns_zone = 31;
|
||||
|
||||
bool p2p_only = 32;
|
||||
}
|
||||
|
||||
message RpcDescriptor {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
common::config::ConfigLoader,
|
||||
common::config::{ConfigFileControl, ConfigFilePermission, ConfigLoader},
|
||||
instance_manager::NetworkInstanceManager,
|
||||
launcher::ConfigSource,
|
||||
proto::{
|
||||
api::manage::*,
|
||||
api::{config::GetConfigRequest, manage::*},
|
||||
rpc_types::{self, controller::BaseController},
|
||||
},
|
||||
};
|
||||
@@ -47,11 +46,59 @@ impl WebClientService for InstanceManageRpcService {
|
||||
if let Some(inst_id) = req.inst_id {
|
||||
cfg.set_id(inst_id.into());
|
||||
}
|
||||
self.manager.run_network_instance(cfg, ConfigSource::Web)?;
|
||||
println!("instance {} started", id);
|
||||
Ok(RunNetworkInstanceResponse {
|
||||
let resp = RunNetworkInstanceResponse {
|
||||
inst_id: Some(id.into()),
|
||||
})
|
||||
};
|
||||
|
||||
let mut control = if let Some(control) = self.manager.get_instance_config_control(&id) {
|
||||
if !req.overwrite {
|
||||
return Ok(resp);
|
||||
}
|
||||
if control.is_read_only() {
|
||||
return Err(
|
||||
anyhow::anyhow!("instance {} is read-only, cannot be overwritten", id).into(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = control.path.as_ref() {
|
||||
let real_control = ConfigFileControl::from_path(path.clone()).await;
|
||||
if real_control.is_read_only() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"config file {} is read-only, cannot be overwritten",
|
||||
path.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.manager.delete_network_instance(vec![id])?;
|
||||
|
||||
control.clone()
|
||||
} else if let Some(config_dir) = self.manager.get_config_dir() {
|
||||
ConfigFileControl::new(
|
||||
Some(config_dir.join(format!("{}.toml", id))),
|
||||
ConfigFilePermission::default(),
|
||||
)
|
||||
} else {
|
||||
ConfigFileControl::new(None, ConfigFilePermission::default())
|
||||
};
|
||||
|
||||
if !control.is_read_only() {
|
||||
if let Some(config_file) = control.path.as_ref() {
|
||||
if let Err(e) = std::fs::write(config_file, cfg.dump()) {
|
||||
tracing::warn!(
|
||||
"failed to write config file {}: {}",
|
||||
config_file.display(),
|
||||
e
|
||||
);
|
||||
control.set_read_only(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.manager.run_network_instance(cfg, true, control)?;
|
||||
println!("instance {} started", id);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn retain_network_instance(
|
||||
@@ -125,12 +172,79 @@ impl WebClientService for InstanceManageRpcService {
|
||||
_: BaseController,
|
||||
req: DeleteNetworkInstanceRequest,
|
||||
) -> Result<DeleteNetworkInstanceResponse, rpc_types::error::Error> {
|
||||
let remain_inst_ids = self
|
||||
let inst_ids: HashSet<uuid::Uuid> = req.inst_ids.into_iter().map(Into::into).collect();
|
||||
let inst_ids = self
|
||||
.manager
|
||||
.delete_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?;
|
||||
.iter()
|
||||
.filter(|v| inst_ids.contains(v.key()))
|
||||
.filter(|v| v.get_config_file_control().is_deletable())
|
||||
.map(|v| *v.key())
|
||||
.collect::<Vec<_>>();
|
||||
let config_files = inst_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
self.manager
|
||||
.get_instance_config_control(id)
|
||||
.and_then(|control| control.path.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let remain_inst_ids = self.manager.delete_network_instance(inst_ids)?;
|
||||
println!("instance {:?} retained", remain_inst_ids);
|
||||
for config_file in config_files {
|
||||
if let Err(e) = std::fs::remove_file(&config_file) {
|
||||
tracing::warn!(
|
||||
"failed to remove config file {}: {}",
|
||||
config_file.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(DeleteNetworkInstanceResponse {
|
||||
remain_inst_ids: remain_inst_ids.into_iter().map(Into::into).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_network_instance_config(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: GetNetworkInstanceConfigRequest,
|
||||
) -> Result<GetNetworkInstanceConfigResponse, rpc_types::error::Error> {
|
||||
let inst_id: uuid::Uuid = req
|
||||
.inst_id
|
||||
.ok_or_else(|| anyhow::anyhow!("instance id is required"))?
|
||||
.into();
|
||||
let config = self
|
||||
.manager
|
||||
.get_instance_service(&inst_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("instance service not found"))?
|
||||
.get_config_service()
|
||||
.get_config(BaseController::default(), GetConfigRequest::default())
|
||||
.await?
|
||||
.config;
|
||||
Ok(GetNetworkInstanceConfigResponse { config })
|
||||
}
|
||||
|
||||
async fn list_network_instance_meta(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: ListNetworkInstanceMetaRequest,
|
||||
) -> Result<ListNetworkInstanceMetaResponse, rpc_types::error::Error> {
|
||||
let mut metas = Vec::with_capacity(req.inst_ids.len());
|
||||
for inst_id in req.inst_ids {
|
||||
let inst_id: uuid::Uuid = (inst_id).into();
|
||||
let Some(control) = self.manager.get_instance_config_control(&inst_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(name) = self.manager.get_network_instance_name(&inst_id) else {
|
||||
continue;
|
||||
};
|
||||
let meta = NetworkMeta {
|
||||
inst_id: Some(inst_id.into()),
|
||||
network_name: name,
|
||||
config_permission: control.permission.into(),
|
||||
};
|
||||
metas.push(meta);
|
||||
}
|
||||
Ok(ListNetworkInstanceMetaResponse { metas })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,18 +79,23 @@ fn get_instance_service(
|
||||
let id = if let Some(api::instance::instance_identifier::Selector::Id(id)) = selector {
|
||||
(*id).into()
|
||||
} else {
|
||||
let ids = instance_manager.filter_network_instance(|_, i| {
|
||||
if let Some(api::instance::instance_identifier::Selector::InstanceSelector(selector)) =
|
||||
selector
|
||||
{
|
||||
if let Some(name) = selector.name.as_ref() {
|
||||
if i.get_inst_name() != *name {
|
||||
return false;
|
||||
let ids = instance_manager
|
||||
.iter()
|
||||
.filter(|v| {
|
||||
if let Some(api::instance::instance_identifier::Selector::InstanceSelector(
|
||||
selector,
|
||||
)) = selector
|
||||
{
|
||||
if let Some(name) = selector.name.as_ref() {
|
||||
if v.get_inst_name() != *name {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
true
|
||||
})
|
||||
.map(|v| *v.key())
|
||||
.collect::<Vec<_>>();
|
||||
match ids.len() {
|
||||
0 => return Err(anyhow::anyhow!("No instance matches the selector")),
|
||||
1 => ids[0],
|
||||
|
||||
@@ -40,6 +40,7 @@ where
|
||||
&self,
|
||||
identify: T,
|
||||
config: NetworkConfig,
|
||||
save: bool,
|
||||
) -> Result<(), RemoteClientError<E>> {
|
||||
let client = self
|
||||
.get_rpc_client(identify.clone())
|
||||
@@ -50,18 +51,21 @@ where
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config: Some(config.clone()),
|
||||
overwrite: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.get_storage()
|
||||
.insert_or_update_user_network_config(
|
||||
identify,
|
||||
resp.inst_id.unwrap_or_default().into(),
|
||||
config,
|
||||
)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
if save {
|
||||
self.get_storage()
|
||||
.insert_or_update_user_network_config(
|
||||
identify,
|
||||
resp.inst_id.unwrap_or_default().into(),
|
||||
config,
|
||||
)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -156,13 +160,17 @@ where
|
||||
let client = self
|
||||
.get_rpc_client(identify.clone())
|
||||
.ok_or(RemoteClientError::ClientNotFound)?;
|
||||
|
||||
let cfg = self
|
||||
.get_storage()
|
||||
.update_network_config_state(identify, inst_id, disabled)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
.handle_get_network_config(identify.clone(), inst_id)
|
||||
.await?;
|
||||
|
||||
if disabled {
|
||||
self.get_storage()
|
||||
.insert_or_update_user_network_config(identify.clone(), inst_id, cfg.clone())
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
|
||||
client
|
||||
.delete_network_instance(
|
||||
BaseController::default(),
|
||||
@@ -177,15 +185,18 @@ where
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: Some(inst_id.into()),
|
||||
config: Some(
|
||||
cfg.get_network_config()
|
||||
.map_err(RemoteClientError::PersistentError)?,
|
||||
),
|
||||
config: Some(cfg),
|
||||
overwrite: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.get_storage()
|
||||
.update_network_config_state(identify, inst_id, disabled)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -196,14 +207,38 @@ where
|
||||
) -> Result<GetNetworkMetasResponse, RemoteClientError<E>> {
|
||||
let mut metas = std::collections::HashMap::new();
|
||||
|
||||
if let Some(client) = self.get_rpc_client(identify.clone()) {
|
||||
if let Ok(resp) = client
|
||||
.list_network_instance_meta(
|
||||
BaseController::default(),
|
||||
ListNetworkInstanceMetaRequest {
|
||||
inst_ids: inst_ids.iter().cloned().map(|id| id.into()).collect(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
for meta in resp.metas {
|
||||
if let Some(inst_id) = meta.inst_id.as_ref() {
|
||||
let inst_id: uuid::Uuid = (*inst_id).into();
|
||||
metas.insert(inst_id, meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for instance_id in inst_ids {
|
||||
if metas.contains_key(&instance_id) {
|
||||
continue;
|
||||
}
|
||||
let config = self
|
||||
.handle_get_network_config(identify.clone(), instance_id)
|
||||
.await?;
|
||||
metas.insert(
|
||||
instance_id,
|
||||
NetworkMeta {
|
||||
instance_name: config.network_name.unwrap_or_default(),
|
||||
inst_id: Some(instance_id.into()),
|
||||
network_name: config.network_name.unwrap_or_default(),
|
||||
config_permission: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -233,6 +268,22 @@ where
|
||||
identify: T,
|
||||
inst_id: uuid::Uuid,
|
||||
) -> Result<NetworkConfig, RemoteClientError<E>> {
|
||||
if let Some(client) = self.get_rpc_client(identify.clone()) {
|
||||
if let Ok(resp) = client
|
||||
.get_network_instance_config(
|
||||
BaseController::default(),
|
||||
GetNetworkInstanceConfigRequest {
|
||||
inst_id: Some(inst_id.into()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Some(config) = resp.config {
|
||||
return Ok(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inst_id = inst_id.to_string();
|
||||
|
||||
let db_row = self
|
||||
@@ -277,11 +328,6 @@ pub struct ListNetworkInstanceIdsJsonResp {
|
||||
disabled_inst_ids: Vec<crate::proto::common::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct NetworkMeta {
|
||||
instance_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetNetworkMetasResponse {
|
||||
metas: std::collections::HashMap<uuid::Uuid, NetworkMeta>,
|
||||
@@ -312,7 +358,7 @@ where
|
||||
identify: T,
|
||||
network_inst_id: Uuid,
|
||||
disabled: bool,
|
||||
) -> Result<C, E>;
|
||||
) -> Result<(), E>;
|
||||
|
||||
async fn list_network_configs(&self, identify: T, props: ListNetworkProps)
|
||||
-> Result<Vec<C>, E>;
|
||||
|
||||
@@ -38,7 +38,8 @@ async fn test_route_peer_info_ipv6() {
|
||||
global_ctx.set_ipv6(Some(ipv6_cidr));
|
||||
|
||||
// Create RoutePeerInfo with IPv6 support
|
||||
let updated_info = RoutePeerInfo::new_updated_self(123, 456, &global_ctx);
|
||||
let peer_info = RoutePeerInfo::new();
|
||||
let updated_info = peer_info.update_self(123, 456, &global_ctx);
|
||||
|
||||
// Verify IPv6 address is included
|
||||
assert!(updated_info.ipv6_addr.is_some());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use core::panic;
|
||||
use std::{
|
||||
future::Future,
|
||||
sync::{atomic::AtomicU32, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -1950,6 +1951,107 @@ pub async fn acl_rule_test_subnet_proxy(
|
||||
drop_insts(insts).await;
|
||||
}
|
||||
|
||||
async fn assert_panics_ext<F, Fut>(f: F, expect_panic: bool)
|
||||
where
|
||||
F: FnOnce() -> Fut + Send + 'static,
|
||||
Fut: Future + Send + 'static,
|
||||
{
|
||||
// Run the async function in a separate task so panics surface as JoinError
|
||||
let res = tokio::spawn(async move {
|
||||
f().await;
|
||||
})
|
||||
.await;
|
||||
|
||||
if expect_panic {
|
||||
assert!(
|
||||
res.is_err() && res.as_ref().unwrap_err().is_panic(),
|
||||
"Expected function to panic, but it didn't",
|
||||
);
|
||||
} else {
|
||||
assert!(res.is_ok(), "Expected function not to panic, but it did");
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest::rstest]
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
pub async fn p2p_only_test(
|
||||
#[values(true, false)] has_p2p_conn: bool,
|
||||
#[values(true, false)] enable_kcp_proxy: bool,
|
||||
#[values(true, false)] enable_quic_proxy: bool,
|
||||
) {
|
||||
use crate::peers::tests::wait_route_appear_with_cost;
|
||||
|
||||
let insts = init_three_node_ex(
|
||||
"udp",
|
||||
|cfg| {
|
||||
if cfg.get_inst_name() == "inst1" {
|
||||
let mut flags = cfg.get_flags();
|
||||
flags.enable_kcp_proxy = enable_kcp_proxy;
|
||||
flags.enable_quic_proxy = enable_quic_proxy;
|
||||
flags.disable_p2p = true;
|
||||
flags.p2p_only = true;
|
||||
cfg.set_flags(flags);
|
||||
} else if cfg.get_inst_name() == "inst3" {
|
||||
// 添加子网代理配置
|
||||
cfg.add_proxy_cidr("10.1.2.0/24".parse().unwrap(), None)
|
||||
.unwrap();
|
||||
}
|
||||
cfg
|
||||
},
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
if has_p2p_conn {
|
||||
insts[2]
|
||||
.get_conn_manager()
|
||||
.add_connector(RingTunnelConnector::new(
|
||||
format!("ring://{}", insts[0].id()).parse().unwrap(),
|
||||
));
|
||||
wait_route_appear_with_cost(
|
||||
insts[2].get_peer_manager(),
|
||||
insts[0].get_peer_manager().my_peer_id(),
|
||||
Some(1),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let target_ip = "10.1.2.4";
|
||||
|
||||
for target_ip in ["10.144.144.3", target_ip] {
|
||||
assert_panics_ext(
|
||||
|| async {
|
||||
subnet_proxy_test_icmp(target_ip).await;
|
||||
},
|
||||
!has_p2p_conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
let listen_ip = if target_ip == "10.144.144.3" {
|
||||
"0.0.0.0"
|
||||
} else {
|
||||
"10.1.2.4"
|
||||
};
|
||||
assert_panics_ext(
|
||||
|| async {
|
||||
subnet_proxy_test_tcp(listen_ip, target_ip).await;
|
||||
},
|
||||
!has_p2p_conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_panics_ext(
|
||||
|| async {
|
||||
subnet_proxy_test_udp(listen_ip, target_ip).await;
|
||||
},
|
||||
!has_p2p_conn,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest::rstest]
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
|
||||
@@ -35,4 +35,8 @@ impl Controller {
|
||||
pub fn get_rpc_service(&self) -> InstanceManageRpcService {
|
||||
InstanceManageRpcService::new(self.manager.clone())
|
||||
}
|
||||
|
||||
pub(super) fn notify_manager_stopping(&self) {
|
||||
self.manager.notify_stop_check();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
common::scoped_task::ScopedTask, instance_manager::NetworkInstanceManager,
|
||||
tunnel::TunnelConnector,
|
||||
common::{
|
||||
config::TomlConfigLoader, global_ctx::GlobalCtx, scoped_task::ScopedTask,
|
||||
set_default_machine_id, stun::MockStunInfoCollector,
|
||||
},
|
||||
connector::create_connector_by_url,
|
||||
instance_manager::{NetworkInstanceManager, WebClientGuard},
|
||||
proto::common::NatType,
|
||||
tunnel::{IpVersion, TunnelConnector},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use url::Url;
|
||||
|
||||
pub mod controller;
|
||||
pub mod session;
|
||||
@@ -11,6 +19,7 @@ pub mod session;
|
||||
pub struct WebClient {
|
||||
controller: Arc<controller::Controller>,
|
||||
tasks: ScopedTask<()>,
|
||||
manager_guard: WebClientGuard,
|
||||
}
|
||||
|
||||
impl WebClient {
|
||||
@@ -20,6 +29,7 @@ impl WebClient {
|
||||
hostname: H,
|
||||
manager: Arc<NetworkInstanceManager>,
|
||||
) -> Self {
|
||||
let manager_guard = manager.register_web_client();
|
||||
let controller = Arc::new(controller::Controller::new(
|
||||
token.to_string(),
|
||||
hostname.to_string(),
|
||||
@@ -31,7 +41,11 @@ impl WebClient {
|
||||
Self::routine(controller_clone, Box::new(connector)).await;
|
||||
}));
|
||||
|
||||
WebClient { controller, tasks }
|
||||
WebClient {
|
||||
controller,
|
||||
tasks,
|
||||
manager_guard,
|
||||
}
|
||||
}
|
||||
|
||||
async fn routine(
|
||||
@@ -58,3 +72,90 @@ impl WebClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_web_client(
|
||||
config_server_url_s: &str,
|
||||
machine_id: Option<String>,
|
||||
hostname: Option<String>,
|
||||
manager: Arc<NetworkInstanceManager>,
|
||||
) -> Result<WebClient> {
|
||||
set_default_machine_id(machine_id);
|
||||
let config_server_url = match Url::parse(config_server_url_s) {
|
||||
Ok(u) => u,
|
||||
Err(_) => format!(
|
||||
"udp://config-server.easytier.cn:22020/{}",
|
||||
config_server_url_s
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut c_url = config_server_url.clone();
|
||||
c_url.set_path("");
|
||||
let token = config_server_url
|
||||
.path_segments()
|
||||
.and_then(|mut x| x.next())
|
||||
.map(|x| percent_encoding::percent_decode_str(x).decode_utf8())
|
||||
.transpose()
|
||||
.with_context(|| "failed to decode config server token")?
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
if token.is_empty() {
|
||||
return Err(anyhow::anyhow!("empty token"));
|
||||
}
|
||||
|
||||
let config = TomlConfigLoader::default();
|
||||
let global_ctx = Arc::new(GlobalCtx::new(config));
|
||||
global_ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||
udp_nat_type: NatType::Unknown,
|
||||
}));
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.bind_device = false;
|
||||
global_ctx.set_flags(flags);
|
||||
let hostname = match hostname {
|
||||
None => gethostname::gethostname().to_string_lossy().to_string(),
|
||||
Some(hostname) => hostname,
|
||||
};
|
||||
Ok(WebClient::new(
|
||||
create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?,
|
||||
token.to_string(),
|
||||
hostname,
|
||||
manager.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use crate::instance_manager::NetworkInstanceManager;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_manager_wait() {
|
||||
let manager = Arc::new(NetworkInstanceManager::new());
|
||||
let client = super::run_web_client(
|
||||
format!("ring://{}/test", uuid::Uuid::new_v4()).as_str(),
|
||||
None,
|
||||
None,
|
||||
manager.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let sleep_finish = Arc::new(AtomicBool::new(false));
|
||||
let sleep_finish_clone = sleep_finish.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
println!("Dropping client...");
|
||||
sleep_finish_clone.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
drop(client);
|
||||
println!("Client dropped.");
|
||||
});
|
||||
|
||||
println!("Waiting for manager...");
|
||||
manager.wait().await;
|
||||
assert!(sleep_finish.load(std::sync::atomic::Ordering::Relaxed));
|
||||
println!("Manager stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +269,7 @@ no_tun = false
|
||||
use_smoltcp = false
|
||||
foreign_network_whitelist = "*"
|
||||
disable_p2p = false
|
||||
p2p_only = false
|
||||
relay_all_peer_rpc = false
|
||||
disable_udp_hole_punching = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user