Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c23b544c34 | ||
|
|
9d76b86f49 | ||
|
|
bb0ccca3e5 | ||
|
|
306817ae9a | ||
|
|
d2ec60e108 | ||
|
|
e016aeddeb | ||
|
|
a4419a31fd | ||
|
|
34e4e907a9 | ||
|
|
2f4a097787 | ||
|
|
f3de00be37 | ||
|
|
4cf61f0d4a | ||
|
|
4e5915f98e | ||
|
|
870eca9e9f |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ on:
|
||||
version:
|
||||
description: 'Version for this release'
|
||||
type: string
|
||||
default: 'v2.1.0'
|
||||
default: 'v2.1.2'
|
||||
required: true
|
||||
make_latest:
|
||||
description: 'Mark this release as latest'
|
||||
|
||||
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -1830,7 +1830,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||
|
||||
[[package]]
|
||||
name = "easytier"
|
||||
version = "2.1.0"
|
||||
version = "2.1.2"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -1926,7 +1926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "easytier-gui"
|
||||
version = "2.1.0"
|
||||
version = "2.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1979,6 +1979,7 @@ dependencies = [
|
||||
"axum-login",
|
||||
"axum-messages",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
"dashmap",
|
||||
"easytier",
|
||||
@@ -1987,12 +1988,14 @@ dependencies = [
|
||||
"password-auth",
|
||||
"rand 0.8.5",
|
||||
"rust-embed",
|
||||
"rust-i18n",
|
||||
"rusttype",
|
||||
"sea-orm",
|
||||
"sea-orm-migration",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"sys-locale",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
@@ -7312,9 +7315,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.1.0"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c24f1ab82d336e09f5f1094a4d9227c99ac26cce263bfdf8136897cc6db6f1d0"
|
||||
checksum = "d3889b392db6d32a105d3757230ea0220090b8f94c90d3e60b6c5eb91178ab1b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7350,7 +7353,7 @@ dependencies = [
|
||||
"tauri-runtime",
|
||||
"tauri-runtime-wry",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.2",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
"tray-icon",
|
||||
"url",
|
||||
@@ -7562,9 +7565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cce18d43f80d4aba3aa8a0c953bbe835f3d0f2370aca75e8dbb14bd4bab27958"
|
||||
checksum = "a1ef7363e7229ac8d04e8a5d405670dbd43dde8fc4bc3bc56105c35452d03784"
|
||||
dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
@@ -7574,16 +7577,16 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.2",
|
||||
"thiserror 1.0.63",
|
||||
"url",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.2.0"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f442a38863e10129ffe2cec7bd09c2dcf8a098a3a27801a476a304d5bb991d2"
|
||||
checksum = "62fa2068e8498ad007b54d5773d03d57c3ff6dd96f8c8ce58beff44d0d5e0d30"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.1.0",
|
||||
@@ -9259,13 +9262,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.47.0"
|
||||
version = "0.46.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "553ca1ce149982123962fac2506aa75b8b76288779a77e72b12fa2fc34938647"
|
||||
checksum = "2f8c948dc5f7c23bd93ba03b85b7f679852589bb78e150424d993171e4ef7b73"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"dunce",
|
||||
@@ -9290,7 +9292,6 @@ dependencies = [
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
"thiserror 1.0.63",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
|
||||
15
README.md
15
README.md
@@ -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.
|
||||
- **IPv6 Support**: Supports networking using IPv6.
|
||||
- **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
|
||||
|
||||
@@ -52,7 +53,7 @@ EasyTier is a simple, safe and decentralized VPN networking solution implemented
|
||||
|
||||
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)**
|
||||
|
||||
@@ -200,20 +201,20 @@ Subnet proxy information will automatically sync to each node in the virtual net
|
||||
|
||||
### 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.
|
||||
|
||||
Taking two nodes as an example, Node A executes:
|
||||
|
||||
```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
|
||||
|
||||
```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.
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
- [ ] Improve documentation and user guides.
|
||||
- [ ] Support features such as encryption, TCP hole punching, etc.
|
||||
- [ ] Support features such TCP hole punching, KCP, FEC etc.
|
||||
- [ ] Support iOS.
|
||||
- [ ] Support Web configuration management.
|
||||
|
||||
## Community and Contribution
|
||||
|
||||
|
||||
16
README_CN.md
16
README_CN.md
@@ -8,7 +8,7 @@
|
||||
|
||||
[简体中文](/README_CN.md) | [English](/README.md)
|
||||
|
||||
**请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。**
|
||||
**请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。**
|
||||
|
||||
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
- **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。
|
||||
- **IPV6 支持**:支持利用 IPV6 组网。
|
||||
- **多协议类型**: 支持使用 WebSocket、QUIC 等协议进行节点间通信。
|
||||
- **Web 管理界面**:支持通过 [Web 界面](https://easytier.cn)管理节点。
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -52,7 +53,7 @@
|
||||
|
||||
4. **通过Docker Compose安装**
|
||||
|
||||
请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。
|
||||
请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。
|
||||
|
||||
5. **使用一键脚本安装 (仅适用于 Linux)**
|
||||
|
||||
@@ -199,20 +200,20 @@ sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24
|
||||
|
||||
### 无公网IP组网
|
||||
|
||||
EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 ``tcp://public.easytier.top:11010``。
|
||||
EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 ``tcp://public.easytier.cn:11010``。
|
||||
|
||||
使用共享节点时,需要每个入网节点提供相同的 ``--network-name`` 和 ``--network-secret`` 参数,作为网络的唯一标识。
|
||||
|
||||
以双节点为例,节点 A 执行:
|
||||
|
||||
```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 执行
|
||||
|
||||
```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。
|
||||
@@ -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。
|
||||
- [ ] 支持 Web 配置管理。
|
||||
|
||||
## 社区和贡献
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "easytier-gui",
|
||||
"type": "module",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
||||
"scripts": {
|
||||
@@ -35,14 +35,16 @@
|
||||
"@primevue/auto-import-resolver": "^4.1.0",
|
||||
"@tauri-apps/api": "2.1.0",
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"@types/default-gateway": "^7.2.2",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue-macros/volar": "0.30.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cidr-tools": "^11.0.2",
|
||||
"default-gateway": "^7.2.2",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-plugin-format": "^0.1.2",
|
||||
"internal-ip": "^8.0.0",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "^5.6.2",
|
||||
@@ -58,4 +60,4 @@
|
||||
"vue-i18n": "^10.0.0",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "easytier-gui"
|
||||
version = "2.1.0"
|
||||
version = "2.1.2"
|
||||
description = "EasyTier GUI"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
@@ -15,7 +15,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||
|
||||
[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",
|
||||
"image-png",
|
||||
"image-ico",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"vpnservice:allow-prepare-vpn",
|
||||
"vpnservice:allow-start-vpn",
|
||||
"vpnservice:allow-stop-vpn",
|
||||
"vpnservice:allow-register-listener",
|
||||
"vpnservice:allow-registerListener",
|
||||
"os:default",
|
||||
"os:allow-os-type",
|
||||
"os:allow-arch",
|
||||
|
||||
@@ -141,7 +141,6 @@ pub fn run() {
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
utils::setup_panic_handler();
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"createUpdaterArtifacts": false
|
||||
},
|
||||
"productName": "easytier-gui",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.2",
|
||||
"identifier": "com.kkrainbow.easytier",
|
||||
"plugins": {},
|
||||
"app": {
|
||||
|
||||
@@ -49,9 +49,9 @@ async function doStartVpn(ipv4Addr: string, cidr: number, routes: string[]) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('start vpn')
|
||||
console.log('start vpn service', ipv4Addr, cidr, routes)
|
||||
const start_ret = await start_vpn({
|
||||
ipv4Addr: `${ipv4Addr}`,
|
||||
ipv4Addr: `${ipv4Addr}/${cidr}`,
|
||||
routes,
|
||||
disallowedApplications: ['com.kkrainbow.easytier'],
|
||||
mtu: 1300,
|
||||
@@ -113,6 +113,7 @@ function getRoutesForVpn(routes: Route[]): string[] {
|
||||
}
|
||||
|
||||
async function onNetworkInstanceChange() {
|
||||
console.error('vpn service watch network instance change ids', JSON.stringify(networkStore.networkInstanceIds))
|
||||
const insts = networkStore.networkInstanceIds
|
||||
if (!insts) {
|
||||
await doStopVpn()
|
||||
@@ -142,7 +143,7 @@ async function onNetworkInstanceChange() {
|
||||
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
||||
|
||||
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 {
|
||||
await doStopVpn()
|
||||
}
|
||||
@@ -154,7 +155,7 @@ async function onNetworkInstanceChange() {
|
||||
await doStartVpn(virtual_ip, 24, routes)
|
||||
}
|
||||
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()
|
||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
||||
}
|
||||
@@ -175,6 +176,7 @@ async function watchNetworkInstance() {
|
||||
}
|
||||
subscribe_running = false
|
||||
})
|
||||
console.error('vpn service watch network instance')
|
||||
}
|
||||
|
||||
export async function initMobileVpnService() {
|
||||
|
||||
@@ -250,7 +250,12 @@ onBeforeMount(async () => {
|
||||
|
||||
onMounted(async () => {
|
||||
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 process from 'node:process'
|
||||
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
|
||||
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
|
||||
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 Components from 'unplugin-vue-components/vite'
|
||||
import VueMacros from 'unplugin-vue-macros/vite'
|
||||
@@ -13,6 +15,20 @@ import { defineConfig } from 'vite'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
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
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
@@ -99,10 +115,10 @@ export default defineConfig(async () => ({
|
||||
},
|
||||
hmr: host
|
||||
? {
|
||||
protocol: 'ws',
|
||||
host: internalIpV4Sync(),
|
||||
port: 1430,
|
||||
}
|
||||
protocol: 'ws',
|
||||
host: findIp(gateway4sync().gateway),
|
||||
port: 1430,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "easytier-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||
|
||||
[dependencies]
|
||||
easytier = { path = "../easytier" }
|
||||
@@ -36,6 +37,8 @@ rusttype = "0.9.3"
|
||||
imageproc = "0.23.0"
|
||||
|
||||
|
||||
rust-i18n = "3"
|
||||
sys-locale = "0.3"
|
||||
clap = { version = "4.4.8", features = [
|
||||
"string",
|
||||
"unicode",
|
||||
@@ -50,3 +53,5 @@ uuid = { version = "1.5.0", features = [
|
||||
"macro-diagnostics",
|
||||
"serde",
|
||||
] }
|
||||
|
||||
chrono = { version = "0.4.37", features = ["serde"] }
|
||||
|
||||
@@ -303,7 +303,7 @@ function showEventLogs() {
|
||||
|
||||
<template>
|
||||
<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'">
|
||||
<pre>{{ dialogContent }}</pre>
|
||||
</ScrollPanel>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import { Md5 } from 'ts-md5'
|
||||
import { UUID } from './utils';
|
||||
|
||||
export interface ValidateConfigResponse {
|
||||
toml_config: string;
|
||||
@@ -31,6 +32,11 @@ export interface Summary {
|
||||
device_count: number;
|
||||
}
|
||||
|
||||
export interface ListNetworkInstanceIdResponse {
|
||||
running_inst_ids: Array<UUID>,
|
||||
disabled_inst_ids: Array<UUID>,
|
||||
}
|
||||
|
||||
export class ApiClient {
|
||||
private client: AxiosInstance;
|
||||
private authFailedCb: Function | undefined;
|
||||
@@ -141,6 +147,17 @@ export class ApiClient {
|
||||
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> {
|
||||
const response = await this.client.get<any, Record<string, any>>('/machines/' + machine_id + '/networks/info/' + inst_id);
|
||||
return response.info.map;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast } from 'primevue';
|
||||
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';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -33,9 +33,16 @@ const isEditing = ref(false);
|
||||
const showCreateNetworkDialog = ref(false);
|
||||
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
||||
|
||||
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
|
||||
|
||||
const instanceIdList = computed(() => {
|
||||
let insts = deviceInfo.value?.running_network_instances || [];
|
||||
let options = insts.map((instance: string) => {
|
||||
let insts = new Set(deviceInfo.value?.running_network_instances || []);
|
||||
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 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 confirmDeleteNetwork = (event: any) => {
|
||||
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 () => {
|
||||
if (!deviceId.value || !instanceId.value) {
|
||||
return;
|
||||
@@ -146,7 +209,7 @@ const loadDeviceInfo = async () => {
|
||||
|
||||
let periodFunc = new Utils.PeriodicTask(async () => {
|
||||
try {
|
||||
await loadDeviceInfo();
|
||||
await Promise.all([loadNetworkInstanceIds(), loadDeviceInfo()]);
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
}
|
||||
@@ -188,8 +251,23 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</Toolbar>
|
||||
|
||||
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="!!selectedInstanceId">
|
||||
</Status>
|
||||
<!-- For running network, show the 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="text-center text-xl"> Select or create a network instance to manage </div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { Card, InputText, Password, Button, AutoComplete } from 'primevue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
@@ -20,8 +20,60 @@ const registerPassword = ref('');
|
||||
const captcha = ref('');
|
||||
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 () => {
|
||||
// Add your login logic here
|
||||
saveApiHost(apiHost.value);
|
||||
const credential: Api.Credential = { username: username.value, password: password.value, };
|
||||
let ret = await api.value?.login(credential);
|
||||
if (ret.success) {
|
||||
@@ -36,6 +88,7 @@ const onSubmit = async () => {
|
||||
};
|
||||
|
||||
const onRegister = async () => {
|
||||
saveApiHost(apiHost.value);
|
||||
const credential: Api.Credential = { username: registerUsername.value, password: registerPassword.value };
|
||||
const registerReq: Api.RegisterData = { credentials: credential, captcha: captcha.value };
|
||||
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 apiHost = ref<string>(defaultApiHost)
|
||||
const apiHost = ref<string>(getInitialApiHost())
|
||||
const apiHostSuggestions = ref<Array<string>>([])
|
||||
const apiHostSearch = async (event: { query: string }) => {
|
||||
apiHostSuggestions.value = [];
|
||||
let hosts = cleanAndLoadApiHosts();
|
||||
if (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>
|
||||
|
||||
<template>
|
||||
@@ -87,7 +159,7 @@ const apiHostSearch = async (event: { query: string }) => {
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<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>
|
||||
</form>
|
||||
|
||||
@@ -111,7 +183,7 @@ const apiHostSearch = async (event: { query: string }) => {
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<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>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
24
easytier-web/locales/app.yml
Normal file
24
easytier-web/locales/app.yml
Normal file
@@ -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())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use std::{fmt::Debug, str::FromStr as _, sync::Arc};
|
||||
|
||||
use easytier::{
|
||||
common::scoped_task::ScopedTask,
|
||||
@@ -15,6 +15,8 @@ use easytier::{
|
||||
};
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
|
||||
use crate::db::ListNetworkProps;
|
||||
|
||||
use super::storage::{Storage, StorageToken, WeakRefStorage};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -87,10 +89,20 @@ impl WebServerService for SessionRpcService {
|
||||
.map(Into::into)
|
||||
.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);
|
||||
Ok(HeartbeatResponse {})
|
||||
}
|
||||
@@ -196,7 +208,11 @@ impl Session {
|
||||
|
||||
let local_configs = match storage
|
||||
.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
|
||||
{
|
||||
Ok(configs) => configs,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::db::Db;
|
||||
|
||||
@@ -12,11 +12,19 @@ pub struct StorageToken {
|
||||
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)]
|
||||
pub struct StorageInner {
|
||||
// some map for indexing
|
||||
pub token_clients_map: DashMap<String, DashSet<url::Url>>,
|
||||
pub machine_client_url_map: DashMap<uuid::Uuid, DashSet<url::Url>>,
|
||||
token_clients_map: DashMap<String, DashMap<uuid::Uuid, ClientInfo>>,
|
||||
machine_client_url_map: DashMap<uuid::Uuid, ClientInfo>,
|
||||
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
|
||||
.0
|
||||
.token_clients_map
|
||||
.entry(stoken.token)
|
||||
.or_insert_with(DashSet::new);
|
||||
inner.insert(stoken.client_url.clone());
|
||||
.entry(stoken.token.clone())
|
||||
.or_insert_with(DashMap::new);
|
||||
|
||||
self.0
|
||||
.machine_client_url_map
|
||||
.entry(stoken.machine_id)
|
||||
.or_insert_with(DashSet::new)
|
||||
.insert(stoken.client_url.clone());
|
||||
let client_info = ClientInfo {
|
||||
client_url: stoken.client_url.clone(),
|
||||
machine_id: stoken.machine_id,
|
||||
token: stoken.token.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) {
|
||||
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()
|
||||
});
|
||||
|
||||
self.0
|
||||
.machine_client_url_map
|
||||
.remove_if(&stoken.machine_id, |_, set| {
|
||||
set.remove(&stoken.client_url);
|
||||
set.is_empty()
|
||||
});
|
||||
Self::remove_mid_to_client_info_map(
|
||||
&self.0.machine_client_url_map,
|
||||
&stoken.machine_id,
|
||||
&stoken.client_url,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn weak_ref(&self) -> WeakRefStorage {
|
||||
@@ -78,15 +110,19 @@ impl Storage {
|
||||
self.0
|
||||
.machine_client_url_map
|
||||
.get(&machine_id)
|
||||
.map(|url| url.iter().next().map(|url| url.clone()))
|
||||
.flatten()
|
||||
.map(|info| info.client_url.clone())
|
||||
}
|
||||
|
||||
pub fn list_token_clients(&self, token: &str) -> Vec<url::Url> {
|
||||
self.0
|
||||
.token_clients_map
|
||||
.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()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ pub mod entity;
|
||||
|
||||
use entity::user_running_network_configs;
|
||||
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 _,
|
||||
};
|
||||
use sea_orm_migration::MigratorTrait as _;
|
||||
@@ -14,6 +14,12 @@ use crate::migrator;
|
||||
|
||||
type UserIdInDb = i32;
|
||||
|
||||
pub enum ListNetworkProps {
|
||||
All,
|
||||
EnabledOnly,
|
||||
DisabledOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Db {
|
||||
db_path: String,
|
||||
@@ -115,17 +121,51 @@ impl Db {
|
||||
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(
|
||||
&self,
|
||||
user_id: UserIdInDb,
|
||||
device_id: Option<uuid::Uuid>,
|
||||
only_enabled: bool,
|
||||
props: ListNetworkProps,
|
||||
) -> Result<Vec<user_running_network_configs::Model>, DbErr> {
|
||||
use entity::user_running_network_configs as urnc;
|
||||
|
||||
let configs = urnc::Entity::find().filter(urnc::Column::UserId.eq(user_id));
|
||||
let configs = if only_enabled {
|
||||
configs.filter(urnc::Column::Disabled.eq(false))
|
||||
let configs = if matches!(
|
||||
props,
|
||||
ListNetworkProps::EnabledOnly | ListNetworkProps::DisabledOnly
|
||||
) {
|
||||
configs
|
||||
.filter(urnc::Column::Disabled.eq(matches!(props, ListNetworkProps::DisabledOnly)))
|
||||
} else {
|
||||
configs
|
||||
};
|
||||
@@ -140,6 +180,24 @@ impl Db {
|
||||
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>(
|
||||
&self,
|
||||
user_name: T,
|
||||
@@ -167,7 +225,7 @@ impl Db {
|
||||
mod tests {
|
||||
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]
|
||||
async fn test_user_network_config_management() {
|
||||
@@ -209,7 +267,7 @@ mod tests {
|
||||
assert_ne!(result.update_time, result2.update_time);
|
||||
|
||||
assert_eq!(
|
||||
db.list_network_configs(user_id, Some(device_id), true)
|
||||
db.list_network_configs(user_id, Some(device_id), ListNetworkProps::All)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use easytier::{
|
||||
common::config::{ConfigLoader, ConsoleLoggerConfig, TomlConfigLoader},
|
||||
common::{
|
||||
config::{ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, TomlConfigLoader},
|
||||
constants::EASYTIER_VERSION,
|
||||
},
|
||||
tunnel::udp::UdpTunnelListener,
|
||||
utils::init_logger,
|
||||
utils::{init_logger, setup_panic_handler},
|
||||
};
|
||||
|
||||
mod client_manager;
|
||||
@@ -13,26 +20,97 @@ mod db;
|
||||
mod migrator;
|
||||
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]
|
||||
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();
|
||||
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();
|
||||
|
||||
// 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());
|
||||
mgr.serve(listener).await.unwrap();
|
||||
let mgr = Arc::new(mgr);
|
||||
|
||||
let mut restful_server =
|
||||
restful::RestfulServer::new("0.0.0.0:11211".parse().unwrap(), mgr.clone(), db)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut restful_server = restful::RestfulServer::new(
|
||||
format!("0.0.0.0:{}", cli.api_server_port).parse().unwrap(),
|
||||
mgr.clone(),
|
||||
db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
restful_server.start().await.unwrap();
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
}
|
||||
|
||||
@@ -121,7 +121,9 @@ impl RestfulServer {
|
||||
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 {
|
||||
device_count: machines.len() as u32,
|
||||
@@ -167,7 +169,7 @@ impl RestfulServer {
|
||||
.deflate(true)
|
||||
.gzip(true)
|
||||
.zstd(true)
|
||||
.quality(tower_http::compression::CompressionLevel::Best);
|
||||
.quality(tower_http::compression::CompressionLevel::Default);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/v1/summary", get(Self::handle_get_summary))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::extract::Path;
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{delete, post};
|
||||
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::ClientManager;
|
||||
use crate::db::ListNetworkProps;
|
||||
|
||||
use super::users::AuthSession;
|
||||
use super::{
|
||||
@@ -46,13 +47,21 @@ struct ColletNetworkInfoJsonReq {
|
||||
inst_ids: Option<Vec<uuid::Uuid>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct UpdateNetworkStateJsonReq {
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct RemoveNetworkJsonReq {
|
||||
inst_ids: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
struct ListMachineItem {
|
||||
@@ -190,7 +199,7 @@ impl NetworkApi {
|
||||
auth_session: AuthSession,
|
||||
State(client_mgr): AppState,
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Query(payload): Query<ColletNetworkInfoJsonReq>,
|
||||
Json(payload): Json<ColletNetworkInfoJsonReq>,
|
||||
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
|
||||
let result =
|
||||
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 {})
|
||||
.await
|
||||
.map_err(convert_rpc_error)?;
|
||||
Ok(
|
||||
ListNetworkInstanceIdsJsonResp(ret.inst_ids.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
)
|
||||
|
||||
let running_inst_ids = ret.inst_ids.clone().into_iter().map(Into::into).collect();
|
||||
|
||||
// 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(
|
||||
@@ -270,7 +297,7 @@ impl NetworkApi {
|
||||
|
||||
let client_urls = DashSet::new();
|
||||
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 {
|
||||
client_urls.insert(url);
|
||||
}
|
||||
@@ -289,6 +316,54 @@ impl NetworkApi {
|
||||
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(
|
||||
auth_session: AuthSession,
|
||||
State(client_mgr): AppState,
|
||||
@@ -298,25 +373,24 @@ impl NetworkApi {
|
||||
|
||||
let db_row = client_mgr
|
||||
.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
|
||||
.map_err(convert_db_error)?
|
||||
.iter()
|
||||
.find(|x| x.network_instance_id == inst_id)
|
||||
.map(|x| x.network_config.clone())
|
||||
.ok_or((
|
||||
StatusCode::NOT_FOUND,
|
||||
other_error(format!("No such network instance: {}", inst_id)).into(),
|
||||
))?;
|
||||
|
||||
Ok(serde_json::from_str::<NetworkConfig>(&db_row)
|
||||
.map_err(|e| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
other_error(format!("Failed to parse network config: {:?}", e)).into(),
|
||||
)
|
||||
})?
|
||||
.into())
|
||||
Ok(
|
||||
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(),
|
||||
)
|
||||
})?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_route(&mut self) -> Router<AppStateInner> {
|
||||
@@ -332,7 +406,7 @@ impl NetworkApi {
|
||||
)
|
||||
.route(
|
||||
"/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(
|
||||
"/api/v1/machines/:machine-id/networks/info",
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "easytier"
|
||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
version = "2.1.0"
|
||||
version = "2.1.2"
|
||||
edition = "2021"
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
|
||||
@@ -134,6 +134,12 @@ core_clap:
|
||||
compression:
|
||||
en: "compression algorithm to use, support none, zstd. default is 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:
|
||||
panic_backtrace_save:
|
||||
|
||||
@@ -27,8 +27,9 @@ pub fn gen_default_flags() -> Flags {
|
||||
relay_all_peer_rpc: false,
|
||||
disable_udp_hole_punching: false,
|
||||
ipv6_listener: "udp://[::]:0".to_string(),
|
||||
multi_thread: false,
|
||||
multi_thread: true,
|
||||
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 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 set_rpc_portal(&self, addr: SocketAddr);
|
||||
|
||||
@@ -183,6 +187,7 @@ struct Config {
|
||||
dhcp: Option<bool>,
|
||||
network_identity: Option<NetworkIdentity>,
|
||||
listeners: Option<Vec<url::Url>>,
|
||||
mapped_listeners: Option<Vec<url::Url>>,
|
||||
exit_nodes: Option<Vec<Ipv4Addr>>,
|
||||
|
||||
peer: Option<Vec<PeerConfig>>,
|
||||
@@ -472,6 +477,19 @@ impl ConfigLoader for TomlConfigLoader {
|
||||
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> {
|
||||
self.config.lock().unwrap().rpc_portal
|
||||
}
|
||||
|
||||
@@ -230,7 +230,10 @@ impl GlobalCtx {
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
future,
|
||||
io::Write as _,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
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 {
|
||||
// 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(
|
||||
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);
|
||||
uuid::Uuid::from_bytes(b)
|
||||
})
|
||||
.unwrap_or(uuid::Uuid::new_v4());
|
||||
.ok();
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
@@ -103,9 +114,18 @@ pub fn get_machine_id() -> uuid::Uuid {
|
||||
target_os = "windows",
|
||||
target_os = "freebsd"
|
||||
)))]
|
||||
let gen_mid = None;
|
||||
|
||||
if gen_mid.is_some() {
|
||||
return gen_mid.unwrap();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
// 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::{
|
||||
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_BLACKLIST_TIMEOUT_SEC: u64 = 300;
|
||||
|
||||
static TESTING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait PeerManagerForDirectConnector {
|
||||
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?;
|
||||
|
||||
if peer_id != dst_peer_id {
|
||||
if peer_id != dst_peer_id && !TESTING.load(Ordering::Relaxed) {
|
||||
tracing::info!(
|
||||
"connect to ip succ: {}, but peer id mismatch, expect: {}, actual: {}",
|
||||
addr,
|
||||
@@ -279,87 +288,103 @@ impl DirectConnectorManager {
|
||||
|
||||
let listener_host = listener.socket_addrs(|| None).unwrap().pop();
|
||||
match listener_host {
|
||||
Some(SocketAddr::V4(_)) => {
|
||||
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv4"
|
||||
);
|
||||
}
|
||||
});
|
||||
Some(SocketAddr::V4(s_addr)) => {
|
||||
if s_addr.ip().is_unspecified() {
|
||||
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv4"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(public_ipv4) = ip_list.public_ipv4 {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(public_ipv4.to_string().as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?public_ipv4,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for public ipv4"
|
||||
);
|
||||
if let Some(public_ipv4) = ip_list.public_ipv4 {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(public_ipv4.to_string().as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?public_ipv4,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"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(_)) => {
|
||||
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv6"
|
||||
);
|
||||
}
|
||||
});
|
||||
Some(SocketAddr::V6(s_addr)) => {
|
||||
if s_addr.ip().is_unspecified() {
|
||||
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?ip,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for interface ipv6"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(public_ipv6) = ip_list.public_ipv6 {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", public_ipv6.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?public_ipv6,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"failed to set host for public ipv6"
|
||||
);
|
||||
if let Some(public_ipv6) = ip_list.public_ipv6 {
|
||||
let mut addr = (*listener).clone();
|
||||
if addr
|
||||
.set_host(Some(format!("[{}]", public_ipv6.to_string()).as_str()))
|
||||
.is_ok()
|
||||
{
|
||||
tasks.spawn(Self::try_connect_to_ip(
|
||||
data.clone(),
|
||||
dst_peer_id.clone(),
|
||||
addr.to_string(),
|
||||
));
|
||||
} else {
|
||||
tracing::error!(
|
||||
?public_ipv6,
|
||||
?listener,
|
||||
?dst_peer_id,
|
||||
"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 => {
|
||||
@@ -452,6 +477,49 @@ mod tests {
|
||||
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]
|
||||
#[tokio::test]
|
||||
async fn direct_connector_basic_test(
|
||||
|
||||
@@ -297,12 +297,14 @@ impl ManualConnectorManager {
|
||||
|
||||
connector.lock().await.set_ip_version(ip_version);
|
||||
|
||||
set_bind_addr_for_peer_connector(
|
||||
connector.lock().await.as_mut(),
|
||||
ip_version == IpVersion::V4,
|
||||
&ip_collector,
|
||||
)
|
||||
.await;
|
||||
if data.global_ctx.config.get_flags().bind_device {
|
||||
set_bind_addr_for_peer_connector(
|
||||
connector.lock().await.as_mut(),
|
||||
ip_version == IpVersion::V4,
|
||||
&ip_collector,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
||||
connector.lock().await.remote_url().clone(),
|
||||
|
||||
@@ -56,23 +56,27 @@ pub async fn create_connector_by_url(
|
||||
"tcp" => {
|
||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "tcp")?;
|
||||
let mut connector = TcpTunnelConnector::new(url);
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
if global_ctx.config.get_flags().bind_device {
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return Ok(Box::new(connector));
|
||||
}
|
||||
"udp" => {
|
||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "udp")?;
|
||||
let mut connector = UdpTunnelConnector::new(url);
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
if global_ctx.config.get_flags().bind_device {
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return Ok(Box::new(connector));
|
||||
}
|
||||
"ring" => {
|
||||
@@ -84,12 +88,14 @@ pub async fn create_connector_by_url(
|
||||
"quic" => {
|
||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "quic")?;
|
||||
let mut connector = QUICTunnelConnector::new(url);
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
if global_ctx.config.get_flags().bind_device {
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return Ok(Box::new(connector));
|
||||
}
|
||||
#[cfg(feature = "wireguard")]
|
||||
@@ -101,12 +107,14 @@ pub async fn create_connector_by_url(
|
||||
&nid.network_secret.unwrap_or_default(),
|
||||
);
|
||||
let mut connector = WgTunnelConnector::new(url, wg_config);
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
if global_ctx.config.get_flags().bind_device {
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return Ok(Box::new(connector));
|
||||
}
|
||||
#[cfg(feature = "websocket")]
|
||||
@@ -114,12 +122,14 @@ pub async fn create_connector_by_url(
|
||||
use crate::tunnel::{FromUrl, IpVersion};
|
||||
let dst_addr = SocketAddr::from_url(url.clone(), IpVersion::Both)?;
|
||||
let mut connector = crate::tunnel::websocket::WSTunnelConnector::new(url);
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
if global_ctx.config.get_flags().bind_device {
|
||||
set_bind_addr_for_peer_connector(
|
||||
&mut connector,
|
||||
dst_addr.is_ipv4(),
|
||||
&global_ctx.get_ip_collector(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return Ok(Box::new(connector));
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -123,6 +123,13 @@ struct Cli {
|
||||
)]
|
||||
listeners: Vec<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("core_clap.mapped_listeners").to_string(),
|
||||
num_args = 0..
|
||||
)]
|
||||
mapped_listeners: Vec<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("core_clap.no_listener").to_string(),
|
||||
@@ -185,7 +192,7 @@ struct Cli {
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("core_clap.multi_thread").to_string(),
|
||||
default_value = "false"
|
||||
default_value = "true"
|
||||
)]
|
||||
multi_thread: bool,
|
||||
|
||||
@@ -300,6 +307,12 @@ struct Cli {
|
||||
default_value = "none",
|
||||
)]
|
||||
compression: String,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("core_clap.bind_device").to_string()
|
||||
)]
|
||||
bind_device: Option<bool>,
|
||||
}
|
||||
|
||||
rust_i18n::i18n!("locales", fallback = "en");
|
||||
@@ -422,6 +435,23 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
||||
.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() {
|
||||
cfg.add_proxy_cidr(
|
||||
n.parse()
|
||||
@@ -534,6 +564,9 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
||||
),
|
||||
}
|
||||
.into();
|
||||
if let Some(bind_device) = cli.bind_device {
|
||||
f.bind_device = bind_device;
|
||||
}
|
||||
cfg.set_flags(f);
|
||||
|
||||
cfg.set_exit_nodes(cli.exit_nodes.clone());
|
||||
@@ -828,7 +861,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||
rust_i18n::set_locale(&locale);
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use cidr::Ipv4Inet;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use dashmap::DashMap;
|
||||
@@ -24,11 +25,11 @@ use tokio::{
|
||||
use tracing::Level;
|
||||
|
||||
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,
|
||||
peers::{peer_manager::PeerManager, PeerPacketFilter},
|
||||
tunnel::{
|
||||
common::setup_sokcet2,
|
||||
common::{reserve_buf, setup_sokcet2},
|
||||
packet_def::{PacketType, ZCPacket},
|
||||
},
|
||||
};
|
||||
@@ -139,59 +140,81 @@ impl UdpNatEntry {
|
||||
mut packet_sender: Sender<ZCPacket>,
|
||||
virtual_ipv4: Ipv4Addr,
|
||||
) {
|
||||
let mut buf = [0u8; 65536];
|
||||
let mut udp_body: &mut [u8] = unsafe { std::mem::transmute(&mut buf[20 + 8..]) };
|
||||
let mut ip_id = 1;
|
||||
let (s, mut r) = tachyonix::channel(128);
|
||||
|
||||
loop {
|
||||
let (len, src_socket) = match timeout(
|
||||
Duration::from_secs(120),
|
||||
self.socket.recv_from(&mut udp_body),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(x)) => x,
|
||||
Ok(Err(err)) => {
|
||||
tracing::error!(?err, "udp nat recv failed");
|
||||
let self_clone = self.clone();
|
||||
let recv_task = ScopedTask::from(tokio::spawn(async move {
|
||||
let mut cur_buf = BytesMut::new();
|
||||
loop {
|
||||
if self_clone
|
||||
.stopped
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "udp nat recv timeout");
|
||||
break;
|
||||
|
||||
reserve_buf(&mut cur_buf, 64 * 1024 + 28, 128 * 1024 + 28);
|
||||
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) {
|
||||
break;
|
||||
tracing::trace!(?len, ?src_socket, "udp nat packet response received");
|
||||
|
||||
let ret_buf = cur_buf.split();
|
||||
s.send((ret_buf, len, src_socket)).await.unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
let SocketAddr::V4(mut src_v4) = src_socket else {
|
||||
continue;
|
||||
};
|
||||
let self_clone = self.clone();
|
||||
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() {
|
||||
src_v4.set_ip(virtual_ipv4);
|
||||
if src_v4.ip().is_loopback() {
|
||||
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(
|
||||
&self,
|
||||
&mut packet_sender,
|
||||
&mut buf,
|
||||
&src_v4,
|
||||
len,
|
||||
1200,
|
||||
ip_id,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
break;
|
||||
};
|
||||
ip_id = ip_id.wrapping_add(1);
|
||||
}
|
||||
let _ = tokio::join!(recv_task, send_task);
|
||||
|
||||
self.stop();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use tokio::{sync::Mutex, task::JoinSet};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
#[cfg(feature = "quic")]
|
||||
use crate::tunnel::quic::QUICTunnelListener;
|
||||
@@ -63,16 +62,20 @@ impl TunnelHandlerForListener for PeerManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Listener {
|
||||
inner: Arc<Mutex<dyn TunnelListener>>,
|
||||
pub trait ListenerCreatorTrait: Fn() -> Box<dyn TunnelListener> + Send + Sync {}
|
||||
impl<T: Send + Sync> ListenerCreatorTrait for T where T: Fn() -> Box<dyn TunnelListener> + Send {}
|
||||
pub type ListenerCreator = Box<dyn ListenerCreatorTrait>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ListenerFactory {
|
||||
creator_fn: Arc<ListenerCreator>,
|
||||
must_succ: bool,
|
||||
}
|
||||
|
||||
pub struct ListenerManager<H> {
|
||||
global_ctx: ArcGlobalCtx,
|
||||
net_ns: NetNS,
|
||||
listeners: Vec<Listener>,
|
||||
listeners: Vec<ListenerFactory>,
|
||||
peer_manager: Arc<H>,
|
||||
|
||||
tasks: JoinSet<()>,
|
||||
@@ -90,31 +93,39 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
||||
}
|
||||
|
||||
pub async fn prepare_listeners(&mut self) -> Result<(), Error> {
|
||||
let self_id = self.global_ctx.get_id();
|
||||
self.add_listener(
|
||||
RingTunnelListener::new(
|
||||
format!("ring://{}", self.global_ctx.get_id())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
),
|
||||
move || {
|
||||
Box::new(RingTunnelListener::new(
|
||||
format!("ring://{}", self_id).parse().unwrap(),
|
||||
))
|
||||
},
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
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);
|
||||
self.global_ctx
|
||||
.issue_event(GlobalCtxEvent::ListenerAddFailed(l.clone(), msg));
|
||||
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 {
|
||||
let ipv6_listener = self.global_ctx.config.get_flags().ipv6_listener.clone();
|
||||
let _ = self
|
||||
.add_listener(
|
||||
UdpTunnelListener::new(ipv6_listener.parse().unwrap()),
|
||||
move || {
|
||||
Box::new(UdpTunnelListener::new(
|
||||
ipv6_listener.clone().parse().unwrap(),
|
||||
))
|
||||
},
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
@@ -123,85 +134,91 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_listener<L>(&mut self, listener: L, must_succ: bool) -> Result<(), Error>
|
||||
where
|
||||
L: TunnelListener + 'static,
|
||||
{
|
||||
let listener = Arc::new(Mutex::new(listener));
|
||||
self.listeners.push(Listener {
|
||||
inner: listener,
|
||||
pub async fn add_listener<C: ListenerCreatorTrait + 'static>(
|
||||
&mut self,
|
||||
creator: C,
|
||||
must_succ: bool,
|
||||
) -> Result<(), Error> {
|
||||
self.listeners.push(ListenerFactory {
|
||||
creator_fn: Arc::new(Box::new(creator)),
|
||||
must_succ,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[tracing::instrument(skip(creator))]
|
||||
async fn run_listener(
|
||||
listener: Arc<Mutex<dyn TunnelListener>>,
|
||||
creator: Arc<ListenerCreator>,
|
||||
peer_manager: Arc<H>,
|
||||
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 {
|
||||
let ret = match l.accept().await {
|
||||
Ok(ret) => ret,
|
||||
let mut l = (creator)();
|
||||
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) => {
|
||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAcceptFailed(
|
||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAddFailed(
|
||||
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;
|
||||
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();
|
||||
global_ctx.issue_event(GlobalCtxEvent::ConnectionAccepted(
|
||||
tunnel_info
|
||||
.local_addr
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
tunnel_info
|
||||
.remote_addr
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
));
|
||||
tracing::info!(ret = ?ret, "conn accepted");
|
||||
let peer_manager = peer_manager.clone();
|
||||
let global_ctx = global_ctx.clone();
|
||||
tokio::spawn(async move {
|
||||
let server_ret = peer_manager.handle_tunnel(ret).await;
|
||||
if let Err(e) = &server_ret {
|
||||
global_ctx.issue_event(GlobalCtxEvent::ConnectionError(
|
||||
tunnel_info.local_addr.unwrap_or_default().to_string(),
|
||||
tunnel_info.remote_addr.unwrap_or_default().to_string(),
|
||||
e.to_string(),
|
||||
));
|
||||
tracing::error!(error = ?e, "handle conn error");
|
||||
}
|
||||
});
|
||||
let tunnel_info = ret.info().unwrap();
|
||||
global_ctx.issue_event(GlobalCtxEvent::ConnectionAccepted(
|
||||
tunnel_info
|
||||
.local_addr
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
tunnel_info
|
||||
.remote_addr
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
));
|
||||
tracing::info!(ret = ?ret, "conn accepted");
|
||||
let peer_manager = peer_manager.clone();
|
||||
let global_ctx = global_ctx.clone();
|
||||
tokio::spawn(async move {
|
||||
let server_ret = peer_manager.handle_tunnel(ret).await;
|
||||
if let Err(e) = &server_ret {
|
||||
global_ctx.issue_event(GlobalCtxEvent::ConnectionError(
|
||||
tunnel_info.local_addr.unwrap_or_default().to_string(),
|
||||
tunnel_info.remote_addr.unwrap_or_default().to_string(),
|
||||
e.to_string(),
|
||||
));
|
||||
tracing::error!(error = ?e, "handle conn error");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Error> {
|
||||
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(
|
||||
listener.inner.clone(),
|
||||
listener.creator_fn.clone(),
|
||||
self.peer_manager.clone(),
|
||||
self.global_ctx.clone(),
|
||||
));
|
||||
@@ -213,12 +230,14 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use tokio::time::timeout;
|
||||
|
||||
use crate::{
|
||||
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::*;
|
||||
@@ -245,12 +264,18 @@ mod tests {
|
||||
|
||||
let ring_id = format!("ring://{}", uuid::Uuid::new_v4());
|
||||
|
||||
let ring_id_clone = ring_id.clone();
|
||||
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
|
||||
.unwrap();
|
||||
listener_mgr.run().await.unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
|
||||
let connect_once = |ring_id| async move {
|
||||
let tunnel = RingTunnelConnector::new(ring_id).connect().await.unwrap();
|
||||
let (mut recv, _send) = tunnel.split();
|
||||
@@ -269,4 +294,60 @@ mod tests {
|
||||
.await
|
||||
.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 = 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 s_peer_id = new_peer_id();
|
||||
|
||||
@@ -506,6 +499,7 @@ mod tests {
|
||||
s_peer
|
||||
.start_recv_loop(tokio::sync::mpsc::channel(200).0)
|
||||
.await;
|
||||
// do not start ping for s, s only reponde to ping from c
|
||||
|
||||
assert!(c_ret.is_ok());
|
||||
assert!(s_ret.is_ok());
|
||||
@@ -547,7 +541,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
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]
|
||||
|
||||
@@ -289,29 +289,29 @@ impl PeerConnPinger {
|
||||
"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 need_close = if last_rx_packets != current_rx_packets {
|
||||
// if we receive some packet from peers, we should relax the condition
|
||||
counter > 50 && loss_rate_1 > 0.5
|
||||
let current_rx_packets = throughput.rx_packets();
|
||||
if last_rx_packets != current_rx_packets {
|
||||
// if we receive some packet from peers, reset the counter to avoid conn close.
|
||||
// conn will close only if we have 5 continous round pingpong loss after no packet received.
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
// TODO: wait more time to see if the loss rate is still high after no rx
|
||||
} else {
|
||||
true
|
||||
};
|
||||
tracing::debug!(
|
||||
"counter: {}, loss_rate_1: {}, loss_rate_20: {}, cur_rx_packets: {}, last_rx: {}, node_id: {}",
|
||||
counter, loss_rate_1, loss_rate_20, current_rx_packets, last_rx_packets, my_node_id
|
||||
);
|
||||
|
||||
if need_close {
|
||||
tracing::warn!(
|
||||
?ret,
|
||||
?self,
|
||||
?loss_rate_1,
|
||||
?loss_rate_20,
|
||||
?last_rx_packets,
|
||||
?current_rx_packets,
|
||||
"pingpong loss rate too high, closing"
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (counter > 5 && loss_rate_20 > 0.74) || (counter > 100 && loss_rate_1 > 0.35) {
|
||||
tracing::warn!(
|
||||
?ret,
|
||||
?self,
|
||||
?loss_rate_1,
|
||||
?loss_rate_20,
|
||||
?last_rx_packets,
|
||||
?current_rx_packets,
|
||||
"pingpong loss rate too high, closing"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
last_rx_packets = throughput.rx_packets();
|
||||
|
||||
@@ -1739,7 +1739,6 @@ impl RouteSessionManager {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
ret.listeners = self
|
||||
.global_ctx
|
||||
.get_running_listeners()
|
||||
.config
|
||||
.get_mapped_listeners()
|
||||
.into_iter()
|
||||
.chain(self.global_ctx.get_running_listeners().into_iter())
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(ret)
|
||||
|
||||
@@ -21,6 +21,7 @@ message FlagsInConfig {
|
||||
string ipv6_listener = 14;
|
||||
bool multi_thread = 15;
|
||||
CompressionAlgoPb data_compress_algo = 16;
|
||||
bool bind_device = 17;
|
||||
}
|
||||
|
||||
message RpcDescriptor {
|
||||
|
||||
@@ -159,7 +159,10 @@ pub fn build_rpc_packet(
|
||||
let cur_packet = RpcPacket {
|
||||
from_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())
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -38,6 +38,33 @@ impl<L: TunnelListener + 'static> StandAloneServer<L> {
|
||||
&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> {
|
||||
let tasks = self.tasks.clone();
|
||||
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());
|
||||
|
||||
listener
|
||||
.listen()
|
||||
.await
|
||||
.with_context(|| "failed to listen")?;
|
||||
|
||||
let inflight_server = self.inflight_server.clone();
|
||||
|
||||
self.tasks.lock().unwrap().spawn(async move {
|
||||
while let Ok(tunnel) = listener.accept().await {
|
||||
let registry = registry.clone();
|
||||
let inflight_server = inflight_server.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);
|
||||
});
|
||||
loop {
|
||||
let ret = Self::serve_loop(
|
||||
&mut listener,
|
||||
inflight_server.clone(),
|
||||
registry.clone(),
|
||||
tasks.clone(),
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = ret {
|
||||
tracing::error!(?e, url = ?listener.local_url(), "serve_loop exit unexpectedly");
|
||||
println!("standalone serve_loop exit unexpectedly: {:?}", e);
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
panic!("standalone server listener exit");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -287,6 +287,8 @@ async fn standalone_rpc_test() {
|
||||
server.registry().register(service, "test");
|
||||
server.serve().await.unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
let mut client = StandAloneClient::new(TcpTunnelConnector::new(
|
||||
"tcp://127.0.0.1:33455".parse().unwrap(),
|
||||
));
|
||||
|
||||
@@ -28,11 +28,36 @@ impl TcpTunnelListener {
|
||||
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]
|
||||
impl TunnelListener for TcpTunnelListener {
|
||||
async fn listen(&mut self) -> Result<(), TunnelError> {
|
||||
self.listener = None;
|
||||
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
||||
|
||||
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> {
|
||||
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");
|
||||
loop {
|
||||
match self.do_accept().await {
|
||||
Ok(ret) => return Ok(ret),
|
||||
Err(e) => {
|
||||
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 {
|
||||
|
||||
@@ -139,12 +139,17 @@ pub fn setup_panic_handler() {
|
||||
|
||||
if let Some(payload_str) = payload_str {
|
||||
println!(
|
||||
"panic occurred: payload:{}, location: {:?}",
|
||||
"panic occurred: payload:{}, location: {:?}, backtrace: {:#?}",
|
||||
payload_str,
|
||||
info.location()
|
||||
info.location(),
|
||||
backtrace
|
||||
);
|
||||
} else {
|
||||
println!("panic occurred: location: {:?}", info.location());
|
||||
println!(
|
||||
"panic occurred: location: {:?}, backtrace: {:#?}",
|
||||
info.location(),
|
||||
backtrace
|
||||
);
|
||||
}
|
||||
println!("{}", rust_i18n::t!("core_app.panic_backtrace_save"));
|
||||
let _ = std::fs::File::create("easytier-panic.log")
|
||||
@@ -168,4 +173,4 @@ mod tests {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
tracing::debug!("test display debug");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
161
pnpm-lock.yaml
generated
161
pnpm-lock.yaml
generated
@@ -69,6 +69,9 @@ importers:
|
||||
'@tauri-apps/cli':
|
||||
specifier: 2.1.0
|
||||
version: 2.1.0
|
||||
'@types/default-gateway':
|
||||
specifier: ^7.2.2
|
||||
version: 7.2.2
|
||||
'@types/node':
|
||||
specifier: ^22.7.4
|
||||
version: 22.8.6
|
||||
@@ -84,15 +87,18 @@ importers:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
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:
|
||||
specifier: ^9.12.0
|
||||
version: 9.14.0(jiti@2.4.0)
|
||||
eslint-plugin-format:
|
||||
specifier: ^0.1.2
|
||||
version: 0.1.2(eslint@9.14.0(jiti@2.4.0))
|
||||
internal-ip:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
postcss:
|
||||
specifier: ^8.4.47
|
||||
version: 8.4.47
|
||||
@@ -905,16 +911,16 @@ packages:
|
||||
resolution: {integrity: sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/message-compiler@11.0.0-beta.1':
|
||||
resolution: {integrity: sha512-yMXfN4hg/EeSdtWfmoMrwB9X4TXwkBoZlTIpNydQaW9y0tSJHGnUPRoahtkbsyACCm9leSJINLY4jQ0rK6BK0Q==}
|
||||
'@intlify/message-compiler@11.0.0-rc.1':
|
||||
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@10.0.4':
|
||||
resolution: {integrity: sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@11.0.0-beta.1':
|
||||
resolution: {integrity: sha512-Md/4T/QOx7wZ7zqVzSsMx2M/9Mx/1nsgsjXS5SFIowFKydqUhMz7K+y7pMFh781aNYz+rGXYwad8E9/+InK9SA==}
|
||||
'@intlify/shared@11.0.0-rc.1':
|
||||
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/unplugin-vue-i18n@5.2.0':
|
||||
@@ -1353,6 +1359,9 @@ packages:
|
||||
'@types/debug@4.1.12':
|
||||
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':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
@@ -1916,13 +1925,9 @@ packages:
|
||||
resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
cidr-regex@4.0.3:
|
||||
resolution: {integrity: sha512-HOwDIy/rhKeMf6uOzxtv7FAbrz8zPjmVKfSpM+U7/bNBXC5rtOyr758jxcptiSx6ZZn5LOhPJT5WWxPAGDV8dw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
cidr-tools@6.4.2:
|
||||
resolution: {integrity: sha512-KZC8t2ipCqU2M+ISmTxRDGu9bku5MRU3V1cWyGEFJTZEzRhGvBJvVsbpZO5UAu12fExRFihtYGXAlgFFpmK9jw==}
|
||||
engines: {node: '>=16'}
|
||||
cidr-tools@11.0.2:
|
||||
resolution: {integrity: sha512-OLeM9EOXybbhMsGGBNRLCMjn8e+wFOXARIShF/sZwmJLsxWywqfE0By4BMftT6BFWpbcETWpW7TfM2KGCtrZDg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
clean-regexp@1.0.0:
|
||||
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==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
clone-regexp@3.0.0:
|
||||
resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@@ -1967,10 +1968,6 @@ packages:
|
||||
confbox@0.1.8:
|
||||
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:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
@@ -2436,10 +2433,6 @@ packages:
|
||||
function-bind@1.1.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2554,21 +2547,13 @@ packages:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
internal-ip@8.0.0:
|
||||
resolution: {integrity: sha512-e6c3zxr9COnnc29PIz9LffmALOt0XhIJdR7f83DyHcQksL3B40KGmU3Sr1lrHja3i7Zyqo+AbwKZ+nZiMvg/OA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
ip-bigint@7.3.0:
|
||||
resolution: {integrity: sha512-2qVAe0Q9+Y+5nGvmogwK9y4kefD5Ks5l/IG0Jo1lhU9gIF34jifhqrwXwzkIl+LC594Q6SyAlngs4p890xsXVw==}
|
||||
engines: {node: '>=16'}
|
||||
ip-bigint@8.2.0:
|
||||
resolution: {integrity: sha512-46EAEKzGNxojH5JaGEeCix49tL4h1W8ia5mhogZ68HroVAfyLj1E+SFFid4GuyK0mdIKjwcAITLqwg1wlkx2iQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ip-num@1.5.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
@@ -2610,18 +2595,10 @@ packages:
|
||||
engines: {node: '>=14.16'}
|
||||
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:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
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:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -3023,10 +3000,6 @@ packages:
|
||||
oxc-resolver@2.0.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3043,10 +3016,6 @@ packages:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3402,9 +3371,6 @@ packages:
|
||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
|
||||
string-natural-compare@3.0.1:
|
||||
resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3445,10 +3411,6 @@ packages:
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
super-regex@0.2.0:
|
||||
resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
superjson@2.2.1:
|
||||
resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -3500,10 +3462,6 @@ packages:
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
time-span@5.1.0:
|
||||
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
tinyexec@0.3.1:
|
||||
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)))':
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 11.0.0-beta.1
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/message-compiler': 11.0.0-rc.1
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
acorn: 8.14.0
|
||||
escodegen: 2.1.0
|
||||
estree-walker: 2.0.2
|
||||
@@ -4442,21 +4400,21 @@ snapshots:
|
||||
'@intlify/shared': 10.0.4
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@intlify/message-compiler@11.0.0-beta.1':
|
||||
'@intlify/message-compiler@11.0.0-rc.1':
|
||||
dependencies:
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@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))':
|
||||
dependencies:
|
||||
'@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/shared': 11.0.0-beta.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/shared': 11.0.0-rc.1
|
||||
'@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)
|
||||
'@typescript-eslint/scope-manager': 7.18.0
|
||||
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
|
||||
@@ -4479,11 +4437,11 @@ snapshots:
|
||||
- typescript
|
||||
- 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:
|
||||
'@babel/parser': 7.26.2
|
||||
optionalDependencies:
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
'@vue/compiler-dom': 3.5.12
|
||||
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:
|
||||
'@types/ms': 0.7.34
|
||||
|
||||
'@types/default-gateway@7.2.2': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
@@ -5649,16 +5609,9 @@ snapshots:
|
||||
|
||||
ci-info@4.0.0: {}
|
||||
|
||||
cidr-regex@4.0.3:
|
||||
cidr-tools@11.0.2:
|
||||
dependencies:
|
||||
ip-regex: 5.0.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
|
||||
ip-bigint: 8.2.0
|
||||
|
||||
clean-regexp@1.0.0:
|
||||
dependencies:
|
||||
@@ -5670,10 +5623,6 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
clone-regexp@3.0.0:
|
||||
dependencies:
|
||||
is-regexp: 3.1.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
@@ -5696,8 +5645,6 @@ snapshots:
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
convert-hrtime@5.0.0: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
copy-anything@3.0.5:
|
||||
@@ -6278,8 +6225,6 @@ snapshots:
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
function-timeout@0.1.1: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
@@ -6381,19 +6326,10 @@ snapshots:
|
||||
|
||||
indent-string@4.0.0: {}
|
||||
|
||||
internal-ip@8.0.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-bigint@8.2.0: {}
|
||||
|
||||
ip-num@1.5.1: {}
|
||||
|
||||
ip-regex@5.0.0: {}
|
||||
|
||||
is-arrayish@0.2.1: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
@@ -6424,15 +6360,8 @@ snapshots:
|
||||
dependencies:
|
||||
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-regexp@3.1.0: {}
|
||||
|
||||
is-stream@3.0.0: {}
|
||||
|
||||
is-what@4.1.16: {}
|
||||
@@ -6992,10 +6921,6 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-arm64-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:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
@@ -7012,8 +6937,6 @@ snapshots:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
p-timeout@5.1.0: {}
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
@@ -7332,8 +7255,6 @@ snapshots:
|
||||
|
||||
string-argv@0.3.2: {}
|
||||
|
||||
string-natural-compare@3.0.1: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -7378,12 +7299,6 @@ snapshots:
|
||||
pirates: 4.0.6
|
||||
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:
|
||||
dependencies:
|
||||
copy-anything: 3.0.5
|
||||
@@ -7452,10 +7367,6 @@ snapshots:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
time-span@5.1.0:
|
||||
dependencies:
|
||||
convert-hrtime: 5.0.0
|
||||
|
||||
tinyexec@0.3.1: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
|
||||
@@ -89,7 +89,7 @@ class TauriVpnService : VpnService() {
|
||||
|
||||
for (route in routes) {
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const COMMANDS: &[&str] = &[
|
||||
"prepare_vpn",
|
||||
"start_vpn",
|
||||
"stop_vpn",
|
||||
"register_listener",
|
||||
"registerListener",
|
||||
];
|
||||
|
||||
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>
|
||||
<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`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -314,6 +314,16 @@
|
||||
"type": "string",
|
||||
"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.",
|
||||
"type": "string",
|
||||
|
||||
Reference in New Issue
Block a user