mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-16 02:45:41 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c23b544c34 | |||
| 9d76b86f49 | |||
| bb0ccca3e5 | |||
| 306817ae9a | |||
| d2ec60e108 | |||
| e016aeddeb | |||
| a4419a31fd | |||
| 34e4e907a9 | |||
| 2f4a097787 | |||
| f3de00be37 | |||
| 4cf61f0d4a | |||
| 4e5915f98e | |||
| 870eca9e9f |
@@ -21,7 +21,7 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: 'Version for this release'
|
description: 'Version for this release'
|
||||||
type: string
|
type: string
|
||||||
default: 'v2.1.0'
|
default: 'v2.1.2'
|
||||||
required: true
|
required: true
|
||||||
make_latest:
|
make_latest:
|
||||||
description: 'Mark this release as latest'
|
description: 'Mark this release as latest'
|
||||||
|
|||||||
Generated
+15
-14
@@ -1830,7 +1830,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "easytier"
|
name = "easytier"
|
||||||
version = "2.1.0"
|
version = "2.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1926,7 +1926,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "2.1.0"
|
version = "2.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -1979,6 +1979,7 @@ dependencies = [
|
|||||||
"axum-login",
|
"axum-login",
|
||||||
"axum-messages",
|
"axum-messages",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"easytier",
|
"easytier",
|
||||||
@@ -1987,12 +1988,14 @@ dependencies = [
|
|||||||
"password-auth",
|
"password-auth",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
|
"rust-i18n",
|
||||||
"rusttype",
|
"rusttype",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"sea-orm-migration",
|
"sea-orm-migration",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"sys-locale",
|
||||||
"thiserror 1.0.63",
|
"thiserror 1.0.63",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -7312,9 +7315,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.1.0"
|
version = "2.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c24f1ab82d336e09f5f1094a4d9227c99ac26cce263bfdf8136897cc6db6f1d0"
|
checksum = "d3889b392db6d32a105d3757230ea0220090b8f94c90d3e60b6c5eb91178ab1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -7350,7 +7353,7 @@ dependencies = [
|
|||||||
"tauri-runtime",
|
"tauri-runtime",
|
||||||
"tauri-runtime-wry",
|
"tauri-runtime-wry",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.2",
|
"thiserror 1.0.63",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"url",
|
"url",
|
||||||
@@ -7562,9 +7565,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.2.0"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cce18d43f80d4aba3aa8a0c953bbe835f3d0f2370aca75e8dbb14bd4bab27958"
|
checksum = "a1ef7363e7229ac8d04e8a5d405670dbd43dde8fc4bc3bc56105c35452d03784"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
@@ -7574,16 +7577,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.2",
|
"thiserror 1.0.63",
|
||||||
"url",
|
"url",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "2.2.0"
|
version = "2.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f442a38863e10129ffe2cec7bd09c2dcf8a098a3a27801a476a304d5bb991d2"
|
checksum = "62fa2068e8498ad007b54d5773d03d57c3ff6dd96f8c8ce58beff44d0d5e0d30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
@@ -9259,13 +9262,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wry"
|
name = "wry"
|
||||||
version = "0.47.0"
|
version = "0.46.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "553ca1ce149982123962fac2506aa75b8b76288779a77e72b12fa2fc34938647"
|
checksum = "2f8c948dc5f7c23bd93ba03b85b7f679852589bb78e150424d993171e4ef7b73"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"block2",
|
"block2",
|
||||||
"cookie",
|
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dpi",
|
"dpi",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -9290,7 +9292,6 @@ dependencies = [
|
|||||||
"soup3",
|
"soup3",
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
"thiserror 1.0.63",
|
"thiserror 1.0.63",
|
||||||
"url",
|
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webkit2gtk-sys",
|
"webkit2gtk-sys",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ EasyTier is a simple, safe and decentralized VPN networking solution implemented
|
|||||||
- **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected.
|
- **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected.
|
||||||
- **IPv6 Support**: Supports networking using IPv6.
|
- **IPv6 Support**: Supports networking using IPv6.
|
||||||
- **Multiple Protocol Types**: Supports communication between nodes using protocols such as WebSocket and QUIC.
|
- **Multiple Protocol Types**: Supports communication between nodes using protocols such as WebSocket and QUIC.
|
||||||
|
- **Web Management Interface**: Provides a [web-based management](https://easytier.cn/web) interface for easy configuration and monitoring.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ EasyTier is a simple, safe and decentralized VPN networking solution implemented
|
|||||||
|
|
||||||
4. **Install by Docker Compose**
|
4. **Install by Docker Compose**
|
||||||
|
|
||||||
Please visit the [EasyTier Official Website](https://www.easytier.top/en/) to view the full documentation.
|
Please visit the [EasyTier Official Website](https://www.easytier.cn/en/) to view the full documentation.
|
||||||
|
|
||||||
5. **Install by script (For Linux Only)**
|
5. **Install by script (For Linux Only)**
|
||||||
|
|
||||||
@@ -200,20 +201,20 @@ Subnet proxy information will automatically sync to each node in the virtual net
|
|||||||
|
|
||||||
### Networking without Public IP
|
### Networking without Public IP
|
||||||
|
|
||||||
EasyTier supports networking using shared public nodes. The currently deployed shared public node is ``tcp://public.easytier.top:11010``.
|
EasyTier supports networking using shared public nodes. The currently deployed shared public node is ``tcp://public.easytier.cn:11010``.
|
||||||
|
|
||||||
When using shared nodes, each node entering the network needs to provide the same ``--network-name`` and ``--network-secret`` parameters as the unique identifier of the network.
|
When using shared nodes, each node entering the network needs to provide the same ``--network-name`` and ``--network-secret`` parameters as the unique identifier of the network.
|
||||||
|
|
||||||
Taking two nodes as an example, Node A executes:
|
Taking two nodes as an example, Node A executes:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010
|
sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010
|
||||||
```
|
```
|
||||||
|
|
||||||
Node B executes
|
Node B executes
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010
|
sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010
|
||||||
```
|
```
|
||||||
|
|
||||||
After the command is successfully executed, Node A can access Node B through the virtual IP 10.144.144.2.
|
After the command is successfully executed, Node A can access Node B through the virtual IP 10.144.144.2.
|
||||||
@@ -286,7 +287,7 @@ Run you own public server cluster is exactly same as running an virtual network,
|
|||||||
You can also join the official public server cluster with following command:
|
You can also join the official public server cluster with following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.top:11010
|
sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.cn:11010
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -296,10 +297,8 @@ You can use ``easytier-core --help`` to view all configuration items
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [ ] Improve documentation and user guides.
|
- [ ] Support features such TCP hole punching, KCP, FEC etc.
|
||||||
- [ ] Support features such as encryption, TCP hole punching, etc.
|
|
||||||
- [ ] Support iOS.
|
- [ ] Support iOS.
|
||||||
- [ ] Support Web configuration management.
|
|
||||||
|
|
||||||
## Community and Contribution
|
## Community and Contribution
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
[简体中文](/README_CN.md) | [English](/README.md)
|
[简体中文](/README_CN.md) | [English](/README.md)
|
||||||
|
|
||||||
**请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。**
|
**请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。**
|
||||||
|
|
||||||
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
- **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。
|
- **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。
|
||||||
- **IPV6 支持**:支持利用 IPV6 组网。
|
- **IPV6 支持**:支持利用 IPV6 组网。
|
||||||
- **多协议类型**: 支持使用 WebSocket、QUIC 等协议进行节点间通信。
|
- **多协议类型**: 支持使用 WebSocket、QUIC 等协议进行节点间通信。
|
||||||
|
- **Web 管理界面**:支持通过 [Web 界面](https://easytier.cn)管理节点。
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
|
|
||||||
4. **通过Docker Compose安装**
|
4. **通过Docker Compose安装**
|
||||||
|
|
||||||
请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。
|
请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。
|
||||||
|
|
||||||
5. **使用一键脚本安装 (仅适用于 Linux)**
|
5. **使用一键脚本安装 (仅适用于 Linux)**
|
||||||
|
|
||||||
@@ -199,20 +200,20 @@ sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24
|
|||||||
|
|
||||||
### 无公网IP组网
|
### 无公网IP组网
|
||||||
|
|
||||||
EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 ``tcp://public.easytier.top:11010``。
|
EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 ``tcp://public.easytier.cn:11010``。
|
||||||
|
|
||||||
使用共享节点时,需要每个入网节点提供相同的 ``--network-name`` 和 ``--network-secret`` 参数,作为网络的唯一标识。
|
使用共享节点时,需要每个入网节点提供相同的 ``--network-name`` 和 ``--network-secret`` 参数,作为网络的唯一标识。
|
||||||
|
|
||||||
以双节点为例,节点 A 执行:
|
以双节点为例,节点 A 执行:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010
|
sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010
|
||||||
```
|
```
|
||||||
|
|
||||||
节点 B 执行
|
节点 B 执行
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010
|
sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010
|
||||||
```
|
```
|
||||||
|
|
||||||
命令执行成功后,节点 A 即可通过虚拟 IP 10.144.144.2 访问节点 B。
|
命令执行成功后,节点 A 即可通过虚拟 IP 10.144.144.2 访问节点 B。
|
||||||
@@ -289,7 +290,7 @@ connected_clients:
|
|||||||
也可以使用以下命令加入官方公共服务器集群,后续将实现公共服务器集群的节点间负载均衡:
|
也可以使用以下命令加入官方公共服务器集群,后续将实现公共服务器集群的节点间负载均衡:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.top:11010
|
sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.cn:11010
|
||||||
```
|
```
|
||||||
|
|
||||||
### 其他配置
|
### 其他配置
|
||||||
@@ -299,9 +300,8 @@ sudo easytier-core --network-name easytier --network-secret easytier -p tcp://pu
|
|||||||
## 路线图
|
## 路线图
|
||||||
|
|
||||||
- [ ] 完善文档和用户指南。
|
- [ ] 完善文档和用户指南。
|
||||||
- [ ] 支持 TCP 打洞等特性。
|
- [ ] 支持 TCP 打洞、KCP、FEC 等特性。
|
||||||
- [ ] 支持 iOS。
|
- [ ] 支持 iOS。
|
||||||
- [ ] 支持 Web 配置管理。
|
|
||||||
|
|
||||||
## 社区和贡献
|
## 社区和贡献
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "easytier-gui",
|
"name": "easytier-gui",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.1.0",
|
"version": "2.1.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -35,14 +35,16 @@
|
|||||||
"@primevue/auto-import-resolver": "^4.1.0",
|
"@primevue/auto-import-resolver": "^4.1.0",
|
||||||
"@tauri-apps/api": "2.1.0",
|
"@tauri-apps/api": "2.1.0",
|
||||||
"@tauri-apps/cli": "2.1.0",
|
"@tauri-apps/cli": "2.1.0",
|
||||||
|
"@types/default-gateway": "^7.2.2",
|
||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.4",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue-macros/volar": "0.30.5",
|
"@vue-macros/volar": "0.30.5",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"cidr-tools": "^11.0.2",
|
||||||
|
"default-gateway": "^7.2.2",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-format": "^0.1.2",
|
"eslint-plugin-format": "^0.1.2",
|
||||||
"internal-ip": "^8.0.0",
|
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
@@ -58,4 +60,4 @@
|
|||||||
"vue-i18n": "^10.0.0",
|
"vue-i18n": "^10.0.0",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "2.1.0"
|
version = "2.1.2"
|
||||||
description = "EasyTier GUI"
|
description = "EasyTier GUI"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -15,7 +15,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.1", features = [
|
# wry 0.47 may crash on android, see https://github.com/EasyTier/EasyTier/issues/527
|
||||||
|
tauri = { version = "=2.0.6", features = [
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"image-png",
|
"image-png",
|
||||||
"image-ico",
|
"image-ico",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"vpnservice:allow-prepare-vpn",
|
"vpnservice:allow-prepare-vpn",
|
||||||
"vpnservice:allow-start-vpn",
|
"vpnservice:allow-start-vpn",
|
||||||
"vpnservice:allow-stop-vpn",
|
"vpnservice:allow-stop-vpn",
|
||||||
"vpnservice:allow-register-listener",
|
"vpnservice:allow-registerListener",
|
||||||
"os:default",
|
"os:default",
|
||||||
"os:allow-os-type",
|
"os:allow-os-type",
|
||||||
"os:allow-arch",
|
"os:allow-arch",
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ pub fn run() {
|
|||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
utils::setup_panic_handler();
|
utils::setup_panic_handler();
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default();
|
let mut builder = tauri::Builder::default();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"createUpdaterArtifacts": false
|
"createUpdaterArtifacts": false
|
||||||
},
|
},
|
||||||
"productName": "easytier-gui",
|
"productName": "easytier-gui",
|
||||||
"version": "2.1.0",
|
"version": "2.1.2",
|
||||||
"identifier": "com.kkrainbow.easytier",
|
"identifier": "com.kkrainbow.easytier",
|
||||||
"plugins": {},
|
"plugins": {},
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ async function doStartVpn(ipv4Addr: string, cidr: number, routes: string[]) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('start vpn')
|
console.log('start vpn service', ipv4Addr, cidr, routes)
|
||||||
const start_ret = await start_vpn({
|
const start_ret = await start_vpn({
|
||||||
ipv4Addr: `${ipv4Addr}`,
|
ipv4Addr: `${ipv4Addr}/${cidr}`,
|
||||||
routes,
|
routes,
|
||||||
disallowedApplications: ['com.kkrainbow.easytier'],
|
disallowedApplications: ['com.kkrainbow.easytier'],
|
||||||
mtu: 1300,
|
mtu: 1300,
|
||||||
@@ -113,6 +113,7 @@ function getRoutesForVpn(routes: Route[]): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onNetworkInstanceChange() {
|
async function onNetworkInstanceChange() {
|
||||||
|
console.error('vpn service watch network instance change ids', JSON.stringify(networkStore.networkInstanceIds))
|
||||||
const insts = networkStore.networkInstanceIds
|
const insts = networkStore.networkInstanceIds
|
||||||
if (!insts) {
|
if (!insts) {
|
||||||
await doStopVpn()
|
await doStopVpn()
|
||||||
@@ -142,7 +143,7 @@ async function onNetworkInstanceChange() {
|
|||||||
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
||||||
|
|
||||||
if (ipChanged || routesChanged) {
|
if (ipChanged || routesChanged) {
|
||||||
console.log('virtual ip changed', JSON.stringify(curVpnStatus), virtual_ip)
|
console.info('vpn service virtual ip changed', JSON.stringify(curVpnStatus), virtual_ip)
|
||||||
try {
|
try {
|
||||||
await doStopVpn()
|
await doStopVpn()
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ async function onNetworkInstanceChange() {
|
|||||||
await doStartVpn(virtual_ip, 24, routes)
|
await doStartVpn(virtual_ip, 24, routes)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('start vpn failed, clear all network insts.', e)
|
console.error('start vpn service failed, clear all network insts.', e)
|
||||||
networkStore.clearNetworkInstances()
|
networkStore.clearNetworkInstances()
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
await retainNetworkInstance(networkStore.networkInstanceIds)
|
||||||
}
|
}
|
||||||
@@ -175,6 +176,7 @@ async function watchNetworkInstance() {
|
|||||||
}
|
}
|
||||||
subscribe_running = false
|
subscribe_running = false
|
||||||
})
|
})
|
||||||
|
console.error('vpn service watch network instance')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initMobileVpnService() {
|
export async function initMobileVpnService() {
|
||||||
|
|||||||
@@ -250,7 +250,12 @@ onBeforeMount(async () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (type() === 'android') {
|
if (type() === 'android') {
|
||||||
await initMobileVpnService()
|
try {
|
||||||
|
await initMobileVpnService()
|
||||||
|
console.error("easytier init vpn service done")
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error("easytier init vpn service failed", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { networkInterfaces } from 'node:os'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
|
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
|
||||||
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
|
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
|
||||||
import Vue from '@vitejs/plugin-vue'
|
import Vue from '@vitejs/plugin-vue'
|
||||||
import { internalIpV4Sync } from 'internal-ip'
|
import { containsCidr, parseCidr } from 'cidr-tools'
|
||||||
|
import { gateway4sync } from 'default-gateway'
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
import VueMacros from 'unplugin-vue-macros/vite'
|
import VueMacros from 'unplugin-vue-macros/vite'
|
||||||
@@ -13,6 +15,20 @@ import { defineConfig } from 'vite'
|
|||||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||||
import Layouts from 'vite-plugin-vue-layouts'
|
import Layouts from 'vite-plugin-vue-layouts'
|
||||||
|
|
||||||
|
function findIp(gateway: string) {
|
||||||
|
// Look for the matching interface in all local interfaces
|
||||||
|
console.log('gateway', gateway)
|
||||||
|
for (const addresses of Object.values(networkInterfaces())) {
|
||||||
|
if (!addresses)
|
||||||
|
continue
|
||||||
|
for (const { cidr } of addresses) {
|
||||||
|
if (cidr && containsCidr(cidr, gateway)) {
|
||||||
|
return parseCidr(cidr).ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const host = process.env.TAURI_DEV_HOST
|
const host = process.env.TAURI_DEV_HOST
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
@@ -99,10 +115,10 @@ export default defineConfig(async () => ({
|
|||||||
},
|
},
|
||||||
hmr: host
|
hmr: host
|
||||||
? {
|
? {
|
||||||
protocol: 'ws',
|
protocol: 'ws',
|
||||||
host: internalIpV4Sync(),
|
host: findIp(gateway4sync().gateway),
|
||||||
port: 1430,
|
port: 1430,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "easytier-web"
|
name = "easytier-web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
easytier = { path = "../easytier" }
|
easytier = { path = "../easytier" }
|
||||||
@@ -36,6 +37,8 @@ rusttype = "0.9.3"
|
|||||||
imageproc = "0.23.0"
|
imageproc = "0.23.0"
|
||||||
|
|
||||||
|
|
||||||
|
rust-i18n = "3"
|
||||||
|
sys-locale = "0.3"
|
||||||
clap = { version = "4.4.8", features = [
|
clap = { version = "4.4.8", features = [
|
||||||
"string",
|
"string",
|
||||||
"unicode",
|
"unicode",
|
||||||
@@ -50,3 +53,5 @@ uuid = { version = "1.5.0", features = [
|
|||||||
"macro-diagnostics",
|
"macro-diagnostics",
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
chrono = { version = "0.4.37", features = ["serde"] }
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ function showEventLogs() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="frontend-lib">
|
<div class="frontend-lib">
|
||||||
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto">
|
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto max-w-full">
|
||||||
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
||||||
<pre>{{ dialogContent }}</pre>
|
<pre>{{ dialogContent }}</pre>
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||||
import { Md5 } from 'ts-md5'
|
import { Md5 } from 'ts-md5'
|
||||||
|
import { UUID } from './utils';
|
||||||
|
|
||||||
export interface ValidateConfigResponse {
|
export interface ValidateConfigResponse {
|
||||||
toml_config: string;
|
toml_config: string;
|
||||||
@@ -31,6 +32,11 @@ export interface Summary {
|
|||||||
device_count: number;
|
device_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ListNetworkInstanceIdResponse {
|
||||||
|
running_inst_ids: Array<UUID>,
|
||||||
|
disabled_inst_ids: Array<UUID>,
|
||||||
|
}
|
||||||
|
|
||||||
export class ApiClient {
|
export class ApiClient {
|
||||||
private client: AxiosInstance;
|
private client: AxiosInstance;
|
||||||
private authFailedCb: Function | undefined;
|
private authFailedCb: Function | undefined;
|
||||||
@@ -141,6 +147,17 @@ export class ApiClient {
|
|||||||
return response.machines;
|
return response.machines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async list_deivce_instance_ids(machine_id: string): Promise<ListNetworkInstanceIdResponse> {
|
||||||
|
const response = await this.client.get<any, ListNetworkInstanceIdResponse>('/machines/' + machine_id + '/networks');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update_device_instance_state(machine_id: string, inst_id: string, disabled: boolean): Promise<undefined> {
|
||||||
|
await this.client.put<string>('/machines/' + machine_id + '/networks/' + inst_id, {
|
||||||
|
disabled: disabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async get_network_info(machine_id: string, inst_id: string): Promise<any> {
|
public async get_network_info(machine_id: string, inst_id: string): Promise<any> {
|
||||||
const response = await this.client.get<any, Record<string, any>>('/machines/' + machine_id + '/networks/info/' + inst_id);
|
const response = await this.client.get<any, Record<string, any>>('/machines/' + machine_id + '/networks/info/' + inst_id);
|
||||||
return response.info.map;
|
return response.info.map;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast } from 'primevue';
|
import { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast } from 'primevue';
|
||||||
import { NetworkTypes, Status, Utils, Api, } from 'easytier-frontend-lib';
|
import { NetworkTypes, Status, Utils, Api, } from 'easytier-frontend-lib';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { watch, computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -33,9 +33,16 @@ const isEditing = ref(false);
|
|||||||
const showCreateNetworkDialog = ref(false);
|
const showCreateNetworkDialog = ref(false);
|
||||||
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
||||||
|
|
||||||
|
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
|
||||||
|
|
||||||
const instanceIdList = computed(() => {
|
const instanceIdList = computed(() => {
|
||||||
let insts = deviceInfo.value?.running_network_instances || [];
|
let insts = new Set(deviceInfo.value?.running_network_instances || []);
|
||||||
let options = insts.map((instance: string) => {
|
let t = listInstanceIdResponse.value;
|
||||||
|
if (t) {
|
||||||
|
t.running_inst_ids.forEach((u) => insts.add(Utils.UuidToStr(u)));
|
||||||
|
t.disabled_inst_ids.forEach((u) => insts.add(Utils.UuidToStr(u)));
|
||||||
|
}
|
||||||
|
let options = Array.from(insts).map((instance: string) => {
|
||||||
return { uuid: instance };
|
return { uuid: instance };
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
@@ -51,6 +58,53 @@ const selectedInstanceId = computed({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const needShowNetworkStatus = computed(() => {
|
||||||
|
if (!selectedInstanceId.value) {
|
||||||
|
// nothing selected
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (networkIsDisabled.value) {
|
||||||
|
// network is disabled
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
|
||||||
|
const networkIsDisabled = computed(() => {
|
||||||
|
if (!selectedInstanceId.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return listInstanceIdResponse.value?.disabled_inst_ids.map(Utils.UuidToStr).includes(selectedInstanceId.value?.uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedInstanceId, async (newVal, oldVal) => {
|
||||||
|
if (newVal?.uuid !== oldVal?.uuid && networkIsDisabled.value) {
|
||||||
|
await loadDisabledNetworkConfig();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const disabledNetworkConfig = ref<NetworkTypes.NetworkConfig | undefined>(undefined);
|
||||||
|
|
||||||
|
const loadDisabledNetworkConfig = async () => {
|
||||||
|
disabledNetworkConfig.value = undefined;
|
||||||
|
|
||||||
|
if (!deviceId.value || !selectedInstanceId.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = await props.api?.get_network_config(deviceId.value, selectedInstanceId.value.uuid);
|
||||||
|
disabledNetworkConfig.value = ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateNetworkState = async (disabled: boolean) => {
|
||||||
|
if (!deviceId.value || !selectedInstanceId.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await props.api?.update_device_instance_state(deviceId.value, selectedInstanceId.value.uuid, disabled);
|
||||||
|
await loadNetworkInstanceIds();
|
||||||
|
}
|
||||||
|
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
const confirmDeleteNetwork = (event: any) => {
|
const confirmDeleteNetwork = (event: any) => {
|
||||||
confirm.require({
|
confirm.require({
|
||||||
@@ -128,6 +182,15 @@ const editNetwork = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadNetworkInstanceIds = async () => {
|
||||||
|
if (!deviceId.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listInstanceIdResponse.value = await props.api?.list_deivce_instance_ids(deviceId.value);
|
||||||
|
console.debug("loadNetworkInstanceIds", listInstanceIdResponse.value);
|
||||||
|
}
|
||||||
|
|
||||||
const loadDeviceInfo = async () => {
|
const loadDeviceInfo = async () => {
|
||||||
if (!deviceId.value || !instanceId.value) {
|
if (!deviceId.value || !instanceId.value) {
|
||||||
return;
|
return;
|
||||||
@@ -146,7 +209,7 @@ const loadDeviceInfo = async () => {
|
|||||||
|
|
||||||
let periodFunc = new Utils.PeriodicTask(async () => {
|
let periodFunc = new Utils.PeriodicTask(async () => {
|
||||||
try {
|
try {
|
||||||
await loadDeviceInfo();
|
await Promise.all([loadNetworkInstanceIds(), loadDeviceInfo()]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.debug(e);
|
console.debug(e);
|
||||||
}
|
}
|
||||||
@@ -188,8 +251,23 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="!!selectedInstanceId">
|
<!-- For running network, show the status -->
|
||||||
</Status>
|
<div v-if="needShowNetworkStatus">
|
||||||
|
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="needShowNetworkStatus">
|
||||||
|
</Status>
|
||||||
|
<center>
|
||||||
|
<Button @click="updateNetworkState(true)" label="Disable Network" severity="warn" />
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- For disabled network, show the config -->
|
||||||
|
<div v-if="networkIsDisabled">
|
||||||
|
<Config :cur-network="disabledNetworkConfig" @run-network="updateNetworkState(false)"
|
||||||
|
v-if="disabledNetworkConfig" />
|
||||||
|
<div v-else>
|
||||||
|
<div class="text-center text-xl"> Network is disabled, Loading config... </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 place-content-center h-full" v-if="!selectedInstanceId">
|
<div class="grid grid-cols-1 gap-4 place-content-center h-full" v-if="!selectedInstanceId">
|
||||||
<div class="text-center text-xl"> Select or create a network instance to manage </div>
|
<div class="text-center text-xl"> Select or create a network instance to manage </div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { Card, InputText, Password, Button, AutoComplete } from 'primevue';
|
import { Card, InputText, Password, Button, AutoComplete } from 'primevue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
@@ -20,8 +20,60 @@ const registerPassword = ref('');
|
|||||||
const captcha = ref('');
|
const captcha = ref('');
|
||||||
const captchaSrc = computed(() => api.value.captcha_url());
|
const captchaSrc = computed(() => api.value.captcha_url());
|
||||||
|
|
||||||
|
interface ApiHost {
|
||||||
|
value: string;
|
||||||
|
usedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidHttpUrl = (s: string): boolean => {
|
||||||
|
let url;
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(s);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.protocol === "http:" || url.protocol === "https:";
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanAndLoadApiHosts = (): Array<ApiHost> => {
|
||||||
|
const maxHosts = 10;
|
||||||
|
const apiHosts = localStorage.getItem('apiHosts');
|
||||||
|
if (apiHosts) {
|
||||||
|
const hosts: Array<ApiHost> = JSON.parse(apiHosts);
|
||||||
|
// sort by usedAt
|
||||||
|
hosts.sort((a, b) => b.usedAt - a.usedAt);
|
||||||
|
|
||||||
|
// only keep the first 10
|
||||||
|
if (hosts.length > maxHosts) {
|
||||||
|
hosts.splice(maxHosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('apiHosts', JSON.stringify(hosts));
|
||||||
|
return hosts;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveApiHost = (host: string) => {
|
||||||
|
console.log('Save API Host:', host);
|
||||||
|
if (!isValidHttpUrl(host)) {
|
||||||
|
console.error('Invalid API Host:', host);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hosts = cleanAndLoadApiHosts();
|
||||||
|
const newHost: ApiHost = { value: host, usedAt: Date.now() };
|
||||||
|
hosts = hosts.filter((h) => h.value !== host);
|
||||||
|
hosts.push(newHost);
|
||||||
|
localStorage.setItem('apiHosts', JSON.stringify(hosts));
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
// Add your login logic here
|
// Add your login logic here
|
||||||
|
saveApiHost(apiHost.value);
|
||||||
const credential: Api.Credential = { username: username.value, password: password.value, };
|
const credential: Api.Credential = { username: username.value, password: password.value, };
|
||||||
let ret = await api.value?.login(credential);
|
let ret = await api.value?.login(credential);
|
||||||
if (ret.success) {
|
if (ret.success) {
|
||||||
@@ -36,6 +88,7 @@ const onSubmit = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onRegister = async () => {
|
const onRegister = async () => {
|
||||||
|
saveApiHost(apiHost.value);
|
||||||
const credential: Api.Credential = { username: registerUsername.value, password: registerPassword.value };
|
const credential: Api.Credential = { username: registerUsername.value, password: registerPassword.value };
|
||||||
const registerReq: Api.RegisterData = { credentials: credential, captcha: captcha.value };
|
const registerReq: Api.RegisterData = { credentials: credential, captcha: captcha.value };
|
||||||
let ret = await api.value?.register(registerReq);
|
let ret = await api.value?.register(registerReq);
|
||||||
@@ -47,17 +100,36 @@ const onRegister = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getInitialApiHost = (): string => {
|
||||||
|
const hosts = cleanAndLoadApiHosts();
|
||||||
|
if (hosts.length > 0) {
|
||||||
|
return hosts[0].value;
|
||||||
|
} else {
|
||||||
|
return defaultApiHost;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const defaultApiHost = 'https://config-server.easytier.cn'
|
const defaultApiHost = 'https://config-server.easytier.cn'
|
||||||
const apiHost = ref<string>(defaultApiHost)
|
const apiHost = ref<string>(getInitialApiHost())
|
||||||
const apiHostSuggestions = ref<Array<string>>([])
|
const apiHostSuggestions = ref<Array<string>>([])
|
||||||
const apiHostSearch = async (event: { query: string }) => {
|
const apiHostSearch = async (event: { query: string }) => {
|
||||||
apiHostSuggestions.value = [];
|
apiHostSuggestions.value = [];
|
||||||
|
let hosts = cleanAndLoadApiHosts();
|
||||||
if (event.query) {
|
if (event.query) {
|
||||||
apiHostSuggestions.value.push(event.query);
|
apiHostSuggestions.value.push(event.query);
|
||||||
}
|
}
|
||||||
apiHostSuggestions.value.push(defaultApiHost);
|
hosts.forEach((host) => {
|
||||||
|
apiHostSuggestions.value.push(host.value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let hosts = cleanAndLoadApiHosts();
|
||||||
|
if (hosts.length === 0) {
|
||||||
|
saveApiHost(defaultApiHost);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -87,7 +159,7 @@ const apiHostSearch = async (event: { query: string }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Button label="Register" type="button" class="w-full"
|
<Button label="Register" type="button" class="w-full"
|
||||||
@click="$router.replace({ name: 'register' })" severity="secondary" />
|
@click="saveApiHost(apiHost); $router.replace({ name: 'register' })" severity="secondary" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -111,7 +183,7 @@ const apiHostSearch = async (event: { query: string }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Button label="Back to Login" type="button" class="w-full"
|
<Button label="Back to Login" type="button" class="w-full"
|
||||||
@click="$router.replace({ name: 'login' })" severity="secondary" />
|
@click="saveApiHost(apiHost); $router.replace({ name: 'login' })" severity="secondary" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
_version: 2
|
||||||
|
|
||||||
|
cli:
|
||||||
|
db:
|
||||||
|
en: "path to the sqlite3 database file, used to save all the data"
|
||||||
|
zh-CN: "sqlite3 数据库文件路径, 用于保存所有数据"
|
||||||
|
console_log_level:
|
||||||
|
en: "The log level for the console logger. Possible values: trace, debug, info, warn, error"
|
||||||
|
zh-CN: "控制台日志级别。可能的值:trace, debug, info, warn, error"
|
||||||
|
file_log_level:
|
||||||
|
en: "The log level for the file logger. Possible values: trace, debug, info, warn, error"
|
||||||
|
zh-CN: "文件日志级别。可能的值:trace, debug, info, warn, error"
|
||||||
|
file_log_dir:
|
||||||
|
en: "The directory to save the log files, default is the current directory"
|
||||||
|
zh-CN: "保存日志文件的目录,默认为当前目录"
|
||||||
|
config_server_port:
|
||||||
|
en: "The port to listen for the config server, used by the easytier-core to connect to"
|
||||||
|
zh-CN: "配置服务器的监听端口,用于被 easytier-core 连接"
|
||||||
|
config_server_protocol:
|
||||||
|
en: "The protocol to listen for the config server, used by the easytier-core to connect to"
|
||||||
|
zh-CN: "配置服务器的监听协议,用于被 easytier-core 连接, 可能的值:udp, tcp"
|
||||||
|
api_server_port:
|
||||||
|
en: "The port to listen for the restful server, acting as ApiHost and used by the web frontend"
|
||||||
|
zh-CN: "restful 服务器的监听端口,作为 ApiHost 并被 web 前端使用"
|
||||||
@@ -93,7 +93,7 @@ impl ClientManager {
|
|||||||
.map(|item| item.value().clone())
|
.map(|item| item.value().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_machine_by_token(&self, token: String) -> Vec<url::Url> {
|
pub async fn list_machine_by_token(&self, token: String) -> Vec<url::Url> {
|
||||||
self.storage.list_token_clients(&token)
|
self.storage.list_token_clients(&token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{fmt::Debug, sync::Arc};
|
use std::{fmt::Debug, str::FromStr as _, sync::Arc};
|
||||||
|
|
||||||
use easytier::{
|
use easytier::{
|
||||||
common::scoped_task::ScopedTask,
|
common::scoped_task::ScopedTask,
|
||||||
@@ -15,6 +15,8 @@ use easytier::{
|
|||||||
};
|
};
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::{broadcast, RwLock};
|
||||||
|
|
||||||
|
use crate::db::ListNetworkProps;
|
||||||
|
|
||||||
use super::storage::{Storage, StorageToken, WeakRefStorage};
|
use super::storage::{Storage, StorageToken, WeakRefStorage};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -87,10 +89,20 @@ impl WebServerService for SessionRpcService {
|
|||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.unwrap_or(uuid::Uuid::new_v4()),
|
.unwrap_or(uuid::Uuid::new_v4()),
|
||||||
});
|
});
|
||||||
if let Ok(storage) = Storage::try_from(data.storage.clone()) {
|
|
||||||
storage.add_client(data.storage_token.as_ref().unwrap().clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(storage) = Storage::try_from(data.storage.clone()) {
|
||||||
|
let Ok(report_time) = chrono::DateTime::<chrono::Local>::from_str(&req.report_time)
|
||||||
|
else {
|
||||||
|
tracing::error!("Failed to parse report time: {:?}", req.report_time);
|
||||||
|
return Ok(HeartbeatResponse {});
|
||||||
|
};
|
||||||
|
storage.update_client(
|
||||||
|
data.storage_token.as_ref().unwrap().clone(),
|
||||||
|
report_time.timestamp(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let _ = data.notifier.send(req);
|
let _ = data.notifier.send(req);
|
||||||
Ok(HeartbeatResponse {})
|
Ok(HeartbeatResponse {})
|
||||||
}
|
}
|
||||||
@@ -196,7 +208,11 @@ impl Session {
|
|||||||
|
|
||||||
let local_configs = match storage
|
let local_configs = match storage
|
||||||
.db
|
.db
|
||||||
.list_network_configs(user_id, Some(req.machine_id.unwrap().into()), true)
|
.list_network_configs(
|
||||||
|
user_id,
|
||||||
|
Some(req.machine_id.unwrap().into()),
|
||||||
|
ListNetworkProps::EnabledOnly,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(configs) => configs,
|
Ok(configs) => configs,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::DashMap;
|
||||||
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
|
|
||||||
@@ -12,11 +12,19 @@ pub struct StorageToken {
|
|||||||
pub machine_id: uuid::Uuid,
|
pub machine_id: uuid::Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ClientInfo {
|
||||||
|
client_url: url::Url,
|
||||||
|
machine_id: uuid::Uuid,
|
||||||
|
token: String,
|
||||||
|
report_time: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StorageInner {
|
pub struct StorageInner {
|
||||||
// some map for indexing
|
// some map for indexing
|
||||||
pub token_clients_map: DashMap<String, DashSet<url::Url>>,
|
token_clients_map: DashMap<String, DashMap<uuid::Uuid, ClientInfo>>,
|
||||||
pub machine_client_url_map: DashMap<uuid::Uuid, DashSet<url::Url>>,
|
machine_client_url_map: DashMap<uuid::Uuid, ClientInfo>,
|
||||||
pub db: Db,
|
pub db: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,33 +49,57 @@ impl Storage {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_client(&self, stoken: StorageToken) {
|
fn remove_mid_to_client_info_map(
|
||||||
|
map: &DashMap<uuid::Uuid, ClientInfo>,
|
||||||
|
machine_id: &uuid::Uuid,
|
||||||
|
client_url: &url::Url,
|
||||||
|
) {
|
||||||
|
map.remove_if(&machine_id, |_, v| v.client_url == *client_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_mid_to_client_info_map(
|
||||||
|
map: &DashMap<uuid::Uuid, ClientInfo>,
|
||||||
|
client_info: &ClientInfo,
|
||||||
|
) {
|
||||||
|
map.entry(client_info.machine_id)
|
||||||
|
.and_modify(|e| {
|
||||||
|
if e.report_time < client_info.report_time {
|
||||||
|
assert_eq!(e.machine_id, client_info.machine_id);
|
||||||
|
*e = client_info.clone();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_insert(client_info.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_client(&self, stoken: StorageToken, report_time: i64) {
|
||||||
let inner = self
|
let inner = self
|
||||||
.0
|
.0
|
||||||
.token_clients_map
|
.token_clients_map
|
||||||
.entry(stoken.token)
|
.entry(stoken.token.clone())
|
||||||
.or_insert_with(DashSet::new);
|
.or_insert_with(DashMap::new);
|
||||||
inner.insert(stoken.client_url.clone());
|
|
||||||
|
|
||||||
self.0
|
let client_info = ClientInfo {
|
||||||
.machine_client_url_map
|
client_url: stoken.client_url.clone(),
|
||||||
.entry(stoken.machine_id)
|
machine_id: stoken.machine_id,
|
||||||
.or_insert_with(DashSet::new)
|
token: stoken.token.clone(),
|
||||||
.insert(stoken.client_url.clone());
|
report_time,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::update_mid_to_client_info_map(&inner, &client_info);
|
||||||
|
Self::update_mid_to_client_info_map(&self.0.machine_client_url_map, &client_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_client(&self, stoken: &StorageToken) {
|
pub fn remove_client(&self, stoken: &StorageToken) {
|
||||||
self.0.token_clients_map.remove_if(&stoken.token, |_, set| {
|
self.0.token_clients_map.remove_if(&stoken.token, |_, set| {
|
||||||
set.remove(&stoken.client_url);
|
Self::remove_mid_to_client_info_map(set, &stoken.machine_id, &stoken.client_url);
|
||||||
set.is_empty()
|
set.is_empty()
|
||||||
});
|
});
|
||||||
|
|
||||||
self.0
|
Self::remove_mid_to_client_info_map(
|
||||||
.machine_client_url_map
|
&self.0.machine_client_url_map,
|
||||||
.remove_if(&stoken.machine_id, |_, set| {
|
&stoken.machine_id,
|
||||||
set.remove(&stoken.client_url);
|
&stoken.client_url,
|
||||||
set.is_empty()
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weak_ref(&self) -> WeakRefStorage {
|
pub fn weak_ref(&self) -> WeakRefStorage {
|
||||||
@@ -78,15 +110,19 @@ impl Storage {
|
|||||||
self.0
|
self.0
|
||||||
.machine_client_url_map
|
.machine_client_url_map
|
||||||
.get(&machine_id)
|
.get(&machine_id)
|
||||||
.map(|url| url.iter().next().map(|url| url.clone()))
|
.map(|info| info.client_url.clone())
|
||||||
.flatten()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_token_clients(&self, token: &str) -> Vec<url::Url> {
|
pub fn list_token_clients(&self, token: &str) -> Vec<url::Url> {
|
||||||
self.0
|
self.0
|
||||||
.token_clients_map
|
.token_clients_map
|
||||||
.get(token)
|
.get(token)
|
||||||
.map(|set| set.iter().map(|url| url.clone()).collect())
|
.map(|info_map| {
|
||||||
|
info_map
|
||||||
|
.iter()
|
||||||
|
.map(|info| info.value().client_url.clone())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pub mod entity;
|
|||||||
|
|
||||||
use entity::user_running_network_configs;
|
use entity::user_running_network_configs;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait as _,
|
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
|
||||||
QueryFilter as _, SqlxSqliteConnector, TransactionTrait as _,
|
QueryFilter as _, SqlxSqliteConnector, TransactionTrait as _,
|
||||||
};
|
};
|
||||||
use sea_orm_migration::MigratorTrait as _;
|
use sea_orm_migration::MigratorTrait as _;
|
||||||
@@ -14,6 +14,12 @@ use crate::migrator;
|
|||||||
|
|
||||||
type UserIdInDb = i32;
|
type UserIdInDb = i32;
|
||||||
|
|
||||||
|
pub enum ListNetworkProps {
|
||||||
|
All,
|
||||||
|
EnabledOnly,
|
||||||
|
DisabledOnly,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Db {
|
pub struct Db {
|
||||||
db_path: String,
|
db_path: String,
|
||||||
@@ -115,17 +121,51 @@ impl Db {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_network_config_state(
|
||||||
|
&self,
|
||||||
|
user_id: UserIdInDb,
|
||||||
|
network_inst_id: uuid::Uuid,
|
||||||
|
disabled: bool,
|
||||||
|
) -> Result<entity::user_running_network_configs::Model, DbErr> {
|
||||||
|
use entity::user_running_network_configs as urnc;
|
||||||
|
|
||||||
|
urnc::Entity::update_many()
|
||||||
|
.filter(urnc::Column::UserId.eq(user_id))
|
||||||
|
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id.to_string()))
|
||||||
|
.col_expr(urnc::Column::Disabled, Expr::value(disabled))
|
||||||
|
.col_expr(
|
||||||
|
urnc::Column::UpdateTime,
|
||||||
|
Expr::value(chrono::Local::now().fixed_offset()),
|
||||||
|
)
|
||||||
|
.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
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_network_configs(
|
pub async fn list_network_configs(
|
||||||
&self,
|
&self,
|
||||||
user_id: UserIdInDb,
|
user_id: UserIdInDb,
|
||||||
device_id: Option<uuid::Uuid>,
|
device_id: Option<uuid::Uuid>,
|
||||||
only_enabled: bool,
|
props: ListNetworkProps,
|
||||||
) -> Result<Vec<user_running_network_configs::Model>, DbErr> {
|
) -> Result<Vec<user_running_network_configs::Model>, DbErr> {
|
||||||
use entity::user_running_network_configs as urnc;
|
use entity::user_running_network_configs as urnc;
|
||||||
|
|
||||||
let configs = urnc::Entity::find().filter(urnc::Column::UserId.eq(user_id));
|
let configs = urnc::Entity::find().filter(urnc::Column::UserId.eq(user_id));
|
||||||
let configs = if only_enabled {
|
let configs = if matches!(
|
||||||
configs.filter(urnc::Column::Disabled.eq(false))
|
props,
|
||||||
|
ListNetworkProps::EnabledOnly | ListNetworkProps::DisabledOnly
|
||||||
|
) {
|
||||||
|
configs
|
||||||
|
.filter(urnc::Column::Disabled.eq(matches!(props, ListNetworkProps::DisabledOnly)))
|
||||||
} else {
|
} else {
|
||||||
configs
|
configs
|
||||||
};
|
};
|
||||||
@@ -140,6 +180,24 @@ impl Db {
|
|||||||
Ok(configs)
|
Ok(configs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_network_config(
|
||||||
|
&self,
|
||||||
|
user_id: UserIdInDb,
|
||||||
|
device_id: &uuid::Uuid,
|
||||||
|
network_inst_id: &String,
|
||||||
|
) -> Result<Option<user_running_network_configs::Model>, DbErr> {
|
||||||
|
use entity::user_running_network_configs as urnc;
|
||||||
|
|
||||||
|
let config = urnc::Entity::find()
|
||||||
|
.filter(urnc::Column::UserId.eq(user_id))
|
||||||
|
.filter(urnc::Column::DeviceId.eq(device_id.to_string()))
|
||||||
|
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id))
|
||||||
|
.one(self.orm_db())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_user_id<T: ToString>(
|
pub async fn get_user_id<T: ToString>(
|
||||||
&self,
|
&self,
|
||||||
user_name: T,
|
user_name: T,
|
||||||
@@ -167,7 +225,7 @@ impl Db {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
||||||
|
|
||||||
use crate::db::{entity::user_running_network_configs, Db};
|
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_user_network_config_management() {
|
async fn test_user_network_config_management() {
|
||||||
@@ -209,7 +267,7 @@ mod tests {
|
|||||||
assert_ne!(result.update_time, result2.update_time);
|
assert_ne!(result.update_time, result2.update_time);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
db.list_network_configs(user_id, Some(device_id), true)
|
db.list_network_configs(user_id, Some(device_id), ListNetworkProps::All)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.len(),
|
.len(),
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rust_i18n;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use clap::{command, Parser};
|
||||||
use easytier::{
|
use easytier::{
|
||||||
common::config::{ConfigLoader, ConsoleLoggerConfig, TomlConfigLoader},
|
common::{
|
||||||
|
config::{ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, TomlConfigLoader},
|
||||||
|
constants::EASYTIER_VERSION,
|
||||||
|
},
|
||||||
tunnel::udp::UdpTunnelListener,
|
tunnel::udp::UdpTunnelListener,
|
||||||
utils::init_logger,
|
utils::{init_logger, setup_panic_handler},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod client_manager;
|
mod client_manager;
|
||||||
@@ -13,26 +20,97 @@ mod db;
|
|||||||
mod migrator;
|
mod migrator;
|
||||||
mod restful;
|
mod restful;
|
||||||
|
|
||||||
|
rust_i18n::i18n!("locales", fallback = "en");
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(name = "easytier-core", author, version = EASYTIER_VERSION , about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(short, long, default_value = "et.db", help = t!("cli.db").to_string())]
|
||||||
|
db: String,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = t!("cli.console_log_level").to_string(),
|
||||||
|
)]
|
||||||
|
console_log_level: Option<String>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = t!("cli.file_log_level").to_string(),
|
||||||
|
)]
|
||||||
|
file_log_level: Option<String>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = t!("cli.file_log_dir").to_string(),
|
||||||
|
)]
|
||||||
|
file_log_dir: Option<String>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short='c',
|
||||||
|
default_value = "22020",
|
||||||
|
help = t!("cli.config_server_port").to_string(),
|
||||||
|
)]
|
||||||
|
config_server_port: u16,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short='p',
|
||||||
|
default_value = "udp",
|
||||||
|
help = t!("cli.config_server_protocol").to_string(),
|
||||||
|
)]
|
||||||
|
config_server_protocol: String,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short='a',
|
||||||
|
default_value = "11211",
|
||||||
|
help = t!("cli.api_server_port").to_string(),
|
||||||
|
)]
|
||||||
|
api_server_port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||||
|
rust_i18n::set_locale(&locale);
|
||||||
|
setup_panic_handler();
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
let config = TomlConfigLoader::default();
|
let config = TomlConfigLoader::default();
|
||||||
config.set_console_logger_config(ConsoleLoggerConfig {
|
config.set_console_logger_config(ConsoleLoggerConfig {
|
||||||
level: Some("trace".to_string()),
|
level: cli.console_log_level,
|
||||||
|
});
|
||||||
|
config.set_file_logger_config(FileLoggerConfig {
|
||||||
|
dir: cli.file_log_dir,
|
||||||
|
level: cli.file_log_level,
|
||||||
|
file: None,
|
||||||
});
|
});
|
||||||
init_logger(config, false).unwrap();
|
init_logger(config, false).unwrap();
|
||||||
|
|
||||||
// let db = db::Db::new(":memory:").await.unwrap();
|
// let db = db::Db::new(":memory:").await.unwrap();
|
||||||
let db = db::Db::new("et.db").await.unwrap();
|
let db = db::Db::new(cli.db).await.unwrap();
|
||||||
|
|
||||||
let listener = UdpTunnelListener::new("udp://0.0.0.0:22020".parse().unwrap());
|
let listener = UdpTunnelListener::new(
|
||||||
|
format!(
|
||||||
|
"{}://0.0.0.0:{}",
|
||||||
|
cli.config_server_protocol, cli.config_server_port
|
||||||
|
)
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
let mut mgr = client_manager::ClientManager::new(db.clone());
|
let mut mgr = client_manager::ClientManager::new(db.clone());
|
||||||
mgr.serve(listener).await.unwrap();
|
mgr.serve(listener).await.unwrap();
|
||||||
let mgr = Arc::new(mgr);
|
let mgr = Arc::new(mgr);
|
||||||
|
|
||||||
let mut restful_server =
|
let mut restful_server = restful::RestfulServer::new(
|
||||||
restful::RestfulServer::new("0.0.0.0:11211".parse().unwrap(), mgr.clone(), db)
|
format!("0.0.0.0:{}", cli.api_server_port).parse().unwrap(),
|
||||||
.await
|
mgr.clone(),
|
||||||
.unwrap();
|
db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
restful_server.start().await.unwrap();
|
restful_server.start().await.unwrap();
|
||||||
tokio::signal::ctrl_c().await.unwrap();
|
tokio::signal::ctrl_c().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ impl RestfulServer {
|
|||||||
return Err((StatusCode::UNAUTHORIZED, other_error("No such user").into()));
|
return Err((StatusCode::UNAUTHORIZED, other_error("No such user").into()));
|
||||||
};
|
};
|
||||||
|
|
||||||
let machines = client_mgr.list_machine_by_token(user.tokens[0].clone());
|
let machines = client_mgr
|
||||||
|
.list_machine_by_token(user.tokens[0].clone())
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(GetSummaryJsonResp {
|
Ok(GetSummaryJsonResp {
|
||||||
device_count: machines.len() as u32,
|
device_count: machines.len() as u32,
|
||||||
@@ -167,7 +169,7 @@ impl RestfulServer {
|
|||||||
.deflate(true)
|
.deflate(true)
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
.zstd(true)
|
.zstd(true)
|
||||||
.quality(tower_http::compression::CompressionLevel::Best);
|
.quality(tower_http::compression::CompressionLevel::Default);
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/api/v1/summary", get(Self::handle_get_summary))
|
.route("/api/v1/summary", get(Self::handle_get_summary))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::{Path, Query};
|
use axum::extract::Path;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::routing::{delete, post};
|
use axum::routing::{delete, post};
|
||||||
use axum::{extract::State, routing::get, Json, Router};
|
use axum::{extract::State, routing::get, Json, Router};
|
||||||
@@ -13,6 +13,7 @@ use easytier::proto::web::*;
|
|||||||
|
|
||||||
use crate::client_manager::session::Session;
|
use crate::client_manager::session::Session;
|
||||||
use crate::client_manager::ClientManager;
|
use crate::client_manager::ClientManager;
|
||||||
|
use crate::db::ListNetworkProps;
|
||||||
|
|
||||||
use super::users::AuthSession;
|
use super::users::AuthSession;
|
||||||
use super::{
|
use super::{
|
||||||
@@ -46,13 +47,21 @@ struct ColletNetworkInfoJsonReq {
|
|||||||
inst_ids: Option<Vec<uuid::Uuid>>,
|
inst_ids: Option<Vec<uuid::Uuid>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct UpdateNetworkStateJsonReq {
|
||||||
|
disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
struct RemoveNetworkJsonReq {
|
struct RemoveNetworkJsonReq {
|
||||||
inst_ids: Vec<uuid::Uuid>,
|
inst_ids: Vec<uuid::Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
struct ListNetworkInstanceIdsJsonResp(Vec<uuid::Uuid>);
|
struct ListNetworkInstanceIdsJsonResp {
|
||||||
|
running_inst_ids: Vec<easytier::proto::common::Uuid>,
|
||||||
|
disabled_inst_ids: Vec<easytier::proto::common::Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
struct ListMachineItem {
|
struct ListMachineItem {
|
||||||
@@ -190,7 +199,7 @@ impl NetworkApi {
|
|||||||
auth_session: AuthSession,
|
auth_session: AuthSession,
|
||||||
State(client_mgr): AppState,
|
State(client_mgr): AppState,
|
||||||
Path(machine_id): Path<uuid::Uuid>,
|
Path(machine_id): Path<uuid::Uuid>,
|
||||||
Query(payload): Query<ColletNetworkInfoJsonReq>,
|
Json(payload): Json<ColletNetworkInfoJsonReq>,
|
||||||
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
|
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
|
||||||
let result =
|
let result =
|
||||||
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
|
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
|
||||||
@@ -226,10 +235,28 @@ impl NetworkApi {
|
|||||||
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
|
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
|
||||||
.await
|
.await
|
||||||
.map_err(convert_rpc_error)?;
|
.map_err(convert_rpc_error)?;
|
||||||
Ok(
|
|
||||||
ListNetworkInstanceIdsJsonResp(ret.inst_ids.into_iter().map(Into::into).collect())
|
let running_inst_ids = ret.inst_ids.clone().into_iter().map(Into::into).collect();
|
||||||
.into(),
|
|
||||||
)
|
// collect networks that are disabled
|
||||||
|
let disabled_inst_ids = client_mgr
|
||||||
|
.db()
|
||||||
|
.list_network_configs(
|
||||||
|
auth_session.user.unwrap().id(),
|
||||||
|
Some(machine_id),
|
||||||
|
ListNetworkProps::DisabledOnly,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(convert_db_error)?
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| x.network_instance_id.clone().try_into().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(ListNetworkInstanceIdsJsonResp {
|
||||||
|
running_inst_ids,
|
||||||
|
disabled_inst_ids,
|
||||||
|
}
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_remove_network_instance(
|
async fn handle_remove_network_instance(
|
||||||
@@ -270,7 +297,7 @@ impl NetworkApi {
|
|||||||
|
|
||||||
let client_urls = DashSet::new();
|
let client_urls = DashSet::new();
|
||||||
for token in tokens {
|
for token in tokens {
|
||||||
let urls = client_mgr.list_machine_by_token(token);
|
let urls = client_mgr.list_machine_by_token(token).await;
|
||||||
for url in urls {
|
for url in urls {
|
||||||
client_urls.insert(url);
|
client_urls.insert(url);
|
||||||
}
|
}
|
||||||
@@ -289,6 +316,54 @@ impl NetworkApi {
|
|||||||
Ok(Json(ListMachineJsonResp { machines }))
|
Ok(Json(ListMachineJsonResp { machines }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_update_network_state(
|
||||||
|
auth_session: AuthSession,
|
||||||
|
State(client_mgr): AppState,
|
||||||
|
Path((machine_id, inst_id)): Path<(uuid::Uuid, Option<uuid::Uuid>)>,
|
||||||
|
Json(payload): Json<UpdateNetworkStateJsonReq>,
|
||||||
|
) -> Result<(), HttpHandleError> {
|
||||||
|
let Some(inst_id) = inst_id else {
|
||||||
|
// not implement disable all
|
||||||
|
return Err((
|
||||||
|
StatusCode::NOT_IMPLEMENTED,
|
||||||
|
other_error(format!("Not implemented")).into(),
|
||||||
|
))
|
||||||
|
.into();
|
||||||
|
};
|
||||||
|
|
||||||
|
let sess = Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
|
||||||
|
let cfg = client_mgr
|
||||||
|
.db()
|
||||||
|
.update_network_config_state(auth_session.user.unwrap().id(), inst_id, payload.disabled)
|
||||||
|
.await
|
||||||
|
.map_err(convert_db_error)?;
|
||||||
|
|
||||||
|
let c = sess.scoped_rpc_client();
|
||||||
|
|
||||||
|
if payload.disabled {
|
||||||
|
c.delete_network_instance(
|
||||||
|
BaseController::default(),
|
||||||
|
DeleteNetworkInstanceRequest {
|
||||||
|
inst_ids: vec![inst_id.into()],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(convert_rpc_error)?;
|
||||||
|
} else {
|
||||||
|
c.run_network_instance(
|
||||||
|
BaseController::default(),
|
||||||
|
RunNetworkInstanceRequest {
|
||||||
|
inst_id: Some(inst_id.into()),
|
||||||
|
config: Some(serde_json::from_str(&cfg.network_config).unwrap()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(convert_rpc_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_get_network_config(
|
async fn handle_get_network_config(
|
||||||
auth_session: AuthSession,
|
auth_session: AuthSession,
|
||||||
State(client_mgr): AppState,
|
State(client_mgr): AppState,
|
||||||
@@ -298,25 +373,24 @@ impl NetworkApi {
|
|||||||
|
|
||||||
let db_row = client_mgr
|
let db_row = client_mgr
|
||||||
.db()
|
.db()
|
||||||
.list_network_configs(auth_session.user.unwrap().id(), Some(machine_id), false)
|
.get_network_config(auth_session.user.unwrap().id(), &machine_id, &inst_id)
|
||||||
.await
|
.await
|
||||||
.map_err(convert_db_error)?
|
.map_err(convert_db_error)?
|
||||||
.iter()
|
|
||||||
.find(|x| x.network_instance_id == inst_id)
|
|
||||||
.map(|x| x.network_config.clone())
|
|
||||||
.ok_or((
|
.ok_or((
|
||||||
StatusCode::NOT_FOUND,
|
StatusCode::NOT_FOUND,
|
||||||
other_error(format!("No such network instance: {}", inst_id)).into(),
|
other_error(format!("No such network instance: {}", inst_id)).into(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
Ok(serde_json::from_str::<NetworkConfig>(&db_row)
|
Ok(
|
||||||
.map_err(|e| {
|
serde_json::from_str::<NetworkConfig>(&db_row.network_config)
|
||||||
(
|
.map_err(|e| {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
(
|
||||||
other_error(format!("Failed to parse network config: {:?}", e)).into(),
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
)
|
other_error(format!("Failed to parse network config: {:?}", e)).into(),
|
||||||
})?
|
)
|
||||||
.into())
|
})?
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_route(&mut self) -> Router<AppStateInner> {
|
pub fn build_route(&mut self) -> Router<AppStateInner> {
|
||||||
@@ -332,7 +406,7 @@ impl NetworkApi {
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/v1/machines/:machine-id/networks/:inst-id",
|
"/api/v1/machines/:machine-id/networks/:inst-id",
|
||||||
delete(Self::handle_remove_network_instance),
|
delete(Self::handle_remove_network_instance).put(Self::handle_update_network_state),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/v1/machines/:machine-id/networks/info",
|
"/api/v1/machines/:machine-id/networks/info",
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ name = "easytier"
|
|||||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||||
homepage = "https://github.com/EasyTier/EasyTier"
|
homepage = "https://github.com/EasyTier/EasyTier"
|
||||||
repository = "https://github.com/EasyTier/EasyTier"
|
repository = "https://github.com/EasyTier/EasyTier"
|
||||||
version = "2.1.0"
|
version = "2.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["kkrainbow"]
|
authors = ["kkrainbow"]
|
||||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||||
|
|||||||
@@ -134,6 +134,12 @@ core_clap:
|
|||||||
compression:
|
compression:
|
||||||
en: "compression algorithm to use, support none, zstd. default is none"
|
en: "compression algorithm to use, support none, zstd. default is none"
|
||||||
zh-CN: "要使用的压缩算法,支持 none、zstd。默认为 none"
|
zh-CN: "要使用的压缩算法,支持 none、zstd。默认为 none"
|
||||||
|
mapped_listeners:
|
||||||
|
en: "manually specify the public address of the listener, other nodes can use this address to connect to this node. e.g.: tcp://123.123.123.123:11223, can specify multiple."
|
||||||
|
zh-CN: "手动指定监听器的公网地址,其他节点可以使用该地址连接到本节点。例如:tcp://123.123.123.123:11223,可以指定多个。"
|
||||||
|
bind_device:
|
||||||
|
en: "bind the connector socket to physical devices to avoid routing issues. e.g.: subnet proxy segment conflicts with a node's segment, after binding the physical device, it can communicate with the node normally."
|
||||||
|
zh-CN: "将连接器的套接字绑定到物理设备以避免路由问题。比如子网代理网段与某节点的网段冲突,绑定物理设备后可以与该节点正常通信。"
|
||||||
|
|
||||||
core_app:
|
core_app:
|
||||||
panic_backtrace_save:
|
panic_backtrace_save:
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ pub fn gen_default_flags() -> Flags {
|
|||||||
relay_all_peer_rpc: false,
|
relay_all_peer_rpc: false,
|
||||||
disable_udp_hole_punching: false,
|
disable_udp_hole_punching: false,
|
||||||
ipv6_listener: "udp://[::]:0".to_string(),
|
ipv6_listener: "udp://[::]:0".to_string(),
|
||||||
multi_thread: false,
|
multi_thread: true,
|
||||||
data_compress_algo: CompressionAlgoPb::None.into(),
|
data_compress_algo: CompressionAlgoPb::None.into(),
|
||||||
|
bind_device: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +73,9 @@ pub trait ConfigLoader: Send + Sync {
|
|||||||
fn get_listeners(&self) -> Vec<url::Url>;
|
fn get_listeners(&self) -> Vec<url::Url>;
|
||||||
fn set_listeners(&self, listeners: Vec<url::Url>);
|
fn set_listeners(&self, listeners: Vec<url::Url>);
|
||||||
|
|
||||||
|
fn get_mapped_listeners(&self) -> Vec<url::Url>;
|
||||||
|
fn set_mapped_listeners(&self, listeners: Option<Vec<url::Url>>);
|
||||||
|
|
||||||
fn get_rpc_portal(&self) -> Option<SocketAddr>;
|
fn get_rpc_portal(&self) -> Option<SocketAddr>;
|
||||||
fn set_rpc_portal(&self, addr: SocketAddr);
|
fn set_rpc_portal(&self, addr: SocketAddr);
|
||||||
|
|
||||||
@@ -183,6 +187,7 @@ struct Config {
|
|||||||
dhcp: Option<bool>,
|
dhcp: Option<bool>,
|
||||||
network_identity: Option<NetworkIdentity>,
|
network_identity: Option<NetworkIdentity>,
|
||||||
listeners: Option<Vec<url::Url>>,
|
listeners: Option<Vec<url::Url>>,
|
||||||
|
mapped_listeners: Option<Vec<url::Url>>,
|
||||||
exit_nodes: Option<Vec<Ipv4Addr>>,
|
exit_nodes: Option<Vec<Ipv4Addr>>,
|
||||||
|
|
||||||
peer: Option<Vec<PeerConfig>>,
|
peer: Option<Vec<PeerConfig>>,
|
||||||
@@ -472,6 +477,19 @@ impl ConfigLoader for TomlConfigLoader {
|
|||||||
self.config.lock().unwrap().listeners = Some(listeners);
|
self.config.lock().unwrap().listeners = Some(listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_mapped_listeners(&self) -> Vec<url::Url> {
|
||||||
|
self.config
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.mapped_listeners
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_mapped_listeners(&self, listeners: Option<Vec<url::Url>>) {
|
||||||
|
self.config.lock().unwrap().mapped_listeners = listeners;
|
||||||
|
}
|
||||||
|
|
||||||
fn get_rpc_portal(&self) -> Option<SocketAddr> {
|
fn get_rpc_portal(&self) -> Option<SocketAddr> {
|
||||||
self.config.lock().unwrap().rpc_portal
|
self.config.lock().unwrap().rpc_portal
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,10 @@ impl GlobalCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_running_listener(&self, url: url::Url) {
|
pub fn add_running_listener(&self, url: url::Url) {
|
||||||
self.running_listeners.lock().unwrap().push(url);
|
let mut l = self.running_listeners.lock().unwrap();
|
||||||
|
if !l.contains(&url) {
|
||||||
|
l.push(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_vpn_portal_cidr(&self) -> Option<cidr::Ipv4Cidr> {
|
pub fn get_vpn_portal_cidr(&self) -> Option<cidr::Ipv4Cidr> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
future,
|
future,
|
||||||
|
io::Write as _,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
@@ -81,7 +82,17 @@ pub fn join_joinset_background<T: Debug + Send + Sync + 'static>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_machine_id() -> uuid::Uuid {
|
pub fn get_machine_id() -> uuid::Uuid {
|
||||||
// TODO: load from local file
|
// a path same as the binary
|
||||||
|
let machine_id_file = std::env::current_exe()
|
||||||
|
.map(|x| x.with_file_name("et_machine_id"))
|
||||||
|
.unwrap_or_else(|_| std::path::PathBuf::from("et_machine_id"));
|
||||||
|
|
||||||
|
// try load from local file
|
||||||
|
if let Ok(mid) = std::fs::read_to_string(&machine_id_file) {
|
||||||
|
if let Ok(mid) = uuid::Uuid::parse_str(mid.trim()) {
|
||||||
|
return mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
@@ -95,7 +106,7 @@ pub fn get_machine_id() -> uuid::Uuid {
|
|||||||
crate::tunnel::generate_digest_from_str("", x.as_str(), &mut b);
|
crate::tunnel::generate_digest_from_str("", x.as_str(), &mut b);
|
||||||
uuid::Uuid::from_bytes(b)
|
uuid::Uuid::from_bytes(b)
|
||||||
})
|
})
|
||||||
.unwrap_or(uuid::Uuid::new_v4());
|
.ok();
|
||||||
|
|
||||||
#[cfg(not(any(
|
#[cfg(not(any(
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
@@ -103,9 +114,18 @@ pub fn get_machine_id() -> uuid::Uuid {
|
|||||||
target_os = "windows",
|
target_os = "windows",
|
||||||
target_os = "freebsd"
|
target_os = "freebsd"
|
||||||
)))]
|
)))]
|
||||||
|
let gen_mid = None;
|
||||||
|
|
||||||
|
if gen_mid.is_some() {
|
||||||
|
return gen_mid.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let gen_mid = uuid::Uuid::new_v4();
|
let gen_mid = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
// TODO: save to local file
|
// try save to local file
|
||||||
|
if let Ok(mut file) = std::fs::File::create(machine_id_file) {
|
||||||
|
let _ = file.write_all(gen_mid.to_string().as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
gen_mid
|
gen_mid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
// try connect peers directly, with either its public ip or lan ip
|
// try connect peers directly, with either its public ip or lan ip
|
||||||
|
|
||||||
use std::{net::SocketAddr, sync::Arc, time::Duration};
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{error::Error, global_ctx::ArcGlobalCtx, PeerId},
|
common::{error::Error, global_ctx::ArcGlobalCtx, PeerId},
|
||||||
@@ -29,6 +36,8 @@ use super::create_connector_by_url;
|
|||||||
pub const DIRECT_CONNECTOR_SERVICE_ID: u32 = 1;
|
pub const DIRECT_CONNECTOR_SERVICE_ID: u32 = 1;
|
||||||
pub const DIRECT_CONNECTOR_BLACKLIST_TIMEOUT_SEC: u64 = 300;
|
pub const DIRECT_CONNECTOR_BLACKLIST_TIMEOUT_SEC: u64 = 300;
|
||||||
|
|
||||||
|
static TESTING: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait PeerManagerForDirectConnector {
|
pub trait PeerManagerForDirectConnector {
|
||||||
async fn list_peers(&self) -> Vec<PeerId>;
|
async fn list_peers(&self) -> Vec<PeerId>;
|
||||||
@@ -182,7 +191,7 @@ impl DirectConnectorManager {
|
|||||||
|
|
||||||
// let (peer_id, conn_id) = data.peer_manager.try_connect(connector).await?;
|
// let (peer_id, conn_id) = data.peer_manager.try_connect(connector).await?;
|
||||||
|
|
||||||
if peer_id != dst_peer_id {
|
if peer_id != dst_peer_id && !TESTING.load(Ordering::Relaxed) {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"connect to ip succ: {}, but peer id mismatch, expect: {}, actual: {}",
|
"connect to ip succ: {}, but peer id mismatch, expect: {}, actual: {}",
|
||||||
addr,
|
addr,
|
||||||
@@ -279,87 +288,103 @@ impl DirectConnectorManager {
|
|||||||
|
|
||||||
let listener_host = listener.socket_addrs(|| None).unwrap().pop();
|
let listener_host = listener.socket_addrs(|| None).unwrap().pop();
|
||||||
match listener_host {
|
match listener_host {
|
||||||
Some(SocketAddr::V4(_)) => {
|
Some(SocketAddr::V4(s_addr)) => {
|
||||||
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
if s_addr.ip().is_unspecified() {
|
||||||
let mut addr = (*listener).clone();
|
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
||||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
let mut addr = (*listener).clone();
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||||
data.clone(),
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
dst_peer_id.clone(),
|
data.clone(),
|
||||||
addr.to_string(),
|
dst_peer_id.clone(),
|
||||||
));
|
addr.to_string(),
|
||||||
} else {
|
));
|
||||||
tracing::error!(
|
} else {
|
||||||
?ip,
|
tracing::error!(
|
||||||
?listener,
|
?ip,
|
||||||
?dst_peer_id,
|
?listener,
|
||||||
"failed to set host for interface ipv4"
|
?dst_peer_id,
|
||||||
);
|
"failed to set host for interface ipv4"
|
||||||
}
|
);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(public_ipv4) = ip_list.public_ipv4 {
|
if let Some(public_ipv4) = ip_list.public_ipv4 {
|
||||||
let mut addr = (*listener).clone();
|
let mut addr = (*listener).clone();
|
||||||
if addr
|
if addr
|
||||||
.set_host(Some(public_ipv4.to_string().as_str()))
|
.set_host(Some(public_ipv4.to_string().as_str()))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
data.clone(),
|
data.clone(),
|
||||||
dst_peer_id.clone(),
|
dst_peer_id.clone(),
|
||||||
addr.to_string(),
|
addr.to_string(),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?public_ipv4,
|
?public_ipv4,
|
||||||
?listener,
|
?listener,
|
||||||
?dst_peer_id,
|
?dst_peer_id,
|
||||||
"failed to set host for public ipv4"
|
"failed to set host for public ipv4"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||||
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
|
data.clone(),
|
||||||
|
dst_peer_id.clone(),
|
||||||
|
listener.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(SocketAddr::V6(_)) => {
|
Some(SocketAddr::V6(s_addr)) => {
|
||||||
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
if s_addr.ip().is_unspecified() {
|
||||||
let mut addr = (*listener).clone();
|
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
||||||
if addr
|
let mut addr = (*listener).clone();
|
||||||
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
if addr
|
||||||
.is_ok()
|
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
||||||
{
|
.is_ok()
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
{
|
||||||
data.clone(),
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
dst_peer_id.clone(),
|
data.clone(),
|
||||||
addr.to_string(),
|
dst_peer_id.clone(),
|
||||||
));
|
addr.to_string(),
|
||||||
} else {
|
));
|
||||||
tracing::error!(
|
} else {
|
||||||
?ip,
|
tracing::error!(
|
||||||
?listener,
|
?ip,
|
||||||
?dst_peer_id,
|
?listener,
|
||||||
"failed to set host for interface ipv6"
|
?dst_peer_id,
|
||||||
);
|
"failed to set host for interface ipv6"
|
||||||
}
|
);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(public_ipv6) = ip_list.public_ipv6 {
|
if let Some(public_ipv6) = ip_list.public_ipv6 {
|
||||||
let mut addr = (*listener).clone();
|
let mut addr = (*listener).clone();
|
||||||
if addr
|
if addr
|
||||||
.set_host(Some(format!("[{}]", public_ipv6.to_string()).as_str()))
|
.set_host(Some(format!("[{}]", public_ipv6.to_string()).as_str()))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
data.clone(),
|
data.clone(),
|
||||||
dst_peer_id.clone(),
|
dst_peer_id.clone(),
|
||||||
addr.to_string(),
|
addr.to_string(),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?public_ipv6,
|
?public_ipv6,
|
||||||
?listener,
|
?listener,
|
||||||
?dst_peer_id,
|
?dst_peer_id,
|
||||||
"failed to set host for public ipv6"
|
"failed to set host for public ipv6"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||||
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
|
data.clone(),
|
||||||
|
dst_peer_id.clone(),
|
||||||
|
listener.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p => {
|
p => {
|
||||||
@@ -452,6 +477,49 @@ mod tests {
|
|||||||
proto::peer_rpc::GetIpListResponse,
|
proto::peer_rpc::GetIpListResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::TESTING;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn direct_connector_mapped_listener() {
|
||||||
|
TESTING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let p_a = create_mock_peer_manager().await;
|
||||||
|
let p_b = create_mock_peer_manager().await;
|
||||||
|
let p_c = create_mock_peer_manager().await;
|
||||||
|
let p_x = create_mock_peer_manager().await;
|
||||||
|
connect_peer_manager(p_a.clone(), p_b.clone()).await;
|
||||||
|
connect_peer_manager(p_b.clone(), p_c.clone()).await;
|
||||||
|
connect_peer_manager(p_c.clone(), p_x.clone()).await;
|
||||||
|
|
||||||
|
wait_route_appear(p_a.clone(), p_c.clone()).await.unwrap();
|
||||||
|
wait_route_appear(p_a.clone(), p_x.clone()).await.unwrap();
|
||||||
|
|
||||||
|
let mut f = p_a.get_global_ctx().get_flags();
|
||||||
|
f.bind_device = false;
|
||||||
|
p_a.get_global_ctx().config.set_flags(f);
|
||||||
|
|
||||||
|
p_c.get_global_ctx()
|
||||||
|
.config
|
||||||
|
.set_mapped_listeners(Some(vec!["tcp://127.0.0.1:11334".parse().unwrap()]));
|
||||||
|
|
||||||
|
p_x.get_global_ctx()
|
||||||
|
.config
|
||||||
|
.set_listeners(vec!["tcp://0.0.0.0:11334".parse().unwrap()]);
|
||||||
|
let mut lis_x = ListenerManager::new(p_x.get_global_ctx(), p_x.clone());
|
||||||
|
lis_x.prepare_listeners().await.unwrap();
|
||||||
|
lis_x.run().await.unwrap();
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
let mut dm_a = DirectConnectorManager::new(p_a.get_global_ctx(), p_a.clone());
|
||||||
|
let mut dm_c = DirectConnectorManager::new(p_c.get_global_ctx(), p_c.clone());
|
||||||
|
dm_a.run_as_client();
|
||||||
|
dm_c.run_as_server();
|
||||||
|
// p_c's mapped listener is p_x's listener, so p_a should connect to p_x directly
|
||||||
|
|
||||||
|
wait_route_appear_with_cost(p_a.clone(), p_x.my_peer_id(), Some(1))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest::rstest]
|
#[rstest::rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn direct_connector_basic_test(
|
async fn direct_connector_basic_test(
|
||||||
|
|||||||
@@ -297,12 +297,14 @@ impl ManualConnectorManager {
|
|||||||
|
|
||||||
connector.lock().await.set_ip_version(ip_version);
|
connector.lock().await.set_ip_version(ip_version);
|
||||||
|
|
||||||
set_bind_addr_for_peer_connector(
|
if data.global_ctx.config.get_flags().bind_device {
|
||||||
connector.lock().await.as_mut(),
|
set_bind_addr_for_peer_connector(
|
||||||
ip_version == IpVersion::V4,
|
connector.lock().await.as_mut(),
|
||||||
&ip_collector,
|
ip_version == IpVersion::V4,
|
||||||
)
|
&ip_collector,
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
||||||
connector.lock().await.remote_url().clone(),
|
connector.lock().await.remote_url().clone(),
|
||||||
|
|||||||
@@ -56,23 +56,27 @@ pub async fn create_connector_by_url(
|
|||||||
"tcp" => {
|
"tcp" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "tcp")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "tcp")?;
|
||||||
let mut connector = TcpTunnelConnector::new(url);
|
let mut connector = TcpTunnelConnector::new(url);
|
||||||
set_bind_addr_for_peer_connector(
|
if global_ctx.config.get_flags().bind_device {
|
||||||
&mut connector,
|
set_bind_addr_for_peer_connector(
|
||||||
dst_addr.is_ipv4(),
|
&mut connector,
|
||||||
&global_ctx.get_ip_collector(),
|
dst_addr.is_ipv4(),
|
||||||
)
|
&global_ctx.get_ip_collector(),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(Box::new(connector));
|
return Ok(Box::new(connector));
|
||||||
}
|
}
|
||||||
"udp" => {
|
"udp" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "udp")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "udp")?;
|
||||||
let mut connector = UdpTunnelConnector::new(url);
|
let mut connector = UdpTunnelConnector::new(url);
|
||||||
set_bind_addr_for_peer_connector(
|
if global_ctx.config.get_flags().bind_device {
|
||||||
&mut connector,
|
set_bind_addr_for_peer_connector(
|
||||||
dst_addr.is_ipv4(),
|
&mut connector,
|
||||||
&global_ctx.get_ip_collector(),
|
dst_addr.is_ipv4(),
|
||||||
)
|
&global_ctx.get_ip_collector(),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(Box::new(connector));
|
return Ok(Box::new(connector));
|
||||||
}
|
}
|
||||||
"ring" => {
|
"ring" => {
|
||||||
@@ -84,12 +88,14 @@ pub async fn create_connector_by_url(
|
|||||||
"quic" => {
|
"quic" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "quic")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "quic")?;
|
||||||
let mut connector = QUICTunnelConnector::new(url);
|
let mut connector = QUICTunnelConnector::new(url);
|
||||||
set_bind_addr_for_peer_connector(
|
if global_ctx.config.get_flags().bind_device {
|
||||||
&mut connector,
|
set_bind_addr_for_peer_connector(
|
||||||
dst_addr.is_ipv4(),
|
&mut connector,
|
||||||
&global_ctx.get_ip_collector(),
|
dst_addr.is_ipv4(),
|
||||||
)
|
&global_ctx.get_ip_collector(),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(Box::new(connector));
|
return Ok(Box::new(connector));
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wireguard")]
|
#[cfg(feature = "wireguard")]
|
||||||
@@ -101,12 +107,14 @@ pub async fn create_connector_by_url(
|
|||||||
&nid.network_secret.unwrap_or_default(),
|
&nid.network_secret.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
let mut connector = WgTunnelConnector::new(url, wg_config);
|
let mut connector = WgTunnelConnector::new(url, wg_config);
|
||||||
set_bind_addr_for_peer_connector(
|
if global_ctx.config.get_flags().bind_device {
|
||||||
&mut connector,
|
set_bind_addr_for_peer_connector(
|
||||||
dst_addr.is_ipv4(),
|
&mut connector,
|
||||||
&global_ctx.get_ip_collector(),
|
dst_addr.is_ipv4(),
|
||||||
)
|
&global_ctx.get_ip_collector(),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(Box::new(connector));
|
return Ok(Box::new(connector));
|
||||||
}
|
}
|
||||||
#[cfg(feature = "websocket")]
|
#[cfg(feature = "websocket")]
|
||||||
@@ -114,12 +122,14 @@ pub async fn create_connector_by_url(
|
|||||||
use crate::tunnel::{FromUrl, IpVersion};
|
use crate::tunnel::{FromUrl, IpVersion};
|
||||||
let dst_addr = SocketAddr::from_url(url.clone(), IpVersion::Both)?;
|
let dst_addr = SocketAddr::from_url(url.clone(), IpVersion::Both)?;
|
||||||
let mut connector = crate::tunnel::websocket::WSTunnelConnector::new(url);
|
let mut connector = crate::tunnel::websocket::WSTunnelConnector::new(url);
|
||||||
set_bind_addr_for_peer_connector(
|
if global_ctx.config.get_flags().bind_device {
|
||||||
&mut connector,
|
set_bind_addr_for_peer_connector(
|
||||||
dst_addr.is_ipv4(),
|
&mut connector,
|
||||||
&global_ctx.get_ip_collector(),
|
dst_addr.is_ipv4(),
|
||||||
)
|
&global_ctx.get_ip_collector(),
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(Box::new(connector));
|
return Ok(Box::new(connector));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ struct Cli {
|
|||||||
)]
|
)]
|
||||||
listeners: Vec<String>,
|
listeners: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = t!("core_clap.mapped_listeners").to_string(),
|
||||||
|
num_args = 0..
|
||||||
|
)]
|
||||||
|
mapped_listeners: Vec<String>,
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = t!("core_clap.no_listener").to_string(),
|
help = t!("core_clap.no_listener").to_string(),
|
||||||
@@ -185,7 +192,7 @@ struct Cli {
|
|||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = t!("core_clap.multi_thread").to_string(),
|
help = t!("core_clap.multi_thread").to_string(),
|
||||||
default_value = "false"
|
default_value = "true"
|
||||||
)]
|
)]
|
||||||
multi_thread: bool,
|
multi_thread: bool,
|
||||||
|
|
||||||
@@ -300,6 +307,12 @@ struct Cli {
|
|||||||
default_value = "none",
|
default_value = "none",
|
||||||
)]
|
)]
|
||||||
compression: String,
|
compression: String,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = t!("core_clap.bind_device").to_string()
|
||||||
|
)]
|
||||||
|
bind_device: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
rust_i18n::i18n!("locales", fallback = "en");
|
rust_i18n::i18n!("locales", fallback = "en");
|
||||||
@@ -422,6 +435,23 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
|||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cfg.set_mapped_listeners(Some(
|
||||||
|
cli.mapped_listeners
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
s.parse()
|
||||||
|
.with_context(|| format!("mapped listener is not a valid url: {}", s))
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.map(|s: url::Url| {
|
||||||
|
if s.port().is_none() {
|
||||||
|
panic!("mapped listener port is missing: {}", s);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
));
|
||||||
|
|
||||||
for n in cli.proxy_networks.iter() {
|
for n in cli.proxy_networks.iter() {
|
||||||
cfg.add_proxy_cidr(
|
cfg.add_proxy_cidr(
|
||||||
n.parse()
|
n.parse()
|
||||||
@@ -534,6 +564,9 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
.into();
|
.into();
|
||||||
|
if let Some(bind_device) = cli.bind_device {
|
||||||
|
f.bind_device = bind_device;
|
||||||
|
}
|
||||||
cfg.set_flags(f);
|
cfg.set_flags(f);
|
||||||
|
|
||||||
cfg.set_exit_nodes(cli.exit_nodes.clone());
|
cfg.set_exit_nodes(cli.exit_nodes.clone());
|
||||||
@@ -828,7 +861,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||||
rust_i18n::set_locale(&locale);
|
rust_i18n::set_locale(&locale);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
use cidr::Ipv4Inet;
|
use cidr::Ipv4Inet;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
@@ -24,11 +25,11 @@ use tokio::{
|
|||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{error::Error, global_ctx::ArcGlobalCtx, PeerId},
|
common::{error::Error, global_ctx::ArcGlobalCtx, scoped_task::ScopedTask, PeerId},
|
||||||
gateway::ip_reassembler::compose_ipv4_packet,
|
gateway::ip_reassembler::compose_ipv4_packet,
|
||||||
peers::{peer_manager::PeerManager, PeerPacketFilter},
|
peers::{peer_manager::PeerManager, PeerPacketFilter},
|
||||||
tunnel::{
|
tunnel::{
|
||||||
common::setup_sokcet2,
|
common::{reserve_buf, setup_sokcet2},
|
||||||
packet_def::{PacketType, ZCPacket},
|
packet_def::{PacketType, ZCPacket},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -139,59 +140,81 @@ impl UdpNatEntry {
|
|||||||
mut packet_sender: Sender<ZCPacket>,
|
mut packet_sender: Sender<ZCPacket>,
|
||||||
virtual_ipv4: Ipv4Addr,
|
virtual_ipv4: Ipv4Addr,
|
||||||
) {
|
) {
|
||||||
let mut buf = [0u8; 65536];
|
let (s, mut r) = tachyonix::channel(128);
|
||||||
let mut udp_body: &mut [u8] = unsafe { std::mem::transmute(&mut buf[20 + 8..]) };
|
|
||||||
let mut ip_id = 1;
|
|
||||||
|
|
||||||
loop {
|
let self_clone = self.clone();
|
||||||
let (len, src_socket) = match timeout(
|
let recv_task = ScopedTask::from(tokio::spawn(async move {
|
||||||
Duration::from_secs(120),
|
let mut cur_buf = BytesMut::new();
|
||||||
self.socket.recv_from(&mut udp_body),
|
loop {
|
||||||
)
|
if self_clone
|
||||||
.await
|
.stopped
|
||||||
{
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
Ok(Ok(x)) => x,
|
{
|
||||||
Ok(Err(err)) => {
|
|
||||||
tracing::error!(?err, "udp nat recv failed");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
|
||||||
tracing::error!(?err, "udp nat recv timeout");
|
reserve_buf(&mut cur_buf, 64 * 1024 + 28, 128 * 1024 + 28);
|
||||||
break;
|
assert_eq!(cur_buf.len(), 0);
|
||||||
|
unsafe {
|
||||||
|
cur_buf.advance_mut(28);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
tracing::trace!(?len, ?src_socket, "udp nat packet response received");
|
let (len, src_socket) = match timeout(
|
||||||
|
Duration::from_secs(120),
|
||||||
|
self_clone.socket.recv_buf_from(&mut cur_buf),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(x)) => x,
|
||||||
|
Ok(Err(err)) => {
|
||||||
|
tracing::error!(?err, "udp nat recv failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!(?err, "udp nat recv timeout");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if self.stopped.load(std::sync::atomic::Ordering::Relaxed) {
|
tracing::trace!(?len, ?src_socket, "udp nat packet response received");
|
||||||
break;
|
|
||||||
|
let ret_buf = cur_buf.split();
|
||||||
|
s.send((ret_buf, len, src_socket)).await.unwrap();
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
let SocketAddr::V4(mut src_v4) = src_socket else {
|
let self_clone = self.clone();
|
||||||
continue;
|
let send_task = ScopedTask::from(tokio::spawn(async move {
|
||||||
};
|
let mut ip_id = 1;
|
||||||
|
while let Ok((mut packet, len, src_socket)) = r.recv().await {
|
||||||
|
let SocketAddr::V4(mut src_v4) = src_socket else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
self.mark_active();
|
self_clone.mark_active();
|
||||||
|
|
||||||
if src_v4.ip().is_loopback() {
|
if src_v4.ip().is_loopback() {
|
||||||
src_v4.set_ip(virtual_ipv4);
|
src_v4.set_ip(virtual_ipv4);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(_) = Self::compose_ipv4_packet(
|
||||||
|
&self_clone,
|
||||||
|
&mut packet_sender,
|
||||||
|
&mut packet,
|
||||||
|
&src_v4,
|
||||||
|
len,
|
||||||
|
1280,
|
||||||
|
ip_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
ip_id = ip_id.wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
let Ok(_) = Self::compose_ipv4_packet(
|
let _ = tokio::join!(recv_task, send_task);
|
||||||
&self,
|
|
||||||
&mut packet_sender,
|
|
||||||
&mut buf,
|
|
||||||
&src_v4,
|
|
||||||
len,
|
|
||||||
1200,
|
|
||||||
ip_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
ip_id = ip_id.wrapping_add(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.stop();
|
self.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::{fmt::Debug, sync::Arc};
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tokio::{sync::Mutex, task::JoinSet};
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
use crate::tunnel::quic::QUICTunnelListener;
|
use crate::tunnel::quic::QUICTunnelListener;
|
||||||
@@ -63,16 +62,20 @@ impl TunnelHandlerForListener for PeerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub trait ListenerCreatorTrait: Fn() -> Box<dyn TunnelListener> + Send + Sync {}
|
||||||
struct Listener {
|
impl<T: Send + Sync> ListenerCreatorTrait for T where T: Fn() -> Box<dyn TunnelListener> + Send {}
|
||||||
inner: Arc<Mutex<dyn TunnelListener>>,
|
pub type ListenerCreator = Box<dyn ListenerCreatorTrait>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ListenerFactory {
|
||||||
|
creator_fn: Arc<ListenerCreator>,
|
||||||
must_succ: bool,
|
must_succ: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ListenerManager<H> {
|
pub struct ListenerManager<H> {
|
||||||
global_ctx: ArcGlobalCtx,
|
global_ctx: ArcGlobalCtx,
|
||||||
net_ns: NetNS,
|
net_ns: NetNS,
|
||||||
listeners: Vec<Listener>,
|
listeners: Vec<ListenerFactory>,
|
||||||
peer_manager: Arc<H>,
|
peer_manager: Arc<H>,
|
||||||
|
|
||||||
tasks: JoinSet<()>,
|
tasks: JoinSet<()>,
|
||||||
@@ -90,31 +93,39 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prepare_listeners(&mut self) -> Result<(), Error> {
|
pub async fn prepare_listeners(&mut self) -> Result<(), Error> {
|
||||||
|
let self_id = self.global_ctx.get_id();
|
||||||
self.add_listener(
|
self.add_listener(
|
||||||
RingTunnelListener::new(
|
move || {
|
||||||
format!("ring://{}", self.global_ctx.get_id())
|
Box::new(RingTunnelListener::new(
|
||||||
.parse()
|
format!("ring://{}", self_id).parse().unwrap(),
|
||||||
.unwrap(),
|
))
|
||||||
),
|
},
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for l in self.global_ctx.config.get_listener_uris().iter() {
|
for l in self.global_ctx.config.get_listener_uris().iter() {
|
||||||
let Ok(lis) = get_listener_by_url(l, self.global_ctx.clone()) else {
|
let l = l.clone();
|
||||||
|
let Ok(_) = get_listener_by_url(&l, self.global_ctx.clone()) else {
|
||||||
let msg = format!("failed to get listener by url: {}, maybe not supported", l);
|
let msg = format!("failed to get listener by url: {}, maybe not supported", l);
|
||||||
self.global_ctx
|
self.global_ctx
|
||||||
.issue_event(GlobalCtxEvent::ListenerAddFailed(l.clone(), msg));
|
.issue_event(GlobalCtxEvent::ListenerAddFailed(l.clone(), msg));
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
self.add_listener(lis, true).await?;
|
let ctx = self.global_ctx.clone();
|
||||||
|
self.add_listener(move || get_listener_by_url(&l, ctx.clone()).unwrap(), true)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.global_ctx.config.get_flags().enable_ipv6 {
|
if self.global_ctx.config.get_flags().enable_ipv6 {
|
||||||
let ipv6_listener = self.global_ctx.config.get_flags().ipv6_listener.clone();
|
let ipv6_listener = self.global_ctx.config.get_flags().ipv6_listener.clone();
|
||||||
let _ = self
|
let _ = self
|
||||||
.add_listener(
|
.add_listener(
|
||||||
UdpTunnelListener::new(ipv6_listener.parse().unwrap()),
|
move || {
|
||||||
|
Box::new(UdpTunnelListener::new(
|
||||||
|
ipv6_listener.clone().parse().unwrap(),
|
||||||
|
))
|
||||||
|
},
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -123,85 +134,91 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_listener<L>(&mut self, listener: L, must_succ: bool) -> Result<(), Error>
|
pub async fn add_listener<C: ListenerCreatorTrait + 'static>(
|
||||||
where
|
&mut self,
|
||||||
L: TunnelListener + 'static,
|
creator: C,
|
||||||
{
|
must_succ: bool,
|
||||||
let listener = Arc::new(Mutex::new(listener));
|
) -> Result<(), Error> {
|
||||||
self.listeners.push(Listener {
|
self.listeners.push(ListenerFactory {
|
||||||
inner: listener,
|
creator_fn: Arc::new(Box::new(creator)),
|
||||||
must_succ,
|
must_succ,
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip(creator))]
|
||||||
async fn run_listener(
|
async fn run_listener(
|
||||||
listener: Arc<Mutex<dyn TunnelListener>>,
|
creator: Arc<ListenerCreator>,
|
||||||
peer_manager: Arc<H>,
|
peer_manager: Arc<H>,
|
||||||
global_ctx: ArcGlobalCtx,
|
global_ctx: ArcGlobalCtx,
|
||||||
) {
|
) {
|
||||||
let mut l = listener.lock().await;
|
|
||||||
global_ctx.add_running_listener(l.local_url());
|
|
||||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAdded(l.local_url()));
|
|
||||||
loop {
|
loop {
|
||||||
let ret = match l.accept().await {
|
let mut l = (creator)();
|
||||||
Ok(ret) => ret,
|
let _g = global_ctx.net_ns.guard();
|
||||||
|
match l.listen().await {
|
||||||
|
Ok(_) => {
|
||||||
|
global_ctx.add_running_listener(l.local_url());
|
||||||
|
global_ctx.issue_event(GlobalCtxEvent::ListenerAdded(l.local_url()));
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAcceptFailed(
|
global_ctx.issue_event(GlobalCtxEvent::ListenerAddFailed(
|
||||||
l.local_url(),
|
l.local_url(),
|
||||||
e.to_string(),
|
format!("error: {:?}, retry listen later...", e),
|
||||||
));
|
));
|
||||||
tracing::error!(?e, ?l, "listener accept error");
|
tracing::error!(?e, ?l, "listener listen error");
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
loop {
|
||||||
|
let ret = match l.accept().await {
|
||||||
|
Ok(ret) => ret,
|
||||||
|
Err(e) => {
|
||||||
|
global_ctx.issue_event(GlobalCtxEvent::ListenerAcceptFailed(
|
||||||
|
l.local_url(),
|
||||||
|
format!("error: {:?}, retry listen later...", e),
|
||||||
|
));
|
||||||
|
tracing::error!(?e, ?l, "listener accept error");
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let tunnel_info = ret.info().unwrap();
|
let tunnel_info = ret.info().unwrap();
|
||||||
global_ctx.issue_event(GlobalCtxEvent::ConnectionAccepted(
|
global_ctx.issue_event(GlobalCtxEvent::ConnectionAccepted(
|
||||||
tunnel_info
|
tunnel_info
|
||||||
.local_addr
|
.local_addr
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
tunnel_info
|
tunnel_info
|
||||||
.remote_addr
|
.remote_addr
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
tracing::info!(ret = ?ret, "conn accepted");
|
tracing::info!(ret = ?ret, "conn accepted");
|
||||||
let peer_manager = peer_manager.clone();
|
let peer_manager = peer_manager.clone();
|
||||||
let global_ctx = global_ctx.clone();
|
let global_ctx = global_ctx.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let server_ret = peer_manager.handle_tunnel(ret).await;
|
let server_ret = peer_manager.handle_tunnel(ret).await;
|
||||||
if let Err(e) = &server_ret {
|
if let Err(e) = &server_ret {
|
||||||
global_ctx.issue_event(GlobalCtxEvent::ConnectionError(
|
global_ctx.issue_event(GlobalCtxEvent::ConnectionError(
|
||||||
tunnel_info.local_addr.unwrap_or_default().to_string(),
|
tunnel_info.local_addr.unwrap_or_default().to_string(),
|
||||||
tunnel_info.remote_addr.unwrap_or_default().to_string(),
|
tunnel_info.remote_addr.unwrap_or_default().to_string(),
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
));
|
));
|
||||||
tracing::error!(error = ?e, "handle conn error");
|
tracing::error!(error = ?e, "handle conn error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<(), Error> {
|
pub async fn run(&mut self) -> Result<(), Error> {
|
||||||
for listener in &self.listeners {
|
for listener in &self.listeners {
|
||||||
let _guard = self.net_ns.guard();
|
|
||||||
let addr = listener.inner.lock().await.local_url();
|
|
||||||
tracing::warn!("run listener: {:?}", listener);
|
|
||||||
listener
|
|
||||||
.inner
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.listen()
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("failed to add listener {}", addr))?;
|
|
||||||
self.tasks.spawn(Self::run_listener(
|
self.tasks.spawn(Self::run_listener(
|
||||||
listener.inner.clone(),
|
listener.creator_fn.clone(),
|
||||||
self.peer_manager.clone(),
|
self.peer_manager.clone(),
|
||||||
self.global_ctx.clone(),
|
self.global_ctx.clone(),
|
||||||
));
|
));
|
||||||
@@ -213,12 +230,14 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
|
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::global_ctx::tests::get_mock_global_ctx,
|
common::global_ctx::tests::get_mock_global_ctx,
|
||||||
tunnel::{packet_def::ZCPacket, ring::RingTunnelConnector, TunnelConnector},
|
tunnel::{packet_def::ZCPacket, ring::RingTunnelConnector, TunnelConnector, TunnelError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -245,12 +264,18 @@ mod tests {
|
|||||||
|
|
||||||
let ring_id = format!("ring://{}", uuid::Uuid::new_v4());
|
let ring_id = format!("ring://{}", uuid::Uuid::new_v4());
|
||||||
|
|
||||||
|
let ring_id_clone = ring_id.clone();
|
||||||
listener_mgr
|
listener_mgr
|
||||||
.add_listener(RingTunnelListener::new(ring_id.parse().unwrap()), true)
|
.add_listener(
|
||||||
|
move || Box::new(RingTunnelListener::new(ring_id_clone.parse().unwrap())),
|
||||||
|
true,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
listener_mgr.run().await.unwrap();
|
listener_mgr.run().await.unwrap();
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
|
||||||
let connect_once = |ring_id| async move {
|
let connect_once = |ring_id| async move {
|
||||||
let tunnel = RingTunnelConnector::new(ring_id).connect().await.unwrap();
|
let tunnel = RingTunnelConnector::new(ring_id).connect().await.unwrap();
|
||||||
let (mut recv, _send) = tunnel.split();
|
let (mut recv, _send) = tunnel.split();
|
||||||
@@ -269,4 +294,60 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn retry_listen() {
|
||||||
|
let counter = Arc::new(AtomicI32::new(0));
|
||||||
|
let drop_counter = Arc::new(AtomicI32::new(0));
|
||||||
|
struct MockListener {
|
||||||
|
counter: Arc<AtomicI32>,
|
||||||
|
drop_counter: Arc<AtomicI32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl TunnelListener for MockListener {
|
||||||
|
fn local_url(&self) -> url::Url {
|
||||||
|
"mock://".parse().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn listen(&mut self) -> Result<(), TunnelError> {
|
||||||
|
self.counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn accept(&mut self) -> Result<Box<dyn Tunnel>, TunnelError> {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
Err(TunnelError::BufferFull)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MockListener {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.drop_counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let handler = Arc::new(MockListenerHandler {});
|
||||||
|
let mut listener_mgr = ListenerManager::new(get_mock_global_ctx(), handler.clone());
|
||||||
|
let counter_clone = counter.clone();
|
||||||
|
let drop_counter_clone = drop_counter.clone();
|
||||||
|
listener_mgr
|
||||||
|
.add_listener(
|
||||||
|
move || {
|
||||||
|
Box::new(MockListener {
|
||||||
|
counter: counter_clone.clone(),
|
||||||
|
drop_counter: drop_counter_clone.clone(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
listener_mgr.run().await.unwrap();
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
assert!(counter.load(Ordering::Relaxed) >= 2);
|
||||||
|
assert!(drop_counter.load(Ordering::Relaxed) >= 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -484,13 +484,6 @@ mod tests {
|
|||||||
let c_recorder = Arc::new(DropSendTunnelFilter::new(drop_start, drop_end));
|
let c_recorder = Arc::new(DropSendTunnelFilter::new(drop_start, drop_end));
|
||||||
let c = TunnelWithFilter::new(c, c_recorder.clone());
|
let c = TunnelWithFilter::new(c, c_recorder.clone());
|
||||||
|
|
||||||
let s = if drop_both {
|
|
||||||
let s_recorder = Arc::new(DropSendTunnelFilter::new(drop_start, drop_end));
|
|
||||||
Box::new(TunnelWithFilter::new(s, s_recorder.clone()))
|
|
||||||
} else {
|
|
||||||
s
|
|
||||||
};
|
|
||||||
|
|
||||||
let c_peer_id = new_peer_id();
|
let c_peer_id = new_peer_id();
|
||||||
let s_peer_id = new_peer_id();
|
let s_peer_id = new_peer_id();
|
||||||
|
|
||||||
@@ -506,6 +499,7 @@ mod tests {
|
|||||||
s_peer
|
s_peer
|
||||||
.start_recv_loop(tokio::sync::mpsc::channel(200).0)
|
.start_recv_loop(tokio::sync::mpsc::channel(200).0)
|
||||||
.await;
|
.await;
|
||||||
|
// do not start ping for s, s only reponde to ping from c
|
||||||
|
|
||||||
assert!(c_ret.is_ok());
|
assert!(c_ret.is_ok());
|
||||||
assert!(s_ret.is_ok());
|
assert!(s_ret.is_ok());
|
||||||
@@ -547,7 +541,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn peer_conn_pingpong_bothside_timeout() {
|
async fn peer_conn_pingpong_bothside_timeout() {
|
||||||
peer_conn_pingpong_test_common(4, 12, true, true).await;
|
peer_conn_pingpong_test_common(3, 14, true, true).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -289,29 +289,29 @@ impl PeerConnPinger {
|
|||||||
"pingpong task recv pingpong_once result"
|
"pingpong task recv pingpong_once result"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (counter > 5 && loss_rate_20 > 0.74) || (counter > 150 && loss_rate_1 > 0.20) {
|
let current_rx_packets = throughput.rx_packets();
|
||||||
let current_rx_packets = throughput.rx_packets();
|
if last_rx_packets != current_rx_packets {
|
||||||
let need_close = if last_rx_packets != current_rx_packets {
|
// if we receive some packet from peers, reset the counter to avoid conn close.
|
||||||
// if we receive some packet from peers, we should relax the condition
|
// conn will close only if we have 5 continous round pingpong loss after no packet received.
|
||||||
counter > 50 && loss_rate_1 > 0.5
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: wait more time to see if the loss rate is still high after no rx
|
tracing::debug!(
|
||||||
} else {
|
"counter: {}, loss_rate_1: {}, loss_rate_20: {}, cur_rx_packets: {}, last_rx: {}, node_id: {}",
|
||||||
true
|
counter, loss_rate_1, loss_rate_20, current_rx_packets, last_rx_packets, my_node_id
|
||||||
};
|
);
|
||||||
|
|
||||||
if need_close {
|
if (counter > 5 && loss_rate_20 > 0.74) || (counter > 100 && loss_rate_1 > 0.35) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
?ret,
|
?ret,
|
||||||
?self,
|
?self,
|
||||||
?loss_rate_1,
|
?loss_rate_1,
|
||||||
?loss_rate_20,
|
?loss_rate_20,
|
||||||
?last_rx_packets,
|
?last_rx_packets,
|
||||||
?current_rx_packets,
|
?current_rx_packets,
|
||||||
"pingpong loss rate too high, closing"
|
"pingpong loss rate too high, closing"
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
last_rx_packets = throughput.rx_packets();
|
last_rx_packets = throughput.rx_packets();
|
||||||
|
|||||||
@@ -1739,7 +1739,6 @@ impl RouteSessionManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let _ = self.stop_session(*peer_id);
|
let _ = self.stop_session(*peer_id);
|
||||||
assert_ne!(Some(*peer_id), cur_dst_peer_id_to_initiate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ impl DirectConnectorRpc for DirectConnectorManagerRpcServer {
|
|||||||
let mut ret = self.global_ctx.get_ip_collector().collect_ip_addrs().await;
|
let mut ret = self.global_ctx.get_ip_collector().collect_ip_addrs().await;
|
||||||
ret.listeners = self
|
ret.listeners = self
|
||||||
.global_ctx
|
.global_ctx
|
||||||
.get_running_listeners()
|
.config
|
||||||
|
.get_mapped_listeners()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.chain(self.global_ctx.get_running_listeners().into_iter())
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect();
|
.collect();
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ message FlagsInConfig {
|
|||||||
string ipv6_listener = 14;
|
string ipv6_listener = 14;
|
||||||
bool multi_thread = 15;
|
bool multi_thread = 15;
|
||||||
CompressionAlgoPb data_compress_algo = 16;
|
CompressionAlgoPb data_compress_algo = 16;
|
||||||
|
bool bind_device = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcDescriptor {
|
message RpcDescriptor {
|
||||||
|
|||||||
@@ -159,7 +159,10 @@ pub fn build_rpc_packet(
|
|||||||
let cur_packet = RpcPacket {
|
let cur_packet = RpcPacket {
|
||||||
from_peer,
|
from_peer,
|
||||||
to_peer,
|
to_peer,
|
||||||
descriptor: if cur_offset == 0 {
|
descriptor: if cur_offset == 0
|
||||||
|
|| compression_info.algo == CompressionAlgoPb::None as i32
|
||||||
|
{
|
||||||
|
// old version must have descriptor on every piece
|
||||||
Some(rpc_desc.clone())
|
Some(rpc_desc.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -38,6 +38,33 @@ impl<L: TunnelListener + 'static> StandAloneServer<L> {
|
|||||||
&self.registry
|
&self.registry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn serve_loop(
|
||||||
|
listener: &mut L,
|
||||||
|
inflight: Arc<AtomicU32>,
|
||||||
|
registry: Arc<ServiceRegistry>,
|
||||||
|
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
listener
|
||||||
|
.listen()
|
||||||
|
.await
|
||||||
|
.with_context(|| "failed to listen")?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let tunnel = listener.accept().await?;
|
||||||
|
let registry = registry.clone();
|
||||||
|
let inflight_server = inflight.clone();
|
||||||
|
inflight_server.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
tasks.lock().unwrap().spawn(async move {
|
||||||
|
let server =
|
||||||
|
BidirectRpcManager::new().set_rx_timeout(Some(Duration::from_secs(60)));
|
||||||
|
server.rpc_server().registry().replace_registry(®istry);
|
||||||
|
server.run_with_tunnel(tunnel);
|
||||||
|
server.wait().await;
|
||||||
|
inflight_server.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn serve(&mut self) -> Result<(), Error> {
|
pub async fn serve(&mut self) -> Result<(), Error> {
|
||||||
let tasks = self.tasks.clone();
|
let tasks = self.tasks.clone();
|
||||||
let mut listener = self.listener.take().unwrap();
|
let mut listener = self.listener.take().unwrap();
|
||||||
@@ -45,28 +72,24 @@ impl<L: TunnelListener + 'static> StandAloneServer<L> {
|
|||||||
|
|
||||||
join_joinset_background(tasks.clone(), "standalone server tasks".to_string());
|
join_joinset_background(tasks.clone(), "standalone server tasks".to_string());
|
||||||
|
|
||||||
listener
|
|
||||||
.listen()
|
|
||||||
.await
|
|
||||||
.with_context(|| "failed to listen")?;
|
|
||||||
|
|
||||||
let inflight_server = self.inflight_server.clone();
|
let inflight_server = self.inflight_server.clone();
|
||||||
|
|
||||||
self.tasks.lock().unwrap().spawn(async move {
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
while let Ok(tunnel) = listener.accept().await {
|
loop {
|
||||||
let registry = registry.clone();
|
let ret = Self::serve_loop(
|
||||||
let inflight_server = inflight_server.clone();
|
&mut listener,
|
||||||
inflight_server.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
inflight_server.clone(),
|
||||||
tasks.lock().unwrap().spawn(async move {
|
registry.clone(),
|
||||||
let server =
|
tasks.clone(),
|
||||||
BidirectRpcManager::new().set_rx_timeout(Some(Duration::from_secs(60)));
|
)
|
||||||
server.rpc_server().registry().replace_registry(®istry);
|
.await;
|
||||||
server.run_with_tunnel(tunnel);
|
if let Err(e) = ret {
|
||||||
server.wait().await;
|
tracing::error!(?e, url = ?listener.local_url(), "serve_loop exit unexpectedly");
|
||||||
inflight_server.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
|
println!("standalone serve_loop exit unexpectedly: {:?}", e);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
panic!("standalone server listener exit");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -287,6 +287,8 @@ async fn standalone_rpc_test() {
|
|||||||
server.registry().register(service, "test");
|
server.registry().register(service, "test");
|
||||||
server.serve().await.unwrap();
|
server.serve().await.unwrap();
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
|
||||||
let mut client = StandAloneClient::new(TcpTunnelConnector::new(
|
let mut client = StandAloneClient::new(TcpTunnelConnector::new(
|
||||||
"tcp://127.0.0.1:33455".parse().unwrap(),
|
"tcp://127.0.0.1:33455".parse().unwrap(),
|
||||||
));
|
));
|
||||||
|
|||||||
+41
-20
@@ -28,11 +28,36 @@ impl TcpTunnelListener {
|
|||||||
listener: None,
|
listener: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn do_accept(&mut self) -> Result<Box<dyn Tunnel>, std::io::Error> {
|
||||||
|
let listener = self.listener.as_ref().unwrap();
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
|
||||||
|
if let Err(e) = stream.set_nodelay(true) {
|
||||||
|
tracing::warn!(?e, "set_nodelay fail in accept");
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = TunnelInfo {
|
||||||
|
tunnel_type: "tcp".to_owned(),
|
||||||
|
local_addr: Some(self.local_url().into()),
|
||||||
|
remote_addr: Some(
|
||||||
|
super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (r, w) = stream.into_split();
|
||||||
|
Ok(Box::new(TunnelWrapper::new(
|
||||||
|
FramedReader::new(r, TCP_MTU_BYTES),
|
||||||
|
FramedWriter::new(w),
|
||||||
|
Some(info),
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TunnelListener for TcpTunnelListener {
|
impl TunnelListener for TcpTunnelListener {
|
||||||
async fn listen(&mut self) -> Result<(), TunnelError> {
|
async fn listen(&mut self) -> Result<(), TunnelError> {
|
||||||
|
self.listener = None;
|
||||||
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
||||||
|
|
||||||
let socket2_socket = socket2::Socket::new(
|
let socket2_socket = socket2::Socket::new(
|
||||||
@@ -56,27 +81,23 @@ impl TunnelListener for TcpTunnelListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn accept(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
async fn accept(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||||
let listener = self.listener.as_ref().unwrap();
|
loop {
|
||||||
let (stream, _) = listener.accept().await?;
|
match self.do_accept().await {
|
||||||
|
Ok(ret) => return Ok(ret),
|
||||||
if let Err(e) = stream.set_nodelay(true) {
|
Err(e) => {
|
||||||
tracing::warn!(?e, "set_nodelay fail in accept");
|
use std::io::ErrorKind::*;
|
||||||
|
if matches!(
|
||||||
|
e.kind(),
|
||||||
|
NotConnected | ConnectionAborted | ConnectionRefused | ConnectionReset
|
||||||
|
) {
|
||||||
|
tracing::warn!(?e, "accept fail with retryable error: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tracing::warn!(?e, "accept fail");
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = TunnelInfo {
|
|
||||||
tunnel_type: "tcp".to_owned(),
|
|
||||||
local_addr: Some(self.local_url().into()),
|
|
||||||
remote_addr: Some(
|
|
||||||
super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (r, w) = stream.into_split();
|
|
||||||
Ok(Box::new(TunnelWrapper::new(
|
|
||||||
FramedReader::new(r, TCP_MTU_BYTES),
|
|
||||||
FramedWriter::new(w),
|
|
||||||
Some(info),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_url(&self) -> url::Url {
|
fn local_url(&self) -> url::Url {
|
||||||
|
|||||||
@@ -139,12 +139,17 @@ pub fn setup_panic_handler() {
|
|||||||
|
|
||||||
if let Some(payload_str) = payload_str {
|
if let Some(payload_str) = payload_str {
|
||||||
println!(
|
println!(
|
||||||
"panic occurred: payload:{}, location: {:?}",
|
"panic occurred: payload:{}, location: {:?}, backtrace: {:#?}",
|
||||||
payload_str,
|
payload_str,
|
||||||
info.location()
|
info.location(),
|
||||||
|
backtrace
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("panic occurred: location: {:?}", info.location());
|
println!(
|
||||||
|
"panic occurred: location: {:?}, backtrace: {:#?}",
|
||||||
|
info.location(),
|
||||||
|
backtrace
|
||||||
|
);
|
||||||
}
|
}
|
||||||
println!("{}", rust_i18n::t!("core_app.panic_backtrace_save"));
|
println!("{}", rust_i18n::t!("core_app.panic_backtrace_save"));
|
||||||
let _ = std::fs::File::create("easytier-panic.log")
|
let _ = std::fs::File::create("easytier-panic.log")
|
||||||
@@ -168,4 +173,4 @@ mod tests {
|
|||||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
tracing::debug!("test display debug");
|
tracing::debug!("test display debug");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+36
-125
@@ -69,6 +69,9 @@ importers:
|
|||||||
'@tauri-apps/cli':
|
'@tauri-apps/cli':
|
||||||
specifier: 2.1.0
|
specifier: 2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
'@types/default-gateway':
|
||||||
|
specifier: ^7.2.2
|
||||||
|
version: 7.2.2
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.7.4
|
specifier: ^22.7.4
|
||||||
version: 22.8.6
|
version: 22.8.6
|
||||||
@@ -84,15 +87,18 @@ importers:
|
|||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.20
|
specifier: ^10.4.20
|
||||||
version: 10.4.20(postcss@8.4.47)
|
version: 10.4.20(postcss@8.4.47)
|
||||||
|
cidr-tools:
|
||||||
|
specifier: ^11.0.2
|
||||||
|
version: 11.0.2
|
||||||
|
default-gateway:
|
||||||
|
specifier: ^7.2.2
|
||||||
|
version: 7.2.2
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.12.0
|
specifier: ^9.12.0
|
||||||
version: 9.14.0(jiti@2.4.0)
|
version: 9.14.0(jiti@2.4.0)
|
||||||
eslint-plugin-format:
|
eslint-plugin-format:
|
||||||
specifier: ^0.1.2
|
specifier: ^0.1.2
|
||||||
version: 0.1.2(eslint@9.14.0(jiti@2.4.0))
|
version: 0.1.2(eslint@9.14.0(jiti@2.4.0))
|
||||||
internal-ip:
|
|
||||||
specifier: ^8.0.0
|
|
||||||
version: 8.0.0
|
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.47
|
specifier: ^8.4.47
|
||||||
version: 8.4.47
|
version: 8.4.47
|
||||||
@@ -905,16 +911,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==}
|
resolution: {integrity: sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
'@intlify/message-compiler@11.0.0-beta.1':
|
'@intlify/message-compiler@11.0.0-rc.1':
|
||||||
resolution: {integrity: sha512-yMXfN4hg/EeSdtWfmoMrwB9X4TXwkBoZlTIpNydQaW9y0tSJHGnUPRoahtkbsyACCm9leSJINLY4jQ0rK6BK0Q==}
|
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
'@intlify/shared@10.0.4':
|
'@intlify/shared@10.0.4':
|
||||||
resolution: {integrity: sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==}
|
resolution: {integrity: sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
'@intlify/shared@11.0.0-beta.1':
|
'@intlify/shared@11.0.0-rc.1':
|
||||||
resolution: {integrity: sha512-Md/4T/QOx7wZ7zqVzSsMx2M/9Mx/1nsgsjXS5SFIowFKydqUhMz7K+y7pMFh781aNYz+rGXYwad8E9/+InK9SA==}
|
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
'@intlify/unplugin-vue-i18n@5.2.0':
|
'@intlify/unplugin-vue-i18n@5.2.0':
|
||||||
@@ -1353,6 +1359,9 @@ packages:
|
|||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||||
|
|
||||||
|
'@types/default-gateway@7.2.2':
|
||||||
|
resolution: {integrity: sha512-35C93fYQlnLKLASkMPoxRvok4fENwB3By9clRLd2I/08n/XRl0pCdf7EB17K5oMMwZu8NBYA8i66jH5r/LYBKA==}
|
||||||
|
|
||||||
'@types/estree@1.0.6':
|
'@types/estree@1.0.6':
|
||||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||||
|
|
||||||
@@ -1916,13 +1925,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==}
|
resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
cidr-regex@4.0.3:
|
cidr-tools@11.0.2:
|
||||||
resolution: {integrity: sha512-HOwDIy/rhKeMf6uOzxtv7FAbrz8zPjmVKfSpM+U7/bNBXC5rtOyr758jxcptiSx6ZZn5LOhPJT5WWxPAGDV8dw==}
|
resolution: {integrity: sha512-OLeM9EOXybbhMsGGBNRLCMjn8e+wFOXARIShF/sZwmJLsxWywqfE0By4BMftT6BFWpbcETWpW7TfM2KGCtrZDg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
cidr-tools@6.4.2:
|
|
||||||
resolution: {integrity: sha512-KZC8t2ipCqU2M+ISmTxRDGu9bku5MRU3V1cWyGEFJTZEzRhGvBJvVsbpZO5UAu12fExRFihtYGXAlgFFpmK9jw==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
|
|
||||||
clean-regexp@1.0.0:
|
clean-regexp@1.0.0:
|
||||||
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
||||||
@@ -1932,10 +1937,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
clone-regexp@3.0.0:
|
|
||||||
resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
@@ -1967,10 +1968,6 @@ packages:
|
|||||||
confbox@0.1.8:
|
confbox@0.1.8:
|
||||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||||
|
|
||||||
convert-hrtime@5.0.0:
|
|
||||||
resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@@ -2436,10 +2433,6 @@ packages:
|
|||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
function-timeout@0.1.1:
|
|
||||||
resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==}
|
|
||||||
engines: {node: '>=14.16'}
|
|
||||||
|
|
||||||
gensync@1.0.0-beta.2:
|
gensync@1.0.0-beta.2:
|
||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -2554,21 +2547,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
internal-ip@8.0.0:
|
ip-bigint@8.2.0:
|
||||||
resolution: {integrity: sha512-e6c3zxr9COnnc29PIz9LffmALOt0XhIJdR7f83DyHcQksL3B40KGmU3Sr1lrHja3i7Zyqo+AbwKZ+nZiMvg/OA==}
|
resolution: {integrity: sha512-46EAEKzGNxojH5JaGEeCix49tL4h1W8ia5mhogZ68HroVAfyLj1E+SFFid4GuyK0mdIKjwcAITLqwg1wlkx2iQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
ip-bigint@7.3.0:
|
|
||||||
resolution: {integrity: sha512-2qVAe0Q9+Y+5nGvmogwK9y4kefD5Ks5l/IG0Jo1lhU9gIF34jifhqrwXwzkIl+LC594Q6SyAlngs4p890xsXVw==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
|
|
||||||
ip-num@1.5.1:
|
ip-num@1.5.1:
|
||||||
resolution: {integrity: sha512-QziFxgxq3mjIf5CuwlzXFYscHxgLqdEdJKRo2UJ5GurL5zrSRMzT/O+nK0ABimoFH8MWF8YwIiwECYsHc1LpUQ==}
|
resolution: {integrity: sha512-QziFxgxq3mjIf5CuwlzXFYscHxgLqdEdJKRo2UJ5GurL5zrSRMzT/O+nK0ABimoFH8MWF8YwIiwECYsHc1LpUQ==}
|
||||||
|
|
||||||
ip-regex@5.0.0:
|
|
||||||
resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
|
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
|
||||||
|
|
||||||
is-arrayish@0.2.1:
|
is-arrayish@0.2.1:
|
||||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||||
|
|
||||||
@@ -2610,18 +2595,10 @@ packages:
|
|||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
is-ip@5.0.1:
|
|
||||||
resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==}
|
|
||||||
engines: {node: '>=14.16'}
|
|
||||||
|
|
||||||
is-number@7.0.0:
|
is-number@7.0.0:
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
is-regexp@3.1.0:
|
|
||||||
resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
is-stream@3.0.0:
|
is-stream@3.0.0:
|
||||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
@@ -3023,10 +3000,6 @@ packages:
|
|||||||
oxc-resolver@2.0.1:
|
oxc-resolver@2.0.1:
|
||||||
resolution: {integrity: sha512-xEbYdEGwafn+Y2GTyW0BGC3iIjJZXl+fxrIkyheew5mZrDODmPXJf2qwsa1ocBeVUC51g9e835vNZ9tRR5fYCg==}
|
resolution: {integrity: sha512-xEbYdEGwafn+Y2GTyW0BGC3iIjJZXl+fxrIkyheew5mZrDODmPXJf2qwsa1ocBeVUC51g9e835vNZ9tRR5fYCg==}
|
||||||
|
|
||||||
p-event@5.0.1:
|
|
||||||
resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==}
|
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
|
||||||
|
|
||||||
p-limit@2.3.0:
|
p-limit@2.3.0:
|
||||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -3043,10 +3016,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
p-timeout@5.1.0:
|
|
||||||
resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
p-try@2.2.0:
|
p-try@2.2.0:
|
||||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -3402,9 +3371,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||||
engines: {node: '>=0.6.19'}
|
engines: {node: '>=0.6.19'}
|
||||||
|
|
||||||
string-natural-compare@3.0.1:
|
|
||||||
resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==}
|
|
||||||
|
|
||||||
string-width@4.2.3:
|
string-width@4.2.3:
|
||||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3445,10 +3411,6 @@ packages:
|
|||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
super-regex@0.2.0:
|
|
||||||
resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==}
|
|
||||||
engines: {node: '>=14.16'}
|
|
||||||
|
|
||||||
superjson@2.2.1:
|
superjson@2.2.1:
|
||||||
resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==}
|
resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@@ -3500,10 +3462,6 @@ packages:
|
|||||||
thenify@3.3.1:
|
thenify@3.3.1:
|
||||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||||
|
|
||||||
time-span@5.1.0:
|
|
||||||
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
tinyexec@0.3.1:
|
tinyexec@0.3.1:
|
||||||
resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==}
|
resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==}
|
||||||
|
|
||||||
@@ -4420,8 +4378,8 @@ snapshots:
|
|||||||
|
|
||||||
'@intlify/bundle-utils@9.0.0(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))':
|
'@intlify/bundle-utils@9.0.0(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/message-compiler': 11.0.0-beta.1
|
'@intlify/message-compiler': 11.0.0-rc.1
|
||||||
'@intlify/shared': 11.0.0-beta.1
|
'@intlify/shared': 11.0.0-rc.1
|
||||||
acorn: 8.14.0
|
acorn: 8.14.0
|
||||||
escodegen: 2.1.0
|
escodegen: 2.1.0
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
@@ -4442,21 +4400,21 @@ snapshots:
|
|||||||
'@intlify/shared': 10.0.4
|
'@intlify/shared': 10.0.4
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
'@intlify/message-compiler@11.0.0-beta.1':
|
'@intlify/message-compiler@11.0.0-rc.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/shared': 11.0.0-beta.1
|
'@intlify/shared': 11.0.0-rc.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
'@intlify/shared@10.0.4': {}
|
'@intlify/shared@10.0.4': {}
|
||||||
|
|
||||||
'@intlify/shared@11.0.0-beta.1': {}
|
'@intlify/shared@11.0.0-rc.1': {}
|
||||||
|
|
||||||
'@intlify/unplugin-vue-i18n@5.2.0(@vue/compiler-dom@3.5.12)(eslint@9.14.0(jiti@2.4.0))(rollup@4.24.3)(typescript@5.6.3)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
'@intlify/unplugin-vue-i18n@5.2.0(@vue/compiler-dom@3.5.12)(eslint@9.14.0(jiti@2.4.0))(rollup@4.24.3)(typescript@5.6.3)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@2.4.0))
|
'@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@2.4.0))
|
||||||
'@intlify/bundle-utils': 9.0.0(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))
|
'@intlify/bundle-utils': 9.0.0(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))
|
||||||
'@intlify/shared': 11.0.0-beta.1
|
'@intlify/shared': 11.0.0-rc.1
|
||||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.0.0-beta.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))
|
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.0.0-rc.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))
|
||||||
'@rollup/pluginutils': 5.1.3(rollup@4.24.3)
|
'@rollup/pluginutils': 5.1.3(rollup@4.24.3)
|
||||||
'@typescript-eslint/scope-manager': 7.18.0
|
'@typescript-eslint/scope-manager': 7.18.0
|
||||||
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
|
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
|
||||||
@@ -4479,11 +4437,11 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- webpack-sources
|
- webpack-sources
|
||||||
|
|
||||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.0.0-beta.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.0.0-rc.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.26.2
|
'@babel/parser': 7.26.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@intlify/shared': 11.0.0-beta.1
|
'@intlify/shared': 11.0.0-rc.1
|
||||||
'@vue/compiler-dom': 3.5.12
|
'@vue/compiler-dom': 3.5.12
|
||||||
vue: 3.5.12(typescript@5.6.3)
|
vue: 3.5.12(typescript@5.6.3)
|
||||||
vue-i18n: 10.0.4(vue@3.5.12(typescript@5.6.3))
|
vue-i18n: 10.0.4(vue@3.5.12(typescript@5.6.3))
|
||||||
@@ -4865,6 +4823,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 0.7.34
|
'@types/ms': 0.7.34
|
||||||
|
|
||||||
|
'@types/default-gateway@7.2.2': {}
|
||||||
|
|
||||||
'@types/estree@1.0.6': {}
|
'@types/estree@1.0.6': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
@@ -5649,16 +5609,9 @@ snapshots:
|
|||||||
|
|
||||||
ci-info@4.0.0: {}
|
ci-info@4.0.0: {}
|
||||||
|
|
||||||
cidr-regex@4.0.3:
|
cidr-tools@11.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ip-regex: 5.0.0
|
ip-bigint: 8.2.0
|
||||||
|
|
||||||
cidr-tools@6.4.2:
|
|
||||||
dependencies:
|
|
||||||
cidr-regex: 4.0.3
|
|
||||||
ip-bigint: 7.3.0
|
|
||||||
ip-regex: 5.0.0
|
|
||||||
string-natural-compare: 3.0.1
|
|
||||||
|
|
||||||
clean-regexp@1.0.0:
|
clean-regexp@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5670,10 +5623,6 @@ snapshots:
|
|||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
wrap-ansi: 7.0.0
|
wrap-ansi: 7.0.0
|
||||||
|
|
||||||
clone-regexp@3.0.0:
|
|
||||||
dependencies:
|
|
||||||
is-regexp: 3.1.0
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
@@ -5696,8 +5645,6 @@ snapshots:
|
|||||||
|
|
||||||
confbox@0.1.8: {}
|
confbox@0.1.8: {}
|
||||||
|
|
||||||
convert-hrtime@5.0.0: {}
|
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
copy-anything@3.0.5:
|
copy-anything@3.0.5:
|
||||||
@@ -6278,8 +6225,6 @@ snapshots:
|
|||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
function-timeout@0.1.1: {}
|
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
get-caller-file@2.0.5: {}
|
get-caller-file@2.0.5: {}
|
||||||
@@ -6381,19 +6326,10 @@ snapshots:
|
|||||||
|
|
||||||
indent-string@4.0.0: {}
|
indent-string@4.0.0: {}
|
||||||
|
|
||||||
internal-ip@8.0.0:
|
ip-bigint@8.2.0: {}
|
||||||
dependencies:
|
|
||||||
cidr-tools: 6.4.2
|
|
||||||
default-gateway: 7.2.2
|
|
||||||
is-ip: 5.0.1
|
|
||||||
p-event: 5.0.1
|
|
||||||
|
|
||||||
ip-bigint@7.3.0: {}
|
|
||||||
|
|
||||||
ip-num@1.5.1: {}
|
ip-num@1.5.1: {}
|
||||||
|
|
||||||
ip-regex@5.0.0: {}
|
|
||||||
|
|
||||||
is-arrayish@0.2.1: {}
|
is-arrayish@0.2.1: {}
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
is-binary-path@2.1.0:
|
||||||
@@ -6424,15 +6360,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 3.0.0
|
is-docker: 3.0.0
|
||||||
|
|
||||||
is-ip@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
ip-regex: 5.0.0
|
|
||||||
super-regex: 0.2.0
|
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
is-number@7.0.0: {}
|
||||||
|
|
||||||
is-regexp@3.1.0: {}
|
|
||||||
|
|
||||||
is-stream@3.0.0: {}
|
is-stream@3.0.0: {}
|
||||||
|
|
||||||
is-what@4.1.16: {}
|
is-what@4.1.16: {}
|
||||||
@@ -6992,10 +6921,6 @@ snapshots:
|
|||||||
'@oxc-resolver/binding-win32-arm64-msvc': 2.0.1
|
'@oxc-resolver/binding-win32-arm64-msvc': 2.0.1
|
||||||
'@oxc-resolver/binding-win32-x64-msvc': 2.0.1
|
'@oxc-resolver/binding-win32-x64-msvc': 2.0.1
|
||||||
|
|
||||||
p-event@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
p-timeout: 5.1.0
|
|
||||||
|
|
||||||
p-limit@2.3.0:
|
p-limit@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-try: 2.2.0
|
p-try: 2.2.0
|
||||||
@@ -7012,8 +6937,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
p-timeout@5.1.0: {}
|
|
||||||
|
|
||||||
p-try@2.2.0: {}
|
p-try@2.2.0: {}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
@@ -7332,8 +7255,6 @@ snapshots:
|
|||||||
|
|
||||||
string-argv@0.3.2: {}
|
string-argv@0.3.2: {}
|
||||||
|
|
||||||
string-natural-compare@3.0.1: {}
|
|
||||||
|
|
||||||
string-width@4.2.3:
|
string-width@4.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
emoji-regex: 8.0.0
|
emoji-regex: 8.0.0
|
||||||
@@ -7378,12 +7299,6 @@ snapshots:
|
|||||||
pirates: 4.0.6
|
pirates: 4.0.6
|
||||||
ts-interface-checker: 0.1.13
|
ts-interface-checker: 0.1.13
|
||||||
|
|
||||||
super-regex@0.2.0:
|
|
||||||
dependencies:
|
|
||||||
clone-regexp: 3.0.0
|
|
||||||
function-timeout: 0.1.1
|
|
||||||
time-span: 5.1.0
|
|
||||||
|
|
||||||
superjson@2.2.1:
|
superjson@2.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
copy-anything: 3.0.5
|
copy-anything: 3.0.5
|
||||||
@@ -7452,10 +7367,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
any-promise: 1.3.0
|
||||||
|
|
||||||
time-span@5.1.0:
|
|
||||||
dependencies:
|
|
||||||
convert-hrtime: 5.0.0
|
|
||||||
|
|
||||||
tinyexec@0.3.1: {}
|
tinyexec@0.3.1: {}
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
to-regex-range@5.0.1:
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class TauriVpnService : VpnService() {
|
|||||||
|
|
||||||
for (route in routes) {
|
for (route in routes) {
|
||||||
val ipParts = route.split("/")
|
val ipParts = route.split("/")
|
||||||
if (ipParts.size != 2) throw IllegalArgumentException("Invalid IP addr string")
|
if (ipParts.size != 2) throw IllegalArgumentException("Invalid route cidr string")
|
||||||
builder.addRoute(ipParts[0], ipParts[1].toInt())
|
builder.addRoute(ipParts[0], ipParts[1].toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const COMMANDS: &[&str] = &[
|
|||||||
"prepare_vpn",
|
"prepare_vpn",
|
||||||
"start_vpn",
|
"start_vpn",
|
||||||
"stop_vpn",
|
"stop_vpn",
|
||||||
"register_listener",
|
"registerListener",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-registerListener"
|
||||||
|
description = "Enables the registerListener command without any pre-configured scope."
|
||||||
|
commands.allow = ["registerListener"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-registerListener"
|
||||||
|
description = "Denies the registerListener command without any pre-configured scope."
|
||||||
|
commands.deny = ["registerListener"]
|
||||||
@@ -69,6 +69,32 @@ Denies the prepare_vpn command without any pre-configured scope.
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
`vpnservice:allow-registerListener`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the registerListener command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`vpnservice:deny-registerListener`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the registerListener command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
`vpnservice:allow-register-listener`
|
`vpnservice:allow-register-listener`
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -314,6 +314,16 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "deny-prepare-vpn"
|
"const": "deny-prepare-vpn"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the registerListener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-registerListener"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the registerListener command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-registerListener"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the register_listener command without any pre-configured scope.",
|
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
Reference in New Issue
Block a user