mirror of
https://github.com/EasyTier/EasyTier.git
synced 2025-09-30 22:42:08 +08:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4dca25db86 | ||
![]() |
d87a440c04 | ||
![]() |
55efd62798 | ||
![]() |
70a41275c1 | ||
![]() |
dd941681ce | ||
![]() |
9824d0adaa | ||
![]() |
d2291628e0 | ||
![]() |
7ab8cad1af | ||
![]() |
2c017e0fc5 | ||
![]() |
d9453589ac | ||
![]() |
e344372616 | ||
![]() |
63821e56bc |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: 'Version for this release'
|
description: 'Version for this release'
|
||||||
type: string
|
type: string
|
||||||
default: 'v2.0.1'
|
default: 'v2.0.3'
|
||||||
required: true
|
required: true
|
||||||
make_latest:
|
make_latest:
|
||||||
description: 'Mark this release as latest'
|
description: 'Mark this release as latest'
|
||||||
|
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1539,7 +1539,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "easytier"
|
name = "easytier"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1631,7 +1631,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@@ -4,84 +4,23 @@
|
|||||||
"path": "."
|
"path": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "gui",
|
||||||
"path": "easytier-gui"
|
"path": "easytier-gui"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"name": "core",
|
||||||
"path": "easytier"
|
"path": "easytier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vpnservice",
|
||||||
|
"path": "tauri-plugin-vpnservice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"eslint.useFlatConfig": true,
|
"i18n-ally.sourceLanguage": "cn",
|
||||||
|
"i18n-ally.keystyle": "nested",
|
||||||
|
"i18n-ally.sortKeys": true,
|
||||||
|
// Disable the default formatter
|
||||||
"prettier.enable": false,
|
"prettier.enable": false,
|
||||||
"editor.formatOnSave": false,
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll.eslint": "explicit",
|
|
||||||
"source.organizeImports": "never"
|
|
||||||
},
|
|
||||||
"eslint.rules.customizations": [
|
|
||||||
{
|
|
||||||
"rule": "style/*",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "style/eol-last",
|
|
||||||
"severity": "error"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "format/*",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*-indent",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*-spacing",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*-spaces",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*-order",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*-dangle",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*-newline",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*quotes",
|
|
||||||
"severity": "off"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "*semi",
|
|
||||||
"severity": "off"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"eslint.validate": [
|
|
||||||
"code-workspace",
|
|
||||||
"javascript",
|
|
||||||
"javascriptreact",
|
|
||||||
"typescript",
|
|
||||||
"typescriptreact",
|
|
||||||
"vue",
|
|
||||||
"html",
|
|
||||||
"markdown",
|
|
||||||
"json",
|
|
||||||
"jsonc",
|
|
||||||
"yaml",
|
|
||||||
"toml",
|
|
||||||
"gql",
|
|
||||||
"graphql"
|
|
||||||
],
|
|
||||||
"i18n-ally.localesPaths": [
|
|
||||||
"easytier-gui/locales"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
82
easytier-gui/.vscode/settings.json
vendored
82
easytier-gui/.vscode/settings.json
vendored
@@ -1,5 +1,81 @@
|
|||||||
{
|
{
|
||||||
"i18n-ally.localesPaths": [
|
"cSpell.words": [
|
||||||
"locales"
|
"easytier",
|
||||||
|
"Vite",
|
||||||
|
"vueuse",
|
||||||
|
"pinia",
|
||||||
|
"demi",
|
||||||
|
"antfu",
|
||||||
|
"iconify",
|
||||||
|
"intlify",
|
||||||
|
"vitejs",
|
||||||
|
"unplugin",
|
||||||
|
"pnpm"
|
||||||
|
],
|
||||||
|
"i18n-ally.localesPaths": "locales",
|
||||||
|
"editor.formatOnSave": false,
|
||||||
|
// Auto fix
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
|
"eslint.rules.customizations": [
|
||||||
|
{
|
||||||
|
"rule": "style/*",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "format/*",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-indent",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-spacing",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-spaces",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-order",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-dangle",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-newline",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*quotes",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*semi",
|
||||||
|
"severity": "off"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// The following is optional.
|
||||||
|
// It's better to put under project setting `.vscode/settings.json`
|
||||||
|
// to avoid conflicts with working with different eslint configs
|
||||||
|
// that does not support all formats.
|
||||||
|
"eslint.validate": [
|
||||||
|
"javascript",
|
||||||
|
"javascriptreact",
|
||||||
|
"typescript",
|
||||||
|
"typescriptreact",
|
||||||
|
"vue",
|
||||||
|
"html",
|
||||||
|
"markdown",
|
||||||
|
"json",
|
||||||
|
"jsonc",
|
||||||
|
"yaml"
|
||||||
]
|
]
|
||||||
}
|
}
|
@@ -72,6 +72,8 @@ loss_rate: 丢包率
|
|||||||
status:
|
status:
|
||||||
version: 内核版本
|
version: 内核版本
|
||||||
local: 本机
|
local: 本机
|
||||||
|
server: 服务器
|
||||||
|
relay: 中继
|
||||||
|
|
||||||
run_network: 运行网络
|
run_network: 运行网络
|
||||||
stop_network: 停止网络
|
stop_network: 停止网络
|
||||||
@@ -91,3 +93,23 @@ about:
|
|||||||
license: 许可证
|
license: 许可证
|
||||||
description: 一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
description: 一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
||||||
check_update: 检查更新
|
check_update: 检查更新
|
||||||
|
|
||||||
|
event:
|
||||||
|
Unknown: 未知
|
||||||
|
TunDeviceReady: Tun设备就绪
|
||||||
|
TunDeviceError: Tun设备错误
|
||||||
|
PeerAdded: 对端添加
|
||||||
|
PeerRemoved: 对端移除
|
||||||
|
PeerConnAdded: 对端连接添加
|
||||||
|
PeerConnRemoved: 对端连接移除
|
||||||
|
ListenerAdded: 监听器添加
|
||||||
|
ListenerAddFailed: 监听器添加失败
|
||||||
|
ListenerAcceptFailed: 监听器接受连接失败
|
||||||
|
ConnectionAccepted: 连接已接受
|
||||||
|
ConnectionError: 连接错误
|
||||||
|
Connecting: 正在连接
|
||||||
|
ConnectError: 连接错误
|
||||||
|
VpnPortalClientConnected: VPN门户客户端已连接
|
||||||
|
VpnPortalClientDisconnected: VPN门户客户端已断开连接
|
||||||
|
DhcpIpv4Changed: DHCP IPv4地址更改
|
||||||
|
DhcpIpv4Conflicted: DHCP IPv4地址冲突
|
||||||
|
@@ -71,6 +71,8 @@ loss_rate: Loss Rate
|
|||||||
status:
|
status:
|
||||||
version: Version
|
version: Version
|
||||||
local: Local
|
local: Local
|
||||||
|
server: Server
|
||||||
|
relay: Relay
|
||||||
|
|
||||||
run_network: Run Network
|
run_network: Run Network
|
||||||
stop_network: Stop Network
|
stop_network: Stop Network
|
||||||
@@ -90,3 +92,23 @@ about:
|
|||||||
license: License
|
license: License
|
||||||
description: 'EasyTier is a simple, safe and decentralized VPN networking solution implemented with the Rust language and Tokio framework.'
|
description: 'EasyTier is a simple, safe and decentralized VPN networking solution implemented with the Rust language and Tokio framework.'
|
||||||
check_update: Check Update
|
check_update: Check Update
|
||||||
|
|
||||||
|
event:
|
||||||
|
Unknown: Unknown
|
||||||
|
TunDeviceReady: TunDeviceReady
|
||||||
|
TunDeviceError: TunDeviceError
|
||||||
|
PeerAdded: PeerAdded
|
||||||
|
PeerRemoved: PeerRemoved
|
||||||
|
PeerConnAdded: PeerConnAdded
|
||||||
|
PeerConnRemoved: PeerConnRemoved
|
||||||
|
ListenerAdded: ListenerAdded
|
||||||
|
ListenerAddFailed: ListenerAddFailed
|
||||||
|
ListenerAcceptFailed: ListenerAcceptFailed
|
||||||
|
ConnectionAccepted: ConnectionAccepted
|
||||||
|
ConnectionError: ConnectionError
|
||||||
|
Connecting: Connecting
|
||||||
|
ConnectError: ConnectError
|
||||||
|
VpnPortalClientConnected: VpnPortalClientConnected
|
||||||
|
VpnPortalClientDisconnected: VpnPortalClientDisconnected
|
||||||
|
DhcpIpv4Changed: DhcpIpv4Changed
|
||||||
|
DhcpIpv4Conflicted: DhcpIpv4Conflicted
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "easytier-gui",
|
"name": "easytier-gui",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"@tauri-apps/plugin-os": "2.0.0-rc.1",
|
"@tauri-apps/plugin-os": "2.0.0-rc.1",
|
||||||
"@tauri-apps/plugin-process": "2.0.0-rc.1",
|
"@tauri-apps/plugin-process": "2.0.0-rc.1",
|
||||||
"@tauri-apps/plugin-shell": "2.0.0-rc.1",
|
"@tauri-apps/plugin-shell": "2.0.0-rc.1",
|
||||||
|
"@vueuse/core": "^11.1.0",
|
||||||
"aura": "link:@primevue\\themes\\aura",
|
"aura": "link:@primevue\\themes\\aura",
|
||||||
"ip-num": "1.5.1",
|
"ip-num": "1.5.1",
|
||||||
"pinia": "^2.2.4",
|
"pinia": "^2.2.4",
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.4",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue-macros/volar": "^0.29.1",
|
"@vue-macros/volar": "0.30.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-format": "^0.1.2",
|
"eslint-plugin-format": "^0.1.2",
|
||||||
@@ -57,5 +58,6 @@
|
|||||||
"vite-plugin-vue-layouts": "^0.11.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vue-i18n": "^10.0.0",
|
"vue-i18n": "^10.0.0",
|
||||||
"vue-tsc": "^2.1.6"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4"
|
||||||
}
|
}
|
||||||
|
1159
easytier-gui/pnpm-lock.yaml
generated
1159
easytier-gui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
description = "EasyTier GUI"
|
description = "EasyTier GUI"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "migrated",
|
"identifier": "migrated",
|
||||||
"description": "permissions that were migrated from v1",
|
"description": "permissions that were migrated from v1",
|
||||||
"local": true,
|
"local": true,
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
"core:window:allow-show",
|
"core:window:allow-show",
|
||||||
"core:window:allow-hide",
|
"core:window:allow-hide",
|
||||||
"core:window:allow-set-focus",
|
"core:window:allow-set-focus",
|
||||||
|
"core:window:allow-set-title",
|
||||||
"core:app:default",
|
"core:app:default",
|
||||||
"core:resources:default",
|
"core:resources:default",
|
||||||
"core:menu:default",
|
"core:menu:default",
|
||||||
@@ -24,7 +26,6 @@
|
|||||||
"shell:default",
|
"shell:default",
|
||||||
"process:default",
|
"process:default",
|
||||||
"clipboard-manager:default",
|
"clipboard-manager:default",
|
||||||
"core:tray:default",
|
|
||||||
"core:tray:allow-new",
|
"core:tray:allow-new",
|
||||||
"core:tray:allow-set-menu",
|
"core:tray:allow-set-menu",
|
||||||
"core:tray:allow-set-title",
|
"core:tray:allow-set-title",
|
||||||
|
@@ -41,6 +41,7 @@ struct NetworkConfig {
|
|||||||
|
|
||||||
dhcp: bool,
|
dhcp: bool,
|
||||||
virtual_ipv4: String,
|
virtual_ipv4: String,
|
||||||
|
network_length: i32,
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
network_name: String,
|
network_name: String,
|
||||||
network_secret: String,
|
network_secret: String,
|
||||||
@@ -83,9 +84,15 @@ impl NetworkConfig {
|
|||||||
|
|
||||||
if !self.dhcp {
|
if !self.dhcp {
|
||||||
if self.virtual_ipv4.len() > 0 {
|
if self.virtual_ipv4.len() > 0 {
|
||||||
cfg.set_ipv4(Some(self.virtual_ipv4.parse().with_context(|| {
|
let ip = format!("{}/{}", self.virtual_ipv4, self.network_length)
|
||||||
format!("failed to parse ipv4 address: {}", self.virtual_ipv4)
|
.parse()
|
||||||
})?))
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to parse ipv4 inet address: {}, {}",
|
||||||
|
self.virtual_ipv4, self.network_length
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
cfg.set_ipv4(Some(ip));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
"createUpdaterArtifacts": false
|
"createUpdaterArtifacts": false
|
||||||
},
|
},
|
||||||
"productName": "easytier-gui",
|
"productName": "easytier-gui",
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"identifier": "com.kkrainbow.easytier",
|
"identifier": "com.kkrainbow.easytier",
|
||||||
"plugins": {},
|
"plugins": {},
|
||||||
"app": {
|
"app": {
|
||||||
|
@@ -1,3 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
|
import pkg from '~/../package.json'
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
await getCurrentWindow().setTitle(`Easytier GUI: v${pkg.version}`)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
|
6
easytier-gui/src/auto-imports.d.ts
vendored
6
easytier-gui/src/auto-imports.d.ts
vendored
@@ -21,6 +21,7 @@ declare global {
|
|||||||
const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
|
const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
|
||||||
const defineStore: typeof import('pinia')['defineStore']
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const event2human: typeof import('./composables/utils')['event2human']
|
||||||
const generateMenuItem: typeof import('./composables/tray')['generateMenuItem']
|
const generateMenuItem: typeof import('./composables/tray')['generateMenuItem']
|
||||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
@@ -44,6 +45,8 @@ declare global {
|
|||||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||||
const markRaw: typeof import('vue')['markRaw']
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
const nextTick: typeof import('vue')['nextTick']
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const num2ipv4: typeof import('./composables/utils')['num2ipv4']
|
||||||
|
const num2ipv6: typeof import('./composables/utils')['num2ipv6']
|
||||||
const onActivated: typeof import('vue')['onActivated']
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||||
@@ -81,6 +84,7 @@ declare global {
|
|||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const timeAgoCn: typeof import('./composables/utils')['timeAgoCn']
|
||||||
const toRaw: typeof import('vue')['toRaw']
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
const toRef: typeof import('vue')['toRef']
|
const toRef: typeof import('vue')['toRef']
|
||||||
const toRefs: typeof import('vue')['toRefs']
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
@@ -150,6 +154,8 @@ declare module 'vue' {
|
|||||||
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
|
readonly num2ipv4: UnwrapRef<typeof import('./composables/utils')['num2ipv4']>
|
||||||
|
readonly num2ipv6: UnwrapRef<typeof import('./composables/utils')['num2ipv6']>
|
||||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import InputGroup from 'primevue/inputgroup'
|
import InputGroup from 'primevue/inputgroup'
|
||||||
import InputGroupAddon from 'primevue/inputgroupaddon'
|
import InputGroupAddon from 'primevue/inputgroupaddon'
|
||||||
import { ping } from 'tauri-plugin-vpnservice-api'
|
|
||||||
import { getOsHostname } from '~/composables/network'
|
import { getOsHostname } from '~/composables/network'
|
||||||
|
|
||||||
import { NetworkingMethod } from '~/types/network'
|
import { NetworkingMethod } from '~/types/network'
|
||||||
@@ -42,10 +41,11 @@ function searchUrlSuggestions(e: { query: string }): string[] {
|
|||||||
if (query.match(/^\w+:.*/)) {
|
if (query.match(/^\w+:.*/)) {
|
||||||
// if query is a valid url, then add to suggestions
|
// if query is a valid url, then add to suggestions
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
new URL(query)
|
new URL(query)
|
||||||
ret.push(query)
|
ret.push(query)
|
||||||
}
|
}
|
||||||
catch (e) {}
|
catch {}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (const proto in protos) {
|
for (const proto in protos) {
|
||||||
@@ -85,6 +85,20 @@ function searchPeerSuggestions(e: { query: string }) {
|
|||||||
peerSuggestions.value = searchUrlSuggestions(e)
|
peerSuggestions.value = searchUrlSuggestions(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inetSuggestions = ref([''])
|
||||||
|
|
||||||
|
function searchInetSuggestions(e: { query: string }) {
|
||||||
|
if (e.query.search('/') >= 0) {
|
||||||
|
inetSuggestions.value = [e.query]
|
||||||
|
} else {
|
||||||
|
const ret = []
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
ret.push(`${e.query}/${i}`)
|
||||||
|
}
|
||||||
|
inetSuggestions.value = ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const listenerSuggestions = ref([''])
|
const listenerSuggestions = ref([''])
|
||||||
|
|
||||||
function searchListenerSuggestiong(e: { query: string }) {
|
function searchListenerSuggestiong(e: { query: string }) {
|
||||||
@@ -128,18 +142,12 @@ const osHostname = ref<string>('')
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
osHostname.value = await getOsHostname()
|
osHostname.value = await getOsHostname()
|
||||||
osHostname.value = await ping('ffdklsajflkdsjl') || ''
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-column h-full">
|
<div class="flex flex-column h-full">
|
||||||
<div class="flex flex-column">
|
<div class="flex flex-column">
|
||||||
<div class="w-10/12 self-center mb-3">
|
|
||||||
<Message severity="warn">
|
|
||||||
{{ t('dhcp_experimental_warning') }}
|
|
||||||
</Message>
|
|
||||||
</div>
|
|
||||||
<div class="w-10/12 self-center ">
|
<div class="w-10/12 self-center ">
|
||||||
<Panel :header="t('basic_settings')">
|
<Panel :header="t('basic_settings')">
|
||||||
<div class="flex flex-column gap-y-2">
|
<div class="flex flex-column gap-y-2">
|
||||||
@@ -159,8 +167,9 @@ onMounted(async () => {
|
|||||||
aria-describedby="virtual_ipv4-help"
|
aria-describedby="virtual_ipv4-help"
|
||||||
/>
|
/>
|
||||||
<InputGroupAddon>
|
<InputGroupAddon>
|
||||||
<span>/24</span>
|
<span>/</span>
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
|
<InputNumber v-model="curNetwork.network_length" :disabled="curNetwork.dhcp" inputId="horizontal-buttons" showButtons :step="1" mode="decimal" :min="1" :max="32" fluid class="max-w-20"/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,9 +236,10 @@ onMounted(async () => {
|
|||||||
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
||||||
<div class="flex flex-column gap-2 grow p-fluid">
|
<div class="flex flex-column gap-2 grow p-fluid">
|
||||||
<label for="username">{{ t('proxy_cidrs') }}</label>
|
<label for="username">{{ t('proxy_cidrs') }}</label>
|
||||||
<Chips
|
<AutoComplete
|
||||||
id="chips" v-model="curNetwork.proxy_cidrs"
|
id="subnet-proxy"
|
||||||
:placeholder="t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full"
|
v-model="curNetwork.proxy_cidrs" :placeholder="t('chips_placeholder', ['10.0.0.0/24'])"
|
||||||
|
class="w-full" multiple fluid :suggestions="inetSuggestions" @complete="searchInetSuggestions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
32
easytier-gui/src/components/HumanEvent.vue
Normal file
32
easytier-gui/src/components/HumanEvent.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { EventType } from '~/types/network'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
event: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const eventKey = computed(() => {
|
||||||
|
const key = Object.keys(props.event)[0]
|
||||||
|
return Object.keys(EventType).includes(key) ? key : 'Unknown'
|
||||||
|
})
|
||||||
|
|
||||||
|
const eventValue = computed(() => {
|
||||||
|
const value = props.event[eventKey.value]
|
||||||
|
return typeof value === 'object' ? value : value
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Fieldset :legend="t(`event.${eventKey}`)">
|
||||||
|
<template v-if="eventKey !== 'Unknown'">
|
||||||
|
<div v-if="event.DhcpIpv4Changed">
|
||||||
|
{{ `${eventValue[0]} -> ${eventValue[1]}` }}
|
||||||
|
</div>
|
||||||
|
<pre v-else>{{ eventValue }}</pre>
|
||||||
|
</template>
|
||||||
|
<pre v-else>{{ eventValue }}</pre>
|
||||||
|
</Fieldset>
|
||||||
|
</template>
|
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useTimeAgo } from '@vueuse/core'
|
||||||
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
||||||
import type { NodeInfo, PeerRoutePair } from '~/types/network'
|
import type { NodeInfo, PeerRoutePair } from '~/types/network'
|
||||||
|
|
||||||
@@ -111,6 +112,13 @@ function version(info: PeerRoutePair) {
|
|||||||
return info.route.version === '' ? 'unknown' : info.route.version
|
return info.route.version === '' ? 'unknown' : info.route.version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ipFormat(info: PeerRoutePair) {
|
||||||
|
const ip = info.route.ipv4_addr
|
||||||
|
if (typeof ip === 'string')
|
||||||
|
return ip
|
||||||
|
return ip ? `${num2ipv4(ip.address)}/${ip.network_length}` : ''
|
||||||
|
}
|
||||||
|
|
||||||
const myNodeInfo = computed(() => {
|
const myNodeInfo = computed(() => {
|
||||||
if (!curNetworkInst.value)
|
if (!curNetworkInst.value)
|
||||||
return {} as NodeInfo
|
return {} as NodeInfo
|
||||||
@@ -151,7 +159,7 @@ const myNodeInfoChips = computed(() => {
|
|||||||
const local_ipv4s = my_node_info.ips?.interface_ipv4s
|
const local_ipv4s = my_node_info.ips?.interface_ipv4s
|
||||||
for (const [idx, ip] of local_ipv4s?.entries()) {
|
for (const [idx, ip] of local_ipv4s?.entries()) {
|
||||||
chips.push({
|
chips.push({
|
||||||
label: `Local IPv4 ${idx}: ${IPv4.fromNumber(ip.addr)}`,
|
label: `Local IPv4 ${idx}: ${num2ipv4(ip)}`,
|
||||||
icon: '',
|
icon: '',
|
||||||
} as Chip)
|
} as Chip)
|
||||||
}
|
}
|
||||||
@@ -160,11 +168,7 @@ const myNodeInfoChips = computed(() => {
|
|||||||
const local_ipv6s = my_node_info.ips?.interface_ipv6s
|
const local_ipv6s = my_node_info.ips?.interface_ipv6s
|
||||||
for (const [idx, ip] of local_ipv6s?.entries()) {
|
for (const [idx, ip] of local_ipv6s?.entries()) {
|
||||||
chips.push({
|
chips.push({
|
||||||
label: `Local IPv6 ${idx}: ${IPv6.fromBigInt((BigInt(ip.part1) << BigInt(96))
|
label: `Local IPv6 ${idx}: ${num2ipv6(ip)}`,
|
||||||
+ (BigInt(ip.part2) << BigInt(64))
|
|
||||||
+ (BigInt(ip.part3) << BigInt(32))
|
|
||||||
+ BigInt(ip.part4),
|
|
||||||
)}`,
|
|
||||||
icon: '',
|
icon: '',
|
||||||
} as Chip)
|
} as Chip)
|
||||||
}
|
}
|
||||||
@@ -210,6 +214,8 @@ const myNodeInfoChips = computed(() => {
|
|||||||
PortRestricted = 5,
|
PortRestricted = 5,
|
||||||
Symmetric = 6,
|
Symmetric = 6,
|
||||||
SymUdpFirewall = 7,
|
SymUdpFirewall = 7,
|
||||||
|
SymmetricEasyInc = 8,
|
||||||
|
SymmetricEasyDec = 9,
|
||||||
};
|
};
|
||||||
const udpNatType: NatType = my_node_info.stun_info?.udp_nat_type
|
const udpNatType: NatType = my_node_info.stun_info?.udp_nat_type
|
||||||
if (udpNatType !== undefined) {
|
if (udpNatType !== undefined) {
|
||||||
@@ -222,6 +228,8 @@ const myNodeInfoChips = computed(() => {
|
|||||||
[NatType.PortRestricted]: 'Port Restricted',
|
[NatType.PortRestricted]: 'Port Restricted',
|
||||||
[NatType.Symmetric]: 'Symmetric',
|
[NatType.Symmetric]: 'Symmetric',
|
||||||
[NatType.SymUdpFirewall]: 'Symmetric UDP Firewall',
|
[NatType.SymUdpFirewall]: 'Symmetric UDP Firewall',
|
||||||
|
[NatType.SymmetricEasyInc]: 'Symmetric Easy Inc',
|
||||||
|
[NatType.SymmetricEasyDec]: 'Symmetric Easy Dec',
|
||||||
}
|
}
|
||||||
|
|
||||||
chips.push({
|
chips.push({
|
||||||
@@ -312,16 +320,18 @@ function showEventLogs() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" :style="{ width: '70%' }">
|
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto">
|
||||||
<Panel>
|
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
||||||
<ScrollPanel style="width: 100%; height: 400px">
|
<pre>{{ dialogContent }}</pre>
|
||||||
<pre>{{ dialogContent }}</pre>
|
</ScrollPanel>
|
||||||
</ScrollPanel>
|
<Timeline v-else :value="dialogContent">
|
||||||
</Panel>
|
<template #opposite="slotProps">
|
||||||
<Divider />
|
<small class="text-surface-500 dark:text-surface-400">{{ useTimeAgo(Date.parse(slotProps.item[0])) }}</small>
|
||||||
<div class="flex justify-content-end gap-2">
|
</template>
|
||||||
<Button type="button" :label="t('close')" @click="dialogVisible = false" />
|
<template #content="slotProps">
|
||||||
</div>
|
<HumanEvent :event="slotProps.item[1]" />
|
||||||
|
</template>
|
||||||
|
</Timeline>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Card v-if="curNetworkInst?.error_msg">
|
<Card v-if="curNetworkInst?.error_msg">
|
||||||
@@ -404,18 +414,46 @@ function showEventLogs() {
|
|||||||
{{ t('peer_info') }}
|
{{ t('peer_info') }}
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<DataTable :value="peerRouteInfos" column-resize-mode="fit" table-style="width: 100%">
|
<DataTable :value="peerRouteInfos" column-resize-mode="fit" table-class="w-full">
|
||||||
<Column field="route.ipv4_addr" style="width: 100px;" :header="t('virtual_ipv4')" />
|
<Column :field="ipFormat" :header="t('virtual_ipv4')" />
|
||||||
<Column field="route.hostname" style="max-width: 250px;" :header="t('hostname')" />
|
<Column :header="t('hostname')">
|
||||||
<Column :field="routeCost" style="width: 100px;" :header="t('route_cost')" />
|
<template #body="slotProps">
|
||||||
<Column :field="latencyMs" style="width: 80px;" :header="t('latency')" />
|
<div
|
||||||
<Column :field="txBytes" style="width: 80px;" :header="t('upload_bytes')" />
|
v-if="!slotProps.data.route.cost || !slotProps.data.route.feature_flag.is_public_server"
|
||||||
<Column :field="rxBytes" style="width: 80px;" :header="t('download_bytes')" />
|
v-tooltip="slotProps.data.route.hostname"
|
||||||
<Column :field="lossRate" style="width: 100px;" :header="t('loss_rate')" />
|
>
|
||||||
<Column :field="version" style="width: 100px;" :header="t('status.version')" />
|
{{
|
||||||
|
slotProps.data.route.hostname }}
|
||||||
|
</div>
|
||||||
|
<div v-else v-tooltip="slotProps.data.route.hostname" class="space-x-1">
|
||||||
|
<Tag v-if="slotProps.data.route.feature_flag.is_public_server" severity="info" value="Info">
|
||||||
|
{{ t('status.server') }}
|
||||||
|
</Tag>
|
||||||
|
<Tag v-if="slotProps.data.route.no_relay_data" severity="warn" value="Warn">
|
||||||
|
{{ t('status.relay') }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column :field="routeCost" :header="t('route_cost')" />
|
||||||
|
<Column :field="latencyMs" :header="t('latency')" />
|
||||||
|
<Column :field="txBytes" :header="t('upload_bytes')" />
|
||||||
|
<Column :field="rxBytes" :header="t('download_bytes')" />
|
||||||
|
<Column :field="lossRate" :header="t('loss_rate')" />
|
||||||
|
<Column :header="t('status.version')">
|
||||||
|
<template #body="slotProps">
|
||||||
|
<span>{{ version(slotProps.data) }}</span>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.p-timeline :deep(.p-timeline-event-opposite) {
|
||||||
|
@apply flex-none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -48,7 +48,7 @@ async function doStartVpn(ipv4Addr: string, cidr: number, routes: string[]) {
|
|||||||
|
|
||||||
console.log('start vpn')
|
console.log('start vpn')
|
||||||
const start_ret = await start_vpn({
|
const start_ret = await start_vpn({
|
||||||
ipv4Addr: `${ipv4Addr}/${cidr}`,
|
ipv4Addr: `${ipv4Addr}`,
|
||||||
routes,
|
routes,
|
||||||
disallowedApplications: ['com.kkrainbow.easytier'],
|
disallowedApplications: ['com.kkrainbow.easytier'],
|
||||||
mtu: 1300,
|
mtu: 1300,
|
||||||
|
15
easytier-gui/src/composables/utils.ts
Normal file
15
easytier-gui/src/composables/utils.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
||||||
|
import type { Ipv4Addr, Ipv6Addr } from '~/types/network'
|
||||||
|
|
||||||
|
export function num2ipv4(ip: Ipv4Addr) {
|
||||||
|
return IPv4.fromNumber(ip.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function num2ipv6(ip: Ipv6Addr) {
|
||||||
|
return IPv6.fromBigInt(
|
||||||
|
(BigInt(ip.part1) << BigInt(96))
|
||||||
|
+ (BigInt(ip.part2) << BigInt(64))
|
||||||
|
+ (BigInt(ip.part3) << BigInt(32))
|
||||||
|
+ BigInt(ip.part4),
|
||||||
|
)
|
||||||
|
}
|
@@ -2,7 +2,16 @@ import { disable, enable, isEnabled } from '@tauri-apps/plugin-autostart'
|
|||||||
|
|
||||||
export async function loadAutoLaunchStatusAsync(target_enable: boolean): Promise<boolean> {
|
export async function loadAutoLaunchStatusAsync(target_enable: boolean): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
target_enable ? await enable() : await disable()
|
if (target_enable) {
|
||||||
|
await enable()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 消除没有配置自启动时进行关闭操作报错
|
||||||
|
try {
|
||||||
|
await disable()
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
localStorage.setItem('auto_launch', JSON.stringify(await isEnabled()))
|
localStorage.setItem('auto_launch', JSON.stringify(await isEnabled()))
|
||||||
return isEnabled()
|
return isEnabled()
|
||||||
}
|
}
|
||||||
|
@@ -181,7 +181,7 @@ const setting_menu_items = ref([
|
|||||||
label: () => t('logging_open_dir'),
|
label: () => t('logging_open_dir'),
|
||||||
icon: 'pi pi-folder-open',
|
icon: 'pi pi-folder-open',
|
||||||
command: async () => {
|
command: async () => {
|
||||||
console.log('open log dir', await appLogDir())
|
// console.log('open log dir', await appLogDir())
|
||||||
await open(await appLogDir())
|
await open(await appLogDir())
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@@ -11,6 +11,7 @@ export interface NetworkConfig {
|
|||||||
|
|
||||||
dhcp: boolean
|
dhcp: boolean
|
||||||
virtual_ipv4: string
|
virtual_ipv4: string
|
||||||
|
network_length: number,
|
||||||
hostname?: string
|
hostname?: string
|
||||||
network_name: string
|
network_name: string
|
||||||
network_secret: string
|
network_secret: string
|
||||||
@@ -42,6 +43,7 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
|||||||
|
|
||||||
dhcp: true,
|
dhcp: true,
|
||||||
virtual_ipv4: '',
|
virtual_ipv4: '',
|
||||||
|
network_length: 24,
|
||||||
network_name: 'easytier',
|
network_name: 'easytier',
|
||||||
network_secret: '',
|
network_secret: '',
|
||||||
|
|
||||||
@@ -137,7 +139,10 @@ export interface StunInfo {
|
|||||||
|
|
||||||
export interface Route {
|
export interface Route {
|
||||||
peer_id: number
|
peer_id: number
|
||||||
ipv4_addr: string
|
ipv4_addr: {
|
||||||
|
address: Ipv4Addr
|
||||||
|
network_length: number
|
||||||
|
} | string | null
|
||||||
next_hop_peer_id: number
|
next_hop_peer_id: number
|
||||||
cost: number
|
cost: number
|
||||||
proxy_cidrs: string[]
|
proxy_cidrs: string[]
|
||||||
@@ -155,6 +160,7 @@ export interface PeerInfo {
|
|||||||
export interface PeerConnInfo {
|
export interface PeerConnInfo {
|
||||||
conn_id: string
|
conn_id: string
|
||||||
my_peer_id: number
|
my_peer_id: number
|
||||||
|
is_client: boolean
|
||||||
peer_id: number
|
peer_id: number
|
||||||
features: string[]
|
features: string[]
|
||||||
tunnel?: TunnelInfo
|
tunnel?: TunnelInfo
|
||||||
@@ -180,3 +186,28 @@ export interface PeerConnStats {
|
|||||||
tx_packets: number
|
tx_packets: number
|
||||||
latency_us: number
|
latency_us: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EventType {
|
||||||
|
TunDeviceReady = 'TunDeviceReady', // string
|
||||||
|
TunDeviceError = 'TunDeviceError', // string
|
||||||
|
|
||||||
|
PeerAdded = 'PeerAdded', // number
|
||||||
|
PeerRemoved = 'PeerRemoved', // number
|
||||||
|
PeerConnAdded = 'PeerConnAdded', // PeerConnInfo
|
||||||
|
PeerConnRemoved = 'PeerConnRemoved', // PeerConnInfo
|
||||||
|
|
||||||
|
ListenerAdded = 'ListenerAdded', // any
|
||||||
|
ListenerAddFailed = 'ListenerAddFailed', // any, string
|
||||||
|
ListenerAcceptFailed = 'ListenerAcceptFailed', // any, string
|
||||||
|
ConnectionAccepted = 'ConnectionAccepted', // string, string
|
||||||
|
ConnectionError = 'ConnectionError', // string, string, string
|
||||||
|
|
||||||
|
Connecting = 'Connecting', // any
|
||||||
|
ConnectError = 'ConnectError', // string, string, string
|
||||||
|
|
||||||
|
VpnPortalClientConnected = 'VpnPortalClientConnected', // string, string
|
||||||
|
VpnPortalClientDisconnected = 'VpnPortalClientDisconnected', // string, string, string
|
||||||
|
|
||||||
|
DhcpIpv4Changed = 'DhcpIpv4Changed', // ipv4 | null, ipv4 | null
|
||||||
|
DhcpIpv4Conflicted = 'DhcpIpv4Conflicted', // ipv4 | null
|
||||||
|
}
|
||||||
|
@@ -3,7 +3,7 @@ name = "easytier"
|
|||||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||||
homepage = "https://github.com/EasyTier/EasyTier"
|
homepage = "https://github.com/EasyTier/EasyTier"
|
||||||
repository = "https://github.com/EasyTier/EasyTier"
|
repository = "https://github.com/EasyTier/EasyTier"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["kkrainbow"]
|
authors = ["kkrainbow"]
|
||||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||||
|
@@ -1,10 +1,5 @@
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use std::{
|
use std::{env, io::Cursor, path::PathBuf};
|
||||||
env,
|
|
||||||
fs::File,
|
|
||||||
io::{copy, Cursor},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
struct WindowsBuild {}
|
struct WindowsBuild {}
|
||||||
|
@@ -23,8 +23,8 @@ pub trait ConfigLoader: Send + Sync {
|
|||||||
fn get_netns(&self) -> Option<String>;
|
fn get_netns(&self) -> Option<String>;
|
||||||
fn set_netns(&self, ns: Option<String>);
|
fn set_netns(&self, ns: Option<String>);
|
||||||
|
|
||||||
fn get_ipv4(&self) -> Option<std::net::Ipv4Addr>;
|
fn get_ipv4(&self) -> Option<cidr::Ipv4Inet>;
|
||||||
fn set_ipv4(&self, addr: Option<std::net::Ipv4Addr>);
|
fn set_ipv4(&self, addr: Option<cidr::Ipv4Inet>);
|
||||||
|
|
||||||
fn get_dhcp(&self) -> bool;
|
fn get_dhcp(&self) -> bool;
|
||||||
fn set_dhcp(&self, dhcp: bool);
|
fn set_dhcp(&self, dhcp: bool);
|
||||||
@@ -324,16 +324,23 @@ impl ConfigLoader for TomlConfigLoader {
|
|||||||
self.config.lock().unwrap().netns = ns;
|
self.config.lock().unwrap().netns = ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ipv4(&self) -> Option<std::net::Ipv4Addr> {
|
fn get_ipv4(&self) -> Option<cidr::Ipv4Inet> {
|
||||||
let locked_config = self.config.lock().unwrap();
|
let locked_config = self.config.lock().unwrap();
|
||||||
locked_config
|
locked_config
|
||||||
.ipv4
|
.ipv4
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.parse().ok())
|
.map(|s| s.parse().ok())
|
||||||
.flatten()
|
.flatten()
|
||||||
|
.map(|c: cidr::Ipv4Inet| {
|
||||||
|
if c.network_length() == 32 {
|
||||||
|
cidr::Ipv4Inet::new(c.address(), 24).unwrap()
|
||||||
|
} else {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_ipv4(&self, addr: Option<std::net::Ipv4Addr>) {
|
fn set_ipv4(&self, addr: Option<cidr::Ipv4Inet>) {
|
||||||
self.config.lock().unwrap().ipv4 = if let Some(addr) = addr {
|
self.config.lock().unwrap().ipv4 = if let Some(addr) = addr {
|
||||||
Some(addr.to_string())
|
Some(addr.to_string())
|
||||||
} else {
|
} else {
|
||||||
@@ -590,7 +597,7 @@ level = "warn"
|
|||||||
assert!(ret.is_ok());
|
assert!(ret.is_ok());
|
||||||
|
|
||||||
let ret = ret.unwrap();
|
let ret = ret.unwrap();
|
||||||
assert_eq!("10.144.144.10", ret.get_ipv4().unwrap().to_string());
|
assert_eq!("10.144.144.10/24", ret.get_ipv4().unwrap().to_string());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec!["tcp://0.0.0.0:11010", "udp://0.0.0.0:11010"],
|
vec!["tcp://0.0.0.0:11010", "udp://0.0.0.0:11010"],
|
||||||
|
@@ -40,8 +40,8 @@ pub enum GlobalCtxEvent {
|
|||||||
VpnPortalClientConnected(String, String), // (portal, client ip)
|
VpnPortalClientConnected(String, String), // (portal, client ip)
|
||||||
VpnPortalClientDisconnected(String, String), // (portal, client ip)
|
VpnPortalClientDisconnected(String, String), // (portal, client ip)
|
||||||
|
|
||||||
DhcpIpv4Changed(Option<std::net::Ipv4Addr>, Option<std::net::Ipv4Addr>), // (old, new)
|
DhcpIpv4Changed(Option<cidr::Ipv4Inet>, Option<cidr::Ipv4Inet>), // (old, new)
|
||||||
DhcpIpv4Conflicted(Option<std::net::Ipv4Addr>),
|
DhcpIpv4Conflicted(Option<cidr::Ipv4Inet>),
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventBus = tokio::sync::broadcast::Sender<GlobalCtxEvent>;
|
type EventBus = tokio::sync::broadcast::Sender<GlobalCtxEvent>;
|
||||||
@@ -56,7 +56,7 @@ pub struct GlobalCtx {
|
|||||||
|
|
||||||
event_bus: EventBus,
|
event_bus: EventBus,
|
||||||
|
|
||||||
cached_ipv4: AtomicCell<Option<std::net::Ipv4Addr>>,
|
cached_ipv4: AtomicCell<Option<cidr::Ipv4Inet>>,
|
||||||
cached_proxy_cidrs: AtomicCell<Option<Vec<cidr::IpCidr>>>,
|
cached_proxy_cidrs: AtomicCell<Option<Vec<cidr::IpCidr>>>,
|
||||||
|
|
||||||
ip_collector: Arc<IPCollector>,
|
ip_collector: Arc<IPCollector>,
|
||||||
@@ -139,7 +139,7 @@ impl GlobalCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ipv4(&self) -> Option<std::net::Ipv4Addr> {
|
pub fn get_ipv4(&self) -> Option<cidr::Ipv4Inet> {
|
||||||
if let Some(ret) = self.cached_ipv4.load() {
|
if let Some(ret) = self.cached_ipv4.load() {
|
||||||
return Some(ret);
|
return Some(ret);
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ impl GlobalCtx {
|
|||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_ipv4(&self, addr: Option<std::net::Ipv4Addr>) {
|
pub fn set_ipv4(&self, addr: Option<cidr::Ipv4Inet>) {
|
||||||
self.config.set_ipv4(addr);
|
self.config.set_ipv4(addr);
|
||||||
self.cached_ipv4.store(None);
|
self.cached_ipv4.store(None);
|
||||||
}
|
}
|
||||||
|
@@ -56,6 +56,8 @@ impl HostResolverIter {
|
|||||||
self.ips = ips
|
self.ips = ips
|
||||||
.filter(|x| x.is_ipv4())
|
.filter(|x| x.is_ipv4())
|
||||||
.choose_multiple(&mut rand::thread_rng(), self.max_ip_per_domain as usize);
|
.choose_multiple(&mut rand::thread_rng(), self.max_ip_per_domain as usize);
|
||||||
|
|
||||||
|
if self.ips.is_empty() {return self.next().await;}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(?host, ?e, "lookup host for stun failed");
|
tracing::warn!(?host, ?e, "lookup host for stun failed");
|
||||||
|
@@ -185,7 +185,7 @@ impl PunchBothEasySymHoleClient {
|
|||||||
my_nat_info: UdpNatType,
|
my_nat_info: UdpNatType,
|
||||||
peer_nat_info: UdpNatType,
|
peer_nat_info: UdpNatType,
|
||||||
is_busy: &mut bool,
|
is_busy: &mut bool,
|
||||||
) -> Result<Box<dyn Tunnel>, anyhow::Error> {
|
) -> Result<Option<Box<dyn Tunnel>>, anyhow::Error> {
|
||||||
*is_busy = false;
|
*is_busy = false;
|
||||||
|
|
||||||
let udp_array = UdpSocketArray::new(
|
let udp_array = UdpSocketArray::new(
|
||||||
@@ -301,7 +301,7 @@ impl PunchBothEasySymHoleClient {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tunnel) => {
|
Ok(tunnel) => {
|
||||||
return Ok(tunnel);
|
return Ok(Some(tunnel));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(?e, "failed to connect with socket");
|
tracing::error!(?e, "failed to connect with socket");
|
||||||
@@ -312,7 +312,7 @@ impl PunchBothEasySymHoleClient {
|
|||||||
udp_array.add_new_socket(socket.socket).await?;
|
udp_array.add_new_socket(socket.socket).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::bail!("failed to punch hole for both easy sym");
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +325,7 @@ pub mod tests {
|
|||||||
|
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
|
use crate::connector::udp_hole_punch::RUN_TESTING;
|
||||||
use crate::{
|
use crate::{
|
||||||
connector::udp_hole_punch::{
|
connector::udp_hole_punch::{
|
||||||
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector,
|
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector,
|
||||||
@@ -338,6 +339,8 @@ pub mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial(hole_punch)]
|
#[serial_test::serial(hole_punch)]
|
||||||
async fn hole_punching_easy_sym(#[values("true", "false")] is_inc: bool) {
|
async fn hole_punching_easy_sym(#[values("true", "false")] is_inc: bool) {
|
||||||
|
RUN_TESTING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
let p_a = create_mock_peer_manager_with_mock_stun(if is_inc {
|
let p_a = create_mock_peer_manager_with_mock_stun(if is_inc {
|
||||||
NatType::SymmetricEasyInc
|
NatType::SymmetricEasyInc
|
||||||
} else {
|
} else {
|
||||||
|
@@ -94,7 +94,7 @@ impl PunchConeHoleClient {
|
|||||||
pub(crate) async fn do_hole_punching(
|
pub(crate) async fn do_hole_punching(
|
||||||
&self,
|
&self,
|
||||||
dst_peer_id: PeerId,
|
dst_peer_id: PeerId,
|
||||||
) -> Result<Box<dyn Tunnel>, anyhow::Error> {
|
) -> Result<Option<Box<dyn Tunnel>>, anyhow::Error> {
|
||||||
tracing::info!(?dst_peer_id, "start hole punching");
|
tracing::info!(?dst_peer_id, "start hole punching");
|
||||||
let tid = rand::random();
|
let tid = rand::random();
|
||||||
|
|
||||||
@@ -110,12 +110,18 @@ impl PunchConeHoleClient {
|
|||||||
.with_context(|| anyhow::anyhow!("failed to get local port from udp array"))?;
|
.with_context(|| anyhow::anyhow!("failed to get local port from udp array"))?;
|
||||||
let local_port = local_addr.port();
|
let local_port = local_addr.port();
|
||||||
|
|
||||||
|
drop(local_socket);
|
||||||
let local_mapped_addr = global_ctx
|
let local_mapped_addr = global_ctx
|
||||||
.get_stun_info_collector()
|
.get_stun_info_collector()
|
||||||
.get_udp_port_mapping(local_port)
|
.get_udp_port_mapping(local_port)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "failed to get udp port mapping")?;
|
.with_context(|| "failed to get udp port mapping")?;
|
||||||
|
|
||||||
|
let local_socket = {
|
||||||
|
let _g = self.peer_mgr.get_global_ctx().net_ns.guard();
|
||||||
|
Arc::new(UdpSocket::bind(local_addr).await?)
|
||||||
|
};
|
||||||
|
|
||||||
// client -> server: tell server the mapped port, server will return the mapped address of listening port.
|
// client -> server: tell server the mapped port, server will return the mapped address of listening port.
|
||||||
let rpc_stub = self
|
let rpc_stub = self
|
||||||
.peer_mgr
|
.peer_mgr
|
||||||
@@ -206,7 +212,7 @@ impl PunchConeHoleClient {
|
|||||||
{
|
{
|
||||||
Ok(tunnel) => {
|
Ok(tunnel) => {
|
||||||
tracing::info!(?tunnel, "hole punched");
|
tracing::info!(?tunnel, "hole punched");
|
||||||
return Ok(tunnel);
|
return Ok(Some(tunnel));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(?e, "failed to connect with socket");
|
tracing::error!(?e, "failed to connect with socket");
|
||||||
@@ -215,7 +221,7 @@ impl PunchConeHoleClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(anyhow::anyhow!("punch task finished but no hole punched"));
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
|
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use both_easy_sym::{PunchBothEasySymHoleClient, PunchBothEasySymHoleServer};
|
use both_easy_sym::{PunchBothEasySymHoleClient, PunchBothEasySymHoleServer};
|
||||||
@@ -27,6 +27,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
rpc_types::{self, controller::BaseController},
|
rpc_types::{self, controller::BaseController},
|
||||||
},
|
},
|
||||||
|
tunnel::Tunnel,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) mod both_easy_sym;
|
pub(crate) mod both_easy_sym;
|
||||||
@@ -36,6 +37,7 @@ pub(crate) mod sym_to_cone;
|
|||||||
|
|
||||||
// sym punch should be serialized
|
// sym punch should be serialized
|
||||||
static SYM_PUNCH_LOCK: Lazy<DashMap<PeerId, Arc<Mutex<()>>>> = Lazy::new(|| DashMap::new());
|
static SYM_PUNCH_LOCK: Lazy<DashMap<PeerId, Arc<Mutex<()>>>> = Lazy::new(|| DashMap::new());
|
||||||
|
static RUN_TESTING: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
|
||||||
|
|
||||||
fn get_sym_punch_lock(peer_id: PeerId) -> Arc<Mutex<()>> {
|
fn get_sym_punch_lock(peer_id: PeerId) -> Arc<Mutex<()>> {
|
||||||
SYM_PUNCH_LOCK
|
SYM_PUNCH_LOCK
|
||||||
@@ -103,6 +105,9 @@ impl UdpHolePunchRpc for UdpHolePunchServer {
|
|||||||
_ctrl: Self::Controller,
|
_ctrl: Self::Controller,
|
||||||
input: SendPunchPacketHardSymRequest,
|
input: SendPunchPacketHardSymRequest,
|
||||||
) -> rpc_types::error::Result<SendPunchPacketHardSymResponse> {
|
) -> rpc_types::error::Result<SendPunchPacketHardSymResponse> {
|
||||||
|
let _locked = get_sym_punch_lock(self.common.get_peer_mgr().my_peer_id())
|
||||||
|
.try_lock_owned()
|
||||||
|
.with_context(|| "sym punch lock is busy")?;
|
||||||
self.sym_to_cone_server
|
self.sym_to_cone_server
|
||||||
.send_punch_packet_hard_sym(input)
|
.send_punch_packet_hard_sym(input)
|
||||||
.await
|
.await
|
||||||
@@ -113,6 +118,9 @@ impl UdpHolePunchRpc for UdpHolePunchServer {
|
|||||||
_ctrl: Self::Controller,
|
_ctrl: Self::Controller,
|
||||||
input: SendPunchPacketEasySymRequest,
|
input: SendPunchPacketEasySymRequest,
|
||||||
) -> rpc_types::error::Result<Void> {
|
) -> rpc_types::error::Result<Void> {
|
||||||
|
let _locked = get_sym_punch_lock(self.common.get_peer_mgr().my_peer_id())
|
||||||
|
.try_lock_owned()
|
||||||
|
.with_context(|| "sym punch lock is busy")?;
|
||||||
self.sym_to_cone_server
|
self.sym_to_cone_server
|
||||||
.send_punch_packet_easy_sym(input)
|
.send_punch_packet_easy_sym(input)
|
||||||
.await
|
.await
|
||||||
@@ -134,6 +142,7 @@ impl UdpHolePunchRpc for UdpHolePunchServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct BackOff {
|
struct BackOff {
|
||||||
backoffs_ms: Vec<u64>,
|
backoffs_ms: Vec<u64>,
|
||||||
current_idx: usize,
|
current_idx: usize,
|
||||||
@@ -186,6 +195,53 @@ impl UdpHoePunchConnectorData {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
async fn handle_punch_result(
|
||||||
|
self: &Self,
|
||||||
|
ret: Result<Option<Box<dyn Tunnel>>, Error>,
|
||||||
|
backoff: Option<&mut BackOff>,
|
||||||
|
round: Option<&mut u32>,
|
||||||
|
) -> bool {
|
||||||
|
let op = |rollback: bool| {
|
||||||
|
if rollback {
|
||||||
|
if let Some(backoff) = backoff {
|
||||||
|
backoff.rollback();
|
||||||
|
}
|
||||||
|
if let Some(round) = round {
|
||||||
|
*round = round.saturating_sub(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(round) = round {
|
||||||
|
*round += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match ret {
|
||||||
|
Ok(Some(tunnel)) => {
|
||||||
|
tracing::info!(?tunnel, "hole punching get tunnel success");
|
||||||
|
|
||||||
|
if let Err(e) = self.peer_mgr.add_client_tunnel(tunnel).await {
|
||||||
|
tracing::warn!(?e, "add client tunnel failed");
|
||||||
|
op(true);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
tracing::info!("hole punching failed, no punch tunnel");
|
||||||
|
op(false);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::info!(?e, "hole punching failed");
|
||||||
|
op(true);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
async fn cone_to_cone(self: Arc<Self>, task_info: PunchTaskInfo) -> Result<(), Error> {
|
async fn cone_to_cone(self: Arc<Self>, task_info: PunchTaskInfo) -> Result<(), Error> {
|
||||||
let mut backoff = BackOff::new(vec![0, 1000, 2000, 4000, 4000, 8000, 8000, 16000]);
|
let mut backoff = BackOff::new(vec![0, 1000, 2000, 4000, 4000, 8000, 8000, 16000]);
|
||||||
@@ -197,20 +253,15 @@ impl UdpHoePunchConnectorData {
|
|||||||
.cone_client
|
.cone_client
|
||||||
.do_hole_punching(task_info.dst_peer_id)
|
.do_hole_punching(task_info.dst_peer_id)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = ret {
|
|
||||||
tracing::info!(?e, "cone_to_cone hole punching failed");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.peer_mgr.add_client_tunnel(ret.unwrap()).await {
|
if self
|
||||||
tracing::warn!(?e, "cone_to_cone add client tunnel failed");
|
.handle_punch_result(ret, Some(&mut backoff), None)
|
||||||
continue;
|
.await
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("cone_to_cone hole punching success");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +274,17 @@ impl UdpHoePunchConnectorData {
|
|||||||
loop {
|
loop {
|
||||||
backoff.sleep_for_next_backoff().await;
|
backoff.sleep_for_next_backoff().await;
|
||||||
|
|
||||||
|
// always try cone first
|
||||||
|
if !RUN_TESTING.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
let ret = self
|
||||||
|
.cone_client
|
||||||
|
.do_hole_punching(task_info.dst_peer_id)
|
||||||
|
.await;
|
||||||
|
if self.handle_punch_result(ret, None, None).await {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ret = {
|
let ret = {
|
||||||
let _lock = get_sym_punch_lock(self.peer_mgr.my_peer_id())
|
let _lock = get_sym_punch_lock(self.peer_mgr.my_peer_id())
|
||||||
.lock_owned()
|
.lock_owned()
|
||||||
@@ -237,19 +299,12 @@ impl UdpHoePunchConnectorData {
|
|||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
round += 1;
|
if self
|
||||||
|
.handle_punch_result(ret, Some(&mut backoff), Some(&mut round))
|
||||||
if let Err(e) = ret {
|
.await
|
||||||
tracing::info!(?e, "sym_to_cone hole punching failed");
|
{
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = self.peer_mgr.add_client_tunnel(ret.unwrap()).await {
|
|
||||||
tracing::warn!(?e, "sym_to_cone add client tunnel failed");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -262,6 +317,17 @@ impl UdpHoePunchConnectorData {
|
|||||||
loop {
|
loop {
|
||||||
backoff.sleep_for_next_backoff().await;
|
backoff.sleep_for_next_backoff().await;
|
||||||
|
|
||||||
|
// always try cone first
|
||||||
|
if !RUN_TESTING.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
let ret = self
|
||||||
|
.cone_client
|
||||||
|
.do_hole_punching(task_info.dst_peer_id)
|
||||||
|
.await;
|
||||||
|
if self.handle_punch_result(ret, None, None).await {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut is_busy = false;
|
let mut is_busy = false;
|
||||||
|
|
||||||
let ret = {
|
let ret = {
|
||||||
@@ -280,19 +346,12 @@ impl UdpHoePunchConnectorData {
|
|||||||
|
|
||||||
if is_busy {
|
if is_busy {
|
||||||
backoff.rollback();
|
backoff.rollback();
|
||||||
|
} else if self
|
||||||
|
.handle_punch_result(ret, Some(&mut backoff), None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = ret {
|
|
||||||
tracing::info!(?e, "both_easy_sym hole punching failed");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.peer_mgr.add_client_tunnel(ret.unwrap()).await {
|
|
||||||
tracing::warn!(?e, "both_easy_sym add client tunnel failed");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -258,7 +258,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
round: u32,
|
round: u32,
|
||||||
last_port_idx: &mut usize,
|
last_port_idx: &mut usize,
|
||||||
my_nat_info: UdpNatType,
|
my_nat_info: UdpNatType,
|
||||||
) -> Result<Box<dyn Tunnel>, anyhow::Error> {
|
) -> Result<Option<Box<dyn Tunnel>>, anyhow::Error> {
|
||||||
let udp_array = self.prepare_udp_array().await?;
|
let udp_array = self.prepare_udp_array().await?;
|
||||||
let global_ctx = self.peer_mgr.get_global_ctx();
|
let global_ctx = self.peer_mgr.get_global_ctx();
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Ok(tunnel);
|
return Ok(Some(tunnel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,14 +411,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
*last_port_idx = rand::random();
|
*last_port_idx = rand::random();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tunnel) = ret_tunnel {
|
Ok(ret_tunnel)
|
||||||
Ok(tunnel)
|
|
||||||
} else {
|
|
||||||
anyhow::bail!(
|
|
||||||
"failed to hole punch, punch task result: {:?}",
|
|
||||||
punch_task_result
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +426,7 @@ pub mod tests {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connector::udp_hole_punch::{
|
connector::udp_hole_punch::{
|
||||||
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector,
|
tests::create_mock_peer_manager_with_mock_stun, UdpHolePunchConnector, RUN_TESTING,
|
||||||
},
|
},
|
||||||
peers::tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost},
|
peers::tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost},
|
||||||
proto::common::NatType,
|
proto::common::NatType,
|
||||||
@@ -443,6 +436,8 @@ pub mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial(hole_punch)]
|
#[serial_test::serial(hole_punch)]
|
||||||
async fn hole_punching_symmetric_only_random() {
|
async fn hole_punching_symmetric_only_random() {
|
||||||
|
RUN_TESTING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
let p_a = create_mock_peer_manager_with_mock_stun(NatType::Symmetric).await;
|
let p_a = create_mock_peer_manager_with_mock_stun(NatType::Symmetric).await;
|
||||||
let p_b = create_mock_peer_manager_with_mock_stun(NatType::PortRestricted).await;
|
let p_b = create_mock_peer_manager_with_mock_stun(NatType::PortRestricted).await;
|
||||||
let p_c = create_mock_peer_manager_with_mock_stun(NatType::PortRestricted).await;
|
let p_c = create_mock_peer_manager_with_mock_stun(NatType::PortRestricted).await;
|
||||||
@@ -518,6 +513,8 @@ pub mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial(hole_punch)]
|
#[serial_test::serial(hole_punch)]
|
||||||
async fn hole_punching_symmetric_only_predict(#[values("true", "false")] is_inc: bool) {
|
async fn hole_punching_symmetric_only_predict(#[values("true", "false")] is_inc: bool) {
|
||||||
|
RUN_TESTING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
let p_a = create_mock_peer_manager_with_mock_stun(if is_inc {
|
let p_a = create_mock_peer_manager_with_mock_stun(if is_inc {
|
||||||
NatType::SymmetricEasyInc
|
NatType::SymmetricEasyInc
|
||||||
} else {
|
} else {
|
||||||
|
@@ -4,7 +4,7 @@ use std::{net::SocketAddr, sync::Mutex, time::Duration, vec};
|
|||||||
|
|
||||||
use anyhow::{Context, Ok};
|
use anyhow::{Context, Ok};
|
||||||
use clap::{command, Args, Parser, Subcommand};
|
use clap::{command, Args, Parser, Subcommand};
|
||||||
use common::stun::StunInfoCollectorTrait;
|
use common::{constants::EASYTIER_VERSION, stun::StunInfoCollectorTrait};
|
||||||
use proto::{
|
use proto::{
|
||||||
common::NatType,
|
common::NatType,
|
||||||
peer_rpc::{GetGlobalPeerMapRequest, PeerCenterRpc, PeerCenterRpcClientFactory},
|
peer_rpc::{GetGlobalPeerMapRequest, PeerCenterRpc, PeerCenterRpcClientFactory},
|
||||||
@@ -30,7 +30,7 @@ use humansize::format_size;
|
|||||||
use tabled::settings::Style;
|
use tabled::settings::Style;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "easytier-cli", author, version, about, long_about = None)]
|
#[command(name = "easytier-cli", author, version = EASYTIER_VERSION, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// the instance name
|
/// the instance name
|
||||||
#[arg(short = 'p', long, default_value = "127.0.0.1:15888")]
|
#[arg(short = 'p', long, default_value = "127.0.0.1:15888")]
|
||||||
@@ -227,7 +227,12 @@ impl CommandHandler {
|
|||||||
impl From<PeerRoutePair> for PeerTableItem {
|
impl From<PeerRoutePair> for PeerTableItem {
|
||||||
fn from(p: PeerRoutePair) -> Self {
|
fn from(p: PeerRoutePair) -> Self {
|
||||||
PeerTableItem {
|
PeerTableItem {
|
||||||
ipv4: p.route.ipv4_addr.clone(),
|
ipv4: p
|
||||||
|
.route
|
||||||
|
.ipv4_addr
|
||||||
|
.clone()
|
||||||
|
.map(|ip| ip.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
hostname: p.route.hostname.clone(),
|
hostname: p.route.hostname.clone(),
|
||||||
cost: cost_to_str(p.route.cost),
|
cost: cost_to_str(p.route.cost),
|
||||||
lat_ms: float_to_str(p.get_latency_ms().unwrap_or(0.0), 3),
|
lat_ms: float_to_str(p.get_latency_ms().unwrap_or(0.0), 3),
|
||||||
@@ -413,7 +418,12 @@ impl CommandHandler {
|
|||||||
|
|
||||||
if p.route.cost == 1 {
|
if p.route.cost == 1 {
|
||||||
items.push(RouteTableItem {
|
items.push(RouteTableItem {
|
||||||
ipv4: p.route.ipv4_addr.clone(),
|
ipv4: p
|
||||||
|
.route
|
||||||
|
.ipv4_addr
|
||||||
|
.clone()
|
||||||
|
.map(|ip| ip.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
hostname: p.route.hostname.clone(),
|
hostname: p.route.hostname.clone(),
|
||||||
proxy_cidrs: p.route.proxy_cidrs.clone().join(",").to_string(),
|
proxy_cidrs: p.route.proxy_cidrs.clone().join(",").to_string(),
|
||||||
next_hop_ipv4: "DIRECT".to_string(),
|
next_hop_ipv4: "DIRECT".to_string(),
|
||||||
@@ -428,10 +438,20 @@ impl CommandHandler {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
items.push(RouteTableItem {
|
items.push(RouteTableItem {
|
||||||
ipv4: p.route.ipv4_addr.clone(),
|
ipv4: p
|
||||||
|
.route
|
||||||
|
.ipv4_addr
|
||||||
|
.clone()
|
||||||
|
.map(|ip| ip.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
hostname: p.route.hostname.clone(),
|
hostname: p.route.hostname.clone(),
|
||||||
proxy_cidrs: p.route.proxy_cidrs.clone().join(",").to_string(),
|
proxy_cidrs: p.route.proxy_cidrs.clone().join(",").to_string(),
|
||||||
next_hop_ipv4: next_hop_pair.route.ipv4_addr.clone(),
|
next_hop_ipv4: next_hop_pair
|
||||||
|
.route
|
||||||
|
.ipv4_addr
|
||||||
|
.clone()
|
||||||
|
.map(|ip| ip.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
next_hop_hostname: next_hop_pair.route.hostname.clone(),
|
next_hop_hostname: next_hop_pair.route.hostname.clone(),
|
||||||
next_hop_lat: next_hop_pair.get_latency_ms().unwrap_or(0.0),
|
next_hop_lat: next_hop_pair.get_latency_ms().unwrap_or(0.0),
|
||||||
cost: p.route.cost,
|
cost: p.route.cost,
|
||||||
|
@@ -26,8 +26,9 @@ mod tunnel;
|
|||||||
mod utils;
|
mod utils;
|
||||||
mod vpn_portal;
|
mod vpn_portal;
|
||||||
|
|
||||||
use common::config::{
|
use common::{
|
||||||
ConsoleLoggerConfig, FileLoggerConfig, NetworkIdentity, PeerConfig, VpnPortalConfig,
|
config::{ConsoleLoggerConfig, FileLoggerConfig, NetworkIdentity, PeerConfig, VpnPortalConfig},
|
||||||
|
constants::EASYTIER_VERSION,
|
||||||
};
|
};
|
||||||
use instance::instance::Instance;
|
use instance::instance::Instance;
|
||||||
use tokio::net::TcpSocket;
|
use tokio::net::TcpSocket;
|
||||||
@@ -49,7 +50,7 @@ use mimalloc_rust::*;
|
|||||||
static GLOBAL_MIMALLOC: GlobalMiMalloc = GlobalMiMalloc;
|
static GLOBAL_MIMALLOC: GlobalMiMalloc = GlobalMiMalloc;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "easytier-core", author, version, about, long_about = None)]
|
#[command(name = "easytier-core", author, version = EASYTIER_VERSION , about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
|
@@ -358,7 +358,12 @@ impl IcmpProxy {
|
|||||||
if !self.cidr_set.contains_v4(ipv4.get_destination())
|
if !self.cidr_set.contains_v4(ipv4.get_destination())
|
||||||
&& !is_exit_node
|
&& !is_exit_node
|
||||||
&& !(self.global_ctx.no_tun()
|
&& !(self.global_ctx.no_tun()
|
||||||
&& Some(ipv4.get_destination()) == self.global_ctx.get_ipv4())
|
&& Some(ipv4.get_destination())
|
||||||
|
== self
|
||||||
|
.global_ctx
|
||||||
|
.get_ipv4()
|
||||||
|
.as_ref()
|
||||||
|
.map(cidr::Ipv4Inet::address))
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -382,7 +387,14 @@ impl IcmpProxy {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.global_ctx.no_tun() && Some(ipv4.get_destination()) == self.global_ctx.get_ipv4() {
|
if self.global_ctx.no_tun()
|
||||||
|
&& Some(ipv4.get_destination())
|
||||||
|
== self
|
||||||
|
.global_ctx
|
||||||
|
.get_ipv4()
|
||||||
|
.as_ref()
|
||||||
|
.map(cidr::Ipv4Inet::address)
|
||||||
|
{
|
||||||
self.send_icmp_reply_to_peer(
|
self.send_icmp_reply_to_peer(
|
||||||
&ipv4.get_destination(),
|
&ipv4.get_destination(),
|
||||||
&ipv4.get_source(),
|
&ipv4.get_source(),
|
||||||
|
@@ -111,7 +111,7 @@ struct Socks5Entry {
|
|||||||
type Socks5EntrySet = Arc<DashSet<Socks5Entry>>;
|
type Socks5EntrySet = Arc<DashSet<Socks5Entry>>;
|
||||||
|
|
||||||
struct Socks5ServerNet {
|
struct Socks5ServerNet {
|
||||||
ipv4_addr: Ipv4Addr,
|
ipv4_addr: cidr::Ipv4Inet,
|
||||||
auth: Option<SimpleUserPassword>,
|
auth: Option<SimpleUserPassword>,
|
||||||
|
|
||||||
smoltcp_net: Arc<Net>,
|
smoltcp_net: Arc<Net>,
|
||||||
@@ -122,7 +122,7 @@ struct Socks5ServerNet {
|
|||||||
|
|
||||||
impl Socks5ServerNet {
|
impl Socks5ServerNet {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ipv4_addr: Ipv4Addr,
|
ipv4_addr: cidr::Ipv4Inet,
|
||||||
auth: Option<SimpleUserPassword>,
|
auth: Option<SimpleUserPassword>,
|
||||||
peer_manager: Arc<PeerManager>,
|
peer_manager: Arc<PeerManager>,
|
||||||
packet_recv: Arc<Mutex<mpsc::Receiver<ZCPacket>>>,
|
packet_recv: Arc<Mutex<mpsc::Receiver<ZCPacket>>>,
|
||||||
@@ -173,8 +173,10 @@ impl Socks5ServerNet {
|
|||||||
dev,
|
dev,
|
||||||
NetConfig::new(
|
NetConfig::new(
|
||||||
interface_config,
|
interface_config,
|
||||||
format!("{}/24", ipv4_addr).parse().unwrap(),
|
format!("{}/{}", ipv4_addr.address(), ipv4_addr.network_length())
|
||||||
vec![format!("{}", ipv4_addr).parse().unwrap()],
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
vec![format!("{}", ipv4_addr.address()).parse().unwrap()],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
use cidr::Ipv4Inet;
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
@@ -526,7 +527,8 @@ impl TcpProxy {
|
|||||||
tracing::warn!("set_nodelay failed, ignore it: {:?}", e);
|
tracing::warn!("set_nodelay failed, ignore it: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let nat_dst = if Some(nat_entry.dst.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip))
|
let nat_dst = if Some(nat_entry.dst.ip())
|
||||||
|
== global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address()))
|
||||||
{
|
{
|
||||||
format!("127.0.0.1:{}", nat_entry.dst.port())
|
format!("127.0.0.1:{}", nat_entry.dst.port())
|
||||||
.parse()
|
.parse()
|
||||||
@@ -591,7 +593,10 @@ impl TcpProxy {
|
|||||||
{
|
{
|
||||||
Some(Ipv4Addr::new(192, 88, 99, 254))
|
Some(Ipv4Addr::new(192, 88, 99, 254))
|
||||||
} else {
|
} else {
|
||||||
self.global_ctx.get_ipv4()
|
self.global_ctx
|
||||||
|
.get_ipv4()
|
||||||
|
.as_ref()
|
||||||
|
.map(cidr::Ipv4Inet::address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +626,8 @@ impl TcpProxy {
|
|||||||
if !self.cidr_set.contains_v4(ipv4.get_destination())
|
if !self.cidr_set.contains_v4(ipv4.get_destination())
|
||||||
&& !is_exit_node
|
&& !is_exit_node
|
||||||
&& !(self.global_ctx.no_tun()
|
&& !(self.global_ctx.no_tun()
|
||||||
&& Some(ipv4.get_destination()) == self.global_ctx.get_ipv4())
|
&& Some(ipv4.get_destination())
|
||||||
|
== self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address))
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use cidr::Ipv4Inet;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use pnet::packet::{
|
use pnet::packet::{
|
||||||
@@ -245,7 +246,8 @@ impl UdpProxy {
|
|||||||
if !self.cidr_set.contains_v4(ipv4.get_destination())
|
if !self.cidr_set.contains_v4(ipv4.get_destination())
|
||||||
&& !is_exit_node
|
&& !is_exit_node
|
||||||
&& !(self.global_ctx.no_tun()
|
&& !(self.global_ctx.no_tun()
|
||||||
&& Some(ipv4.get_destination()) == self.global_ctx.get_ipv4())
|
&& Some(ipv4.get_destination())
|
||||||
|
== self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address))
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -296,14 +298,16 @@ impl UdpProxy {
|
|||||||
.replace(tokio::spawn(UdpNatEntry::forward_task(
|
.replace(tokio::spawn(UdpNatEntry::forward_task(
|
||||||
nat_entry.clone(),
|
nat_entry.clone(),
|
||||||
self.sender.clone(),
|
self.sender.clone(),
|
||||||
self.global_ctx.get_ipv4()?,
|
self.global_ctx.get_ipv4().map(|x| x.address())?,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
nat_entry.mark_active();
|
nat_entry.mark_active();
|
||||||
|
|
||||||
// TODO: should it be async.
|
// TODO: should it be async.
|
||||||
let dst_socket = if Some(ipv4.get_destination()) == self.global_ctx.get_ipv4() {
|
let dst_socket = if Some(ipv4.get_destination())
|
||||||
|
== self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address)
|
||||||
|
{
|
||||||
format!("127.0.0.1:{}", udp_packet.get_destination())
|
format!("127.0.0.1:{}", udp_packet.get_destination())
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@@ -270,19 +270,11 @@ impl Instance {
|
|||||||
|
|
||||||
let mut used_ipv4 = HashSet::new();
|
let mut used_ipv4 = HashSet::new();
|
||||||
for route in routes {
|
for route in routes {
|
||||||
if route.ipv4_addr.is_empty() {
|
let Some(peer_ipv4_addr) = route.ipv4_addr else {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(peer_ipv4_addr) = route.ipv4_addr.parse::<Ipv4Addr>() else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(peer_ipv4_addr) = Ipv4Inet::new(peer_ipv4_addr, 24) else {
|
used_ipv4.insert(peer_ipv4_addr.into());
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
used_ipv4.insert(peer_ipv4_addr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dhcp_inet = used_ipv4.iter().next().unwrap_or(&default_ipv4_addr);
|
let dhcp_inet = used_ipv4.iter().next().unwrap_or(&default_ipv4_addr);
|
||||||
@@ -304,7 +296,7 @@ impl Instance {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_ip = current_dhcp_ip.as_ref().map(Ipv4Inet::address);
|
let last_ip = current_dhcp_ip.clone();
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
?current_dhcp_ip,
|
?current_dhcp_ip,
|
||||||
?candidate_ipv4_addr,
|
?candidate_ipv4_addr,
|
||||||
@@ -316,11 +308,9 @@ impl Instance {
|
|||||||
if let Some(ip) = candidate_ipv4_addr {
|
if let Some(ip) = candidate_ipv4_addr {
|
||||||
if global_ctx_c.no_tun() {
|
if global_ctx_c.no_tun() {
|
||||||
current_dhcp_ip = Some(ip);
|
current_dhcp_ip = Some(ip);
|
||||||
global_ctx_c.set_ipv4(Some(ip.address()));
|
global_ctx_c.set_ipv4(Some(ip));
|
||||||
global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Changed(
|
global_ctx_c
|
||||||
last_ip,
|
.issue_event(GlobalCtxEvent::DhcpIpv4Changed(last_ip, Some(ip)));
|
||||||
Some(ip.address()),
|
|
||||||
));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +321,7 @@ impl Instance {
|
|||||||
&peer_manager_c,
|
&peer_manager_c,
|
||||||
_peer_packet_receiver.clone(),
|
_peer_packet_receiver.clone(),
|
||||||
);
|
);
|
||||||
if let Err(e) = new_nic_ctx.run(ip.address()).await {
|
if let Err(e) = new_nic_ctx.run(ip).await {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?current_dhcp_ip,
|
?current_dhcp_ip,
|
||||||
?candidate_ipv4_addr,
|
?candidate_ipv4_addr,
|
||||||
@@ -345,9 +335,8 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
current_dhcp_ip = Some(ip);
|
current_dhcp_ip = Some(ip);
|
||||||
global_ctx_c.set_ipv4(Some(ip.address()));
|
global_ctx_c.set_ipv4(Some(ip));
|
||||||
global_ctx_c
|
global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Changed(last_ip, Some(ip)));
|
||||||
.issue_event(GlobalCtxEvent::DhcpIpv4Changed(last_ip, Some(ip.address())));
|
|
||||||
} else {
|
} else {
|
||||||
current_dhcp_ip = None;
|
current_dhcp_ip = None;
|
||||||
global_ctx_c.set_ipv4(None);
|
global_ctx_c.set_ipv4(None);
|
||||||
|
@@ -504,8 +504,7 @@ pub fn reg_change_catrgory_in_profile(dev_name: &str) -> io::Result<()> {
|
|||||||
let subkey = profiles_key.open_subkey_with_flags(&subkey_name, KEY_ALL_ACCESS)?;
|
let subkey = profiles_key.open_subkey_with_flags(&subkey_name, KEY_ALL_ACCESS)?;
|
||||||
match subkey.get_value::<String, _>("ProfileName") {
|
match subkey.get_value::<String, _>("ProfileName") {
|
||||||
Ok(profile_name) => {
|
Ok(profile_name) => {
|
||||||
if !dev_name.is_empty() && dev_name == profile_name
|
if !dev_name.is_empty() && dev_name == profile_name {
|
||||||
{
|
|
||||||
match subkey.set_value("Category", &1u32) {
|
match subkey.set_value("Category", &1u32) {
|
||||||
Ok(_) => tracing::trace!("Successfully set Category in registry"),
|
Ok(_) => tracing::trace!("Successfully set Category in registry"),
|
||||||
Err(e) => tracing::error!("Failed to set Category in registry: {}", e),
|
Err(e) => tracing::error!("Failed to set Category in registry: {}", e),
|
||||||
@@ -548,14 +547,16 @@ impl NicCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn assign_ipv4_to_tun_device(&self, ipv4_addr: Ipv4Addr) -> Result<(), Error> {
|
async fn assign_ipv4_to_tun_device(&self, ipv4_addr: cidr::Ipv4Inet) -> Result<(), Error> {
|
||||||
let nic = self.nic.lock().await;
|
let nic = self.nic.lock().await;
|
||||||
nic.link_up().await?;
|
nic.link_up().await?;
|
||||||
nic.remove_ip(None).await?;
|
nic.remove_ip(None).await?;
|
||||||
nic.add_ip(ipv4_addr, 24).await?;
|
nic.add_ip(ipv4_addr.address(), ipv4_addr.network_length() as i32)
|
||||||
|
.await?;
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
{
|
{
|
||||||
nic.add_route(ipv4_addr, 24).await?;
|
nic.add_route(ipv4_addr.first_address(), ipv4_addr.network_length())
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -710,18 +711,17 @@ impl NicCtx {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self, ipv4_addr: Ipv4Addr) -> Result<(), Error> {
|
pub async fn run(&mut self, ipv4_addr: cidr::Ipv4Inet) -> Result<(), Error> {
|
||||||
let tunnel = {
|
let tunnel = {
|
||||||
let mut nic = self.nic.lock().await;
|
let mut nic = self.nic.lock().await;
|
||||||
match nic.create_dev().await {
|
match nic.create_dev().await {
|
||||||
Ok(ret) => {
|
Ok(ret) => {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
{
|
||||||
let dev_name = self.global_ctx.get_flags().dev_name;
|
let dev_name = self.global_ctx.get_flags().dev_name;
|
||||||
let _ = reg_change_catrgory_in_profile(&dev_name);
|
let _ = reg_change_catrgory_in_profile(&dev_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.global_ctx
|
self.global_ctx
|
||||||
.issue_event(GlobalCtxEvent::TunDeviceReady(nic.ifname().to_string()));
|
.issue_event(GlobalCtxEvent::TunDeviceReady(nic.ifname().to_string()));
|
||||||
ret
|
ret
|
||||||
|
@@ -93,7 +93,7 @@ impl PeerConn {
|
|||||||
let peer_conn_tunnel_filter = StatsRecorderTunnelFilter::new();
|
let peer_conn_tunnel_filter = StatsRecorderTunnelFilter::new();
|
||||||
let throughput = peer_conn_tunnel_filter.filter_output();
|
let throughput = peer_conn_tunnel_filter.filter_output();
|
||||||
let peer_conn_tunnel = TunnelWithFilter::new(tunnel, peer_conn_tunnel_filter);
|
let peer_conn_tunnel = TunnelWithFilter::new(tunnel, peer_conn_tunnel_filter);
|
||||||
let mut mpsc_tunnel = MpscTunnel::new(peer_conn_tunnel);
|
let mut mpsc_tunnel = MpscTunnel::new(peer_conn_tunnel, Some(Duration::from_secs(7)));
|
||||||
|
|
||||||
let (recv, sink) = (mpsc_tunnel.get_stream(), mpsc_tunnel.get_sink());
|
let (recv, sink) = (mpsc_tunnel.get_stream(), mpsc_tunnel.get_sink());
|
||||||
|
|
||||||
@@ -224,7 +224,12 @@ impl PeerConn {
|
|||||||
self.info = Some(rsp);
|
self.info = Some(rsp);
|
||||||
self.is_client = Some(false);
|
self.is_client = Some(false);
|
||||||
self.send_handshake().await?;
|
self.send_handshake().await?;
|
||||||
Ok(())
|
|
||||||
|
if self.get_peer_id() == self.my_peer_id {
|
||||||
|
Err(Error::WaitRespError("peer id conflict".to_owned()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
@@ -235,7 +240,12 @@ impl PeerConn {
|
|||||||
tracing::info!("handshake response: {:?}", rsp);
|
tracing::info!("handshake response: {:?}", rsp);
|
||||||
self.info = Some(rsp);
|
self.info = Some(rsp);
|
||||||
self.is_client = Some(true);
|
self.is_client = Some(true);
|
||||||
Ok(())
|
|
||||||
|
if self.get_peer_id() == self.my_peer_id {
|
||||||
|
Err(Error::WaitRespError("peer id conflict".to_owned()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handshake_done(&self) -> bool {
|
pub fn handshake_done(&self) -> bool {
|
||||||
@@ -396,6 +406,24 @@ mod tests {
|
|||||||
use crate::tunnel::filter::PacketRecorderTunnelFilter;
|
use crate::tunnel::filter::PacketRecorderTunnelFilter;
|
||||||
use crate::tunnel::ring::create_ring_tunnel_pair;
|
use crate::tunnel::ring::create_ring_tunnel_pair;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn peer_conn_handshake_same_id() {
|
||||||
|
let (c, s) = create_ring_tunnel_pair();
|
||||||
|
let c_peer_id = new_peer_id();
|
||||||
|
let s_peer_id = c_peer_id;
|
||||||
|
|
||||||
|
let mut c_peer = PeerConn::new(c_peer_id, get_mock_global_ctx(), Box::new(c));
|
||||||
|
let mut s_peer = PeerConn::new(s_peer_id, get_mock_global_ctx(), Box::new(s));
|
||||||
|
|
||||||
|
let (c_ret, s_ret) = tokio::join!(
|
||||||
|
c_peer.do_handshake_as_client(),
|
||||||
|
s_peer.do_handshake_as_server()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(c_ret.is_err());
|
||||||
|
assert!(s_ret.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn peer_conn_handshake() {
|
async fn peer_conn_handshake() {
|
||||||
let (c, s) = create_ring_tunnel_pair();
|
let (c, s) = create_ring_tunnel_pair();
|
||||||
|
@@ -294,6 +294,8 @@ impl PeerConnPinger {
|
|||||||
let need_close = if last_rx_packets != current_rx_packets {
|
let need_close = if last_rx_packets != current_rx_packets {
|
||||||
// if we receive some packet from peers, we should relax the condition
|
// if we receive some packet from peers, we should relax the condition
|
||||||
counter > 50 && loss_rate_1 > 0.5
|
counter > 50 && loss_rate_1 > 0.5
|
||||||
|
|
||||||
|
// TODO: wait more time to see if the loss rate is still high after no rx
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
@@ -718,8 +718,16 @@ impl PeerManager {
|
|||||||
|
|
||||||
let mut is_exit_node = false;
|
let mut is_exit_node = false;
|
||||||
let mut dst_peers = vec![];
|
let mut dst_peers = vec![];
|
||||||
// NOTE: currently we only support ipv4 and cidr is 24
|
let network_length = self
|
||||||
if ipv4_addr.is_broadcast() || ipv4_addr.is_multicast() || ipv4_addr.octets()[3] == 255 {
|
.global_ctx
|
||||||
|
.get_ipv4()
|
||||||
|
.map(|x| x.network_length())
|
||||||
|
.unwrap_or(24);
|
||||||
|
let ipv4_inet = cidr::Ipv4Inet::new(ipv4_addr, network_length).unwrap();
|
||||||
|
if ipv4_addr.is_broadcast()
|
||||||
|
|| ipv4_addr.is_multicast()
|
||||||
|
|| ipv4_addr == ipv4_inet.last_address()
|
||||||
|
{
|
||||||
dst_peers.extend(
|
dst_peers.extend(
|
||||||
self.peers
|
self.peers
|
||||||
.list_routes()
|
.list_routes()
|
||||||
|
@@ -30,7 +30,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
peers::route_trait::{Route, RouteInterfaceBox},
|
peers::route_trait::{Route, RouteInterfaceBox},
|
||||||
proto::{
|
proto::{
|
||||||
common::{NatType, StunInfo},
|
common::{Ipv4Inet, NatType, StunInfo},
|
||||||
peer_rpc::{
|
peer_rpc::{
|
||||||
route_foreign_network_infos, ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey,
|
route_foreign_network_infos, ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey,
|
||||||
OspfRouteRpc, OspfRouteRpcClientFactory, OspfRouteRpcServer, PeerIdVersion,
|
OspfRouteRpc, OspfRouteRpcClientFactory, OspfRouteRpcServer, PeerIdVersion,
|
||||||
@@ -118,6 +118,7 @@ impl RoutePeerInfo {
|
|||||||
easytier_version: EASYTIER_VERSION.to_string(),
|
easytier_version: EASYTIER_VERSION.to_string(),
|
||||||
feature_flag: None,
|
feature_flag: None,
|
||||||
peer_route_id: 0,
|
peer_route_id: 0,
|
||||||
|
network_length: 24,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ impl RoutePeerInfo {
|
|||||||
peer_id: my_peer_id,
|
peer_id: my_peer_id,
|
||||||
inst_id: Some(global_ctx.get_id().into()),
|
inst_id: Some(global_ctx.get_id().into()),
|
||||||
cost: 0,
|
cost: 0,
|
||||||
ipv4_addr: global_ctx.get_ipv4().map(|x| x.into()),
|
ipv4_addr: global_ctx.get_ipv4().map(|x| x.address().into()),
|
||||||
proxy_cidrs: global_ctx
|
proxy_cidrs: global_ctx
|
||||||
.get_proxy_cidrs()
|
.get_proxy_cidrs()
|
||||||
.iter()
|
.iter()
|
||||||
@@ -150,6 +151,10 @@ impl RoutePeerInfo {
|
|||||||
easytier_version: EASYTIER_VERSION.to_string(),
|
easytier_version: EASYTIER_VERSION.to_string(),
|
||||||
feature_flag: Some(global_ctx.get_feature_flags()),
|
feature_flag: Some(global_ctx.get_feature_flags()),
|
||||||
peer_route_id,
|
peer_route_id,
|
||||||
|
network_length: global_ctx
|
||||||
|
.get_ipv4()
|
||||||
|
.map(|x| x.network_length() as u32)
|
||||||
|
.unwrap_or(24),
|
||||||
};
|
};
|
||||||
|
|
||||||
let need_update_periodically = if let Ok(Ok(d)) =
|
let need_update_periodically = if let Ok(Ok(d)) =
|
||||||
@@ -171,12 +176,21 @@ impl RoutePeerInfo {
|
|||||||
|
|
||||||
impl Into<crate::proto::cli::Route> for RoutePeerInfo {
|
impl Into<crate::proto::cli::Route> for RoutePeerInfo {
|
||||||
fn into(self) -> crate::proto::cli::Route {
|
fn into(self) -> crate::proto::cli::Route {
|
||||||
|
let network_length = if self.network_length == 0 {
|
||||||
|
24
|
||||||
|
} else {
|
||||||
|
self.network_length
|
||||||
|
};
|
||||||
|
|
||||||
crate::proto::cli::Route {
|
crate::proto::cli::Route {
|
||||||
peer_id: self.peer_id,
|
peer_id: self.peer_id,
|
||||||
ipv4_addr: if let Some(ipv4_addr) = self.ipv4_addr {
|
ipv4_addr: if let Some(ipv4_addr) = self.ipv4_addr {
|
||||||
ipv4_addr.to_string()
|
Some(Ipv4Inet {
|
||||||
|
address: Some(ipv4_addr.into()),
|
||||||
|
network_length,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
None
|
||||||
},
|
},
|
||||||
next_hop_peer_id: 0,
|
next_hop_peer_id: 0,
|
||||||
cost: self.cost as i32,
|
cost: self.cost as i32,
|
||||||
@@ -305,26 +319,32 @@ impl SyncedRouteInfo {
|
|||||||
my_peer_id: PeerId,
|
my_peer_id: PeerId,
|
||||||
my_peer_route_id: u64,
|
my_peer_route_id: u64,
|
||||||
dst_peer_id: PeerId,
|
dst_peer_id: PeerId,
|
||||||
route_infos: &Vec<RoutePeerInfo>,
|
dst_peer_route_id: Option<u64>,
|
||||||
|
info: &RoutePeerInfo,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// 1. check if we are duplicated.
|
// 1. check if we are duplicated.
|
||||||
for info in route_infos.iter() {
|
if info.peer_id == my_peer_id {
|
||||||
if info.peer_id == my_peer_id {
|
if info.peer_route_id != my_peer_route_id
|
||||||
if info.version > self.get_peer_info_version_with_default(info.peer_id) {
|
&& info.version > self.get_peer_info_version_with_default(info.peer_id)
|
||||||
// if dst peer send to us with higher version info of my peer, our peer id is duplicated
|
{
|
||||||
// TODO: handle this better. restart peer manager?
|
// if dst peer send to us with higher version info of my peer, our peer id is duplicated
|
||||||
panic!("my peer id is duplicated");
|
// TODO: handle this better. restart peer manager?
|
||||||
// return Err(Error::DuplicatePeerId);
|
panic!("my peer id is duplicated");
|
||||||
}
|
// return Err(Error::DuplicatePeerId);
|
||||||
}
|
}
|
||||||
|
} else if info.peer_id == dst_peer_id {
|
||||||
|
let Some(dst_peer_route_id) = dst_peer_route_id else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
if info.peer_id == dst_peer_id && info.peer_route_id != my_peer_route_id {
|
if dst_peer_route_id != info.peer_route_id
|
||||||
if info.version < self.get_peer_info_version_with_default(info.peer_id) {
|
&& info.version < self.get_peer_info_version_with_default(info.peer_id)
|
||||||
// if dst peer send to us with lower version info of dst peer, dst peer id is duplicated
|
{
|
||||||
return Err(Error::DuplicatePeerId);
|
// if dst peer send to us with lower version info of dst peer, dst peer id is duplicated
|
||||||
}
|
return Err(Error::DuplicatePeerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,8 +355,19 @@ impl SyncedRouteInfo {
|
|||||||
dst_peer_id: PeerId,
|
dst_peer_id: PeerId,
|
||||||
peer_infos: &Vec<RoutePeerInfo>,
|
peer_infos: &Vec<RoutePeerInfo>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.check_duplicate_peer_id(my_peer_id, my_peer_route_id, dst_peer_id, peer_infos)?;
|
|
||||||
for mut route_info in peer_infos.iter().map(Clone::clone) {
|
for mut route_info in peer_infos.iter().map(Clone::clone) {
|
||||||
|
self.check_duplicate_peer_id(
|
||||||
|
my_peer_id,
|
||||||
|
my_peer_route_id,
|
||||||
|
dst_peer_id,
|
||||||
|
if route_info.peer_id == dst_peer_id {
|
||||||
|
self.peer_infos.get(&dst_peer_id).map(|x| x.peer_route_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
&route_info,
|
||||||
|
)?;
|
||||||
|
|
||||||
// time between peers may not be synchronized, so update last_update to local now.
|
// time between peers may not be synchronized, so update last_update to local now.
|
||||||
// note only last_update with larger version will be updated to local saved peer info.
|
// note only last_update with larger version will be updated to local saved peer info.
|
||||||
route_info.last_update = Some(SystemTime::now().into());
|
route_info.last_update = Some(SystemTime::now().into());
|
||||||
@@ -1356,7 +1387,9 @@ impl PeerRouteServiceImpl {
|
|||||||
if resp.error.is_some() {
|
if resp.error.is_some() {
|
||||||
let err = resp.error.unwrap();
|
let err = resp.error.unwrap();
|
||||||
if err == Error::DuplicatePeerId as i32 {
|
if err == Error::DuplicatePeerId as i32 {
|
||||||
panic!("duplicate peer id");
|
if !self.global_ctx.get_feature_flags().is_public_server {
|
||||||
|
panic!("duplicate peer id");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::error!(?ret, ?my_peer_id, ?dst_peer_id, "sync_route_info failed");
|
tracing::error!(?ret, ?my_peer_id, ?dst_peer_id, "sync_route_info failed");
|
||||||
session
|
session
|
||||||
|
@@ -45,7 +45,7 @@ message ListPeerResponse {
|
|||||||
|
|
||||||
message Route {
|
message Route {
|
||||||
uint32 peer_id = 1;
|
uint32 peer_id = 1;
|
||||||
string ipv4_addr = 2;
|
common.Ipv4Inet ipv4_addr = 2;
|
||||||
uint32 next_hop_peer_id = 3;
|
uint32 next_hop_peer_id = 3;
|
||||||
int32 cost = 4;
|
int32 cost = 4;
|
||||||
repeated string proxy_cidrs = 5;
|
repeated string proxy_cidrs = 5;
|
||||||
|
@@ -72,6 +72,11 @@ message Ipv6Addr {
|
|||||||
uint32 part4 = 4;
|
uint32 part4 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Ipv4Inet {
|
||||||
|
Ipv4Addr address = 1;
|
||||||
|
uint32 network_length = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message Url { string url = 1; }
|
message Url { string url = 1; }
|
||||||
|
|
||||||
message SocketAddr {
|
message SocketAddr {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/common.rs"));
|
include!(concat!(env!("OUT_DIR"), "/common.rs"));
|
||||||
|
|
||||||
impl From<uuid::Uuid> for Uuid {
|
impl From<uuid::Uuid> for Uuid {
|
||||||
@@ -60,10 +62,8 @@ impl From<Ipv6Addr> for std::net::Ipv6Addr {
|
|||||||
let part3 = value.part3.to_be_bytes();
|
let part3 = value.part3.to_be_bytes();
|
||||||
let part4 = value.part4.to_be_bytes();
|
let part4 = value.part4.to_be_bytes();
|
||||||
std::net::Ipv6Addr::from([
|
std::net::Ipv6Addr::from([
|
||||||
part1[0], part1[1], part1[2], part1[3],
|
part1[0], part1[1], part1[2], part1[3], part2[0], part2[1], part2[2], part2[3],
|
||||||
part2[0], part2[1], part2[2], part2[3],
|
part3[0], part3[1], part3[2], part3[3], part4[0], part4[1], part4[2], part4[3],
|
||||||
part3[0], part3[1], part3[2], part3[3],
|
|
||||||
part4[0], part4[1], part4[2], part4[3]
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,6 +74,37 @@ impl ToString for Ipv6Addr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<cidr::Ipv4Inet> for Ipv4Inet {
|
||||||
|
fn from(value: cidr::Ipv4Inet) -> Self {
|
||||||
|
Ipv4Inet {
|
||||||
|
address: Some(value.address().into()),
|
||||||
|
network_length: value.network_length() as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Ipv4Inet> for cidr::Ipv4Inet {
|
||||||
|
fn from(value: Ipv4Inet) -> Self {
|
||||||
|
cidr::Ipv4Inet::new(value.address.unwrap().into(), value.network_length as u8).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Ipv4Inet {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", cidr::Ipv4Inet::from(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Ipv4Inet {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Ipv4Inet::from(
|
||||||
|
cidr::Ipv4Inet::from_str(s).with_context(|| "Failed to parse Ipv4Inet")?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<url::Url> for Url {
|
impl From<url::Url> for Url {
|
||||||
fn from(value: url::Url) -> Self {
|
fn from(value: url::Url) -> Self {
|
||||||
Url {
|
Url {
|
||||||
|
@@ -20,6 +20,8 @@ message RoutePeerInfo {
|
|||||||
string easytier_version = 10;
|
string easytier_version = 10;
|
||||||
common.PeerFeatureFlag feature_flag = 11;
|
common.PeerFeatureFlag feature_flag = 11;
|
||||||
uint64 peer_route_id = 12;
|
uint64 peer_route_id = 12;
|
||||||
|
|
||||||
|
uint32 network_length = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PeerIdVersion {
|
message PeerIdVersion {
|
||||||
|
@@ -61,8 +61,8 @@ impl Client {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (ring_a, ring_b) = create_ring_tunnel_pair();
|
let (ring_a, ring_b) = create_ring_tunnel_pair();
|
||||||
Self {
|
Self {
|
||||||
mpsc: Mutex::new(MpscTunnel::new(ring_a)),
|
mpsc: Mutex::new(MpscTunnel::new(ring_a, None)),
|
||||||
transport: Mutex::new(MpscTunnel::new(ring_b)),
|
transport: Mutex::new(MpscTunnel::new(ring_b, None)),
|
||||||
inflight_requests: Arc::new(DashMap::new()),
|
inflight_requests: Arc::new(DashMap::new()),
|
||||||
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
||||||
}
|
}
|
||||||
|
@@ -56,8 +56,8 @@ impl Server {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
registry,
|
registry,
|
||||||
mpsc: Mutex::new(Some(MpscTunnel::new(ring_a))),
|
mpsc: Mutex::new(Some(MpscTunnel::new(ring_a, None))),
|
||||||
transport: Mutex::new(MpscTunnel::new(ring_b)),
|
transport: Mutex::new(MpscTunnel::new(ring_b, None)),
|
||||||
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
tasks: Arc::new(Mutex::new(JoinSet::new())),
|
||||||
packet_mergers: Arc::new(DashMap::new()),
|
packet_mergers: Arc::new(DashMap::new()),
|
||||||
}
|
}
|
||||||
|
@@ -175,6 +175,83 @@ async fn rpc_timeout_test() {
|
|||||||
assert_eq!(0, ctx.server.inflight_count());
|
assert_eq!(0, ctx.server.inflight_count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn rpc_tunnel_stuck_test() {
|
||||||
|
use crate::proto::rpc_types;
|
||||||
|
use crate::tunnel::ring::RING_TUNNEL_CAP;
|
||||||
|
|
||||||
|
let rpc_server = Server::new();
|
||||||
|
rpc_server.run();
|
||||||
|
let server = GreetingServer::new(GreetingService {
|
||||||
|
delay_ms: 0,
|
||||||
|
prefix: "Hello".to_string(),
|
||||||
|
});
|
||||||
|
rpc_server.registry().register(server, "test");
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
client.run();
|
||||||
|
|
||||||
|
let rpc_tasks = Arc::new(Mutex::new(JoinSet::new()));
|
||||||
|
let (mut rx, tx) = (
|
||||||
|
rpc_server.get_transport_stream(),
|
||||||
|
client.get_transport_sink(),
|
||||||
|
);
|
||||||
|
|
||||||
|
rpc_tasks.lock().unwrap().spawn(async move {
|
||||||
|
while let Some(Ok(packet)) = rx.next().await {
|
||||||
|
if let Err(err) = tx.send(packet).await {
|
||||||
|
println!("{:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// mock server is stuck (no task to do forwards)
|
||||||
|
|
||||||
|
let mut tasks = JoinSet::new();
|
||||||
|
for _ in 0..RING_TUNNEL_CAP + 15 {
|
||||||
|
let out =
|
||||||
|
client.scoped_client::<GreetingClientFactory<RpcController>>(1, 1, "test".to_string());
|
||||||
|
tasks.spawn(async move {
|
||||||
|
let mut ctrl = RpcController::default();
|
||||||
|
ctrl.timeout_ms = 1000;
|
||||||
|
|
||||||
|
let input = SayHelloRequest {
|
||||||
|
name: "world".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
out.say_hello(ctrl, input).await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while let Some(ret) = tasks.join_next().await {
|
||||||
|
assert!(matches!(ret, Ok(Err(rpc_types::error::Error::Timeout(_)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// start server consumer, new requests should be processed
|
||||||
|
let (mut rx, tx) = (
|
||||||
|
client.get_transport_stream(),
|
||||||
|
rpc_server.get_transport_sink(),
|
||||||
|
);
|
||||||
|
rpc_tasks.lock().unwrap().spawn(async move {
|
||||||
|
while let Some(Ok(packet)) = rx.next().await {
|
||||||
|
if let Err(err) = tx.send(packet).await {
|
||||||
|
println!("{:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let out =
|
||||||
|
client.scoped_client::<GreetingClientFactory<RpcController>>(1, 1, "test".to_string());
|
||||||
|
let mut ctrl = RpcController::default();
|
||||||
|
ctrl.timeout_ms = 1000;
|
||||||
|
let input = SayHelloRequest {
|
||||||
|
name: "fuck world".to_string(),
|
||||||
|
};
|
||||||
|
let ret = out.say_hello(ctrl, input).await.unwrap();
|
||||||
|
assert_eq!(ret.greeting, "Hello fuck world!");
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn standalone_rpc_test() {
|
async fn standalone_rpc_test() {
|
||||||
use crate::proto::rpc_impl::standalone::{StandAloneClient, StandAloneServer};
|
use crate::proto::rpc_impl::standalone::{StandAloneClient, StandAloneServer};
|
||||||
|
@@ -130,7 +130,7 @@ pub fn enable_log() {
|
|||||||
fn check_route(ipv4: &str, dst_peer_id: PeerId, routes: Vec<crate::proto::cli::Route>) {
|
fn check_route(ipv4: &str, dst_peer_id: PeerId, routes: Vec<crate::proto::cli::Route>) {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for r in routes.iter() {
|
for r in routes.iter() {
|
||||||
if r.ipv4_addr == ipv4.to_string() {
|
if r.ipv4_addr == Some(ipv4.parse().unwrap()) {
|
||||||
found = true;
|
found = true;
|
||||||
assert_eq!(r.peer_id, dst_peer_id, "{:?}", routes);
|
assert_eq!(r.peer_id, dst_peer_id, "{:?}", routes);
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ async fn wait_proxy_route_appear(
|
|||||||
let r = r;
|
let r = r;
|
||||||
if r.proxy_cidrs.contains(&proxy_cidr.to_owned()) {
|
if r.proxy_cidrs.contains(&proxy_cidr.to_owned()) {
|
||||||
assert_eq!(r.peer_id, dst_peer_id);
|
assert_eq!(r.peer_id, dst_peer_id);
|
||||||
assert_eq!(r.ipv4_addr, ipv4);
|
assert_eq!(r.ipv4_addr, Some(ipv4.parse().unwrap()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -184,13 +184,13 @@ pub async fn basic_three_node_test(#[values("tcp", "udp", "wg", "ws", "wss")] pr
|
|||||||
let insts = init_three_node(proto).await;
|
let insts = init_three_node(proto).await;
|
||||||
|
|
||||||
check_route(
|
check_route(
|
||||||
"10.144.144.2",
|
"10.144.144.2/24",
|
||||||
insts[1].peer_id(),
|
insts[1].peer_id(),
|
||||||
insts[0].get_peer_manager().list_routes().await,
|
insts[0].get_peer_manager().list_routes().await,
|
||||||
);
|
);
|
||||||
|
|
||||||
check_route(
|
check_route(
|
||||||
"10.144.144.3",
|
"10.144.144.3/24",
|
||||||
insts[2].peer_id(),
|
insts[2].peer_id(),
|
||||||
insts[0].get_peer_manager().list_routes().await,
|
insts[0].get_peer_manager().list_routes().await,
|
||||||
);
|
);
|
||||||
@@ -357,7 +357,7 @@ pub async fn subnet_proxy_three_node_test(
|
|||||||
|
|
||||||
wait_proxy_route_appear(
|
wait_proxy_route_appear(
|
||||||
&insts[0].get_peer_manager(),
|
&insts[0].get_peer_manager(),
|
||||||
"10.144.144.3",
|
"10.144.144.3/24",
|
||||||
insts[2].peer_id(),
|
insts[2].peer_id(),
|
||||||
"10.1.2.0/24",
|
"10.1.2.0/24",
|
||||||
)
|
)
|
||||||
|
@@ -41,13 +41,13 @@ pub struct MpscTunnel<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Tunnel> MpscTunnel<T> {
|
impl<T: Tunnel> MpscTunnel<T> {
|
||||||
pub fn new(tunnel: T) -> Self {
|
pub fn new(tunnel: T, send_timeout: Option<Duration>) -> Self {
|
||||||
let (tx, mut rx) = channel(32);
|
let (tx, mut rx) = channel(32);
|
||||||
let (stream, mut sink) = tunnel.split();
|
let (stream, mut sink) = tunnel.split();
|
||||||
|
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
if let Err(e) = Self::forward_one_round(&mut rx, &mut sink).await {
|
if let Err(e) = Self::forward_one_round(&mut rx, &mut sink, send_timeout).await {
|
||||||
tracing::error!(?e, "forward error");
|
tracing::error!(?e, "forward error");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -68,21 +68,59 @@ impl<T: Tunnel> MpscTunnel<T> {
|
|||||||
async fn forward_one_round(
|
async fn forward_one_round(
|
||||||
rx: &mut Receiver<ZCPacket>,
|
rx: &mut Receiver<ZCPacket>,
|
||||||
sink: &mut Pin<Box<dyn ZCPacketSink>>,
|
sink: &mut Pin<Box<dyn ZCPacketSink>>,
|
||||||
|
send_timeout_ms: Option<Duration>,
|
||||||
) -> Result<(), TunnelError> {
|
) -> Result<(), TunnelError> {
|
||||||
let item = rx.recv().await.with_context(|| "recv error")?;
|
let item = rx.recv().await.with_context(|| "recv error")?;
|
||||||
sink.feed(item).await?;
|
if let Some(timeout_ms) = send_timeout_ms {
|
||||||
|
Self::forward_one_round_with_timeout(rx, sink, item, timeout_ms).await
|
||||||
|
} else {
|
||||||
|
Self::forward_one_round_no_timeout(rx, sink, item).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn forward_one_round_no_timeout(
|
||||||
|
rx: &mut Receiver<ZCPacket>,
|
||||||
|
sink: &mut Pin<Box<dyn ZCPacketSink>>,
|
||||||
|
initial_item: ZCPacket,
|
||||||
|
) -> Result<(), TunnelError> {
|
||||||
|
sink.feed(initial_item).await?;
|
||||||
|
|
||||||
while let Ok(item) = rx.try_recv() {
|
while let Ok(item) = rx.try_recv() {
|
||||||
if let Err(e) = timeout(Duration::from_secs(5), sink.feed(item))
|
match sink.feed(item).await {
|
||||||
.await
|
Err(e) => {
|
||||||
.unwrap()
|
tracing::error!(?e, "feed error");
|
||||||
{
|
return Err(e);
|
||||||
tracing::error!(?e, "feed error");
|
}
|
||||||
break;
|
Ok(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sink.flush().await
|
sink.flush().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn forward_one_round_with_timeout(
|
||||||
|
rx: &mut Receiver<ZCPacket>,
|
||||||
|
sink: &mut Pin<Box<dyn ZCPacketSink>>,
|
||||||
|
initial_item: ZCPacket,
|
||||||
|
timeout_ms: Duration,
|
||||||
|
) -> Result<(), TunnelError> {
|
||||||
|
match timeout(timeout_ms, async move {
|
||||||
|
Self::forward_one_round_no_timeout(rx, sink, initial_item).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(_)) => Ok(()),
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
tracing::error!(?e, "forward error");
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(?e, "forward timeout");
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_stream(&mut self) -> Pin<Box<dyn ZCPacketStream>> {
|
pub fn get_stream(&mut self) -> Pin<Box<dyn ZCPacketStream>> {
|
||||||
self.stream.take().unwrap()
|
self.stream.take().unwrap()
|
||||||
}
|
}
|
||||||
@@ -97,17 +135,12 @@ impl<T: Tunnel> MpscTunnel<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Tunnel> From<T> for MpscTunnel<T> {
|
|
||||||
fn from(tunnel: T) -> Self {
|
|
||||||
Self::new(tunnel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
use crate::tunnel::{
|
use crate::tunnel::{
|
||||||
|
ring::{create_ring_tunnel_pair, RING_TUNNEL_CAP},
|
||||||
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
tcp::{TcpTunnelConnector, TcpTunnelListener},
|
||||||
TunnelConnector, TunnelListener,
|
TunnelConnector, TunnelListener,
|
||||||
};
|
};
|
||||||
@@ -147,7 +180,7 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let tunnel = connector.connect().await.unwrap();
|
let tunnel = connector.connect().await.unwrap();
|
||||||
let mpsc_tunnel = MpscTunnel::from(tunnel);
|
let mpsc_tunnel = MpscTunnel::new(tunnel, None);
|
||||||
|
|
||||||
let sink1 = mpsc_tunnel.get_sink();
|
let sink1 = mpsc_tunnel.get_sink();
|
||||||
let t2 = tokio::spawn(async move {
|
let t2 = tokio::spawn(async move {
|
||||||
@@ -198,4 +231,24 @@ mod tests {
|
|||||||
|
|
||||||
let _ = tokio::join!(t1, t2, t3, t4);
|
let _ = tokio::join!(t1, t2, t3, t4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn mpsc_slow_receiver_with_send_timeout() {
|
||||||
|
let (a, _b) = create_ring_tunnel_pair();
|
||||||
|
let mpsc_tunnel = MpscTunnel::new(a, Some(Duration::from_secs(1)));
|
||||||
|
let s = mpsc_tunnel.get_sink();
|
||||||
|
for _ in 0..RING_TUNNEL_CAP {
|
||||||
|
s.send(ZCPacket::new_with_payload(&[0; 1024]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(1500)).await;
|
||||||
|
let e = s.send(ZCPacket::new_with_payload(&[0; 1024])).await;
|
||||||
|
assert!(e.is_ok());
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_millis(1500)).await;
|
||||||
|
|
||||||
|
let e = s.send(ZCPacket::new_with_payload(&[0; 1024])).await;
|
||||||
|
assert!(e.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ use super::{
|
|||||||
StreamItem, Tunnel, TunnelConnector, TunnelError, TunnelInfo, TunnelListener,
|
StreamItem, Tunnel, TunnelConnector, TunnelError, TunnelInfo, TunnelListener,
|
||||||
};
|
};
|
||||||
|
|
||||||
static RING_TUNNEL_CAP: usize = 128;
|
pub static RING_TUNNEL_CAP: usize = 128;
|
||||||
static RING_TUNNEL_RESERVERD_CAP: usize = 4;
|
static RING_TUNNEL_RESERVERD_CAP: usize = 4;
|
||||||
|
|
||||||
type RingLock = parking_lot::Mutex<()>;
|
type RingLock = parking_lot::Mutex<()>;
|
||||||
|
@@ -296,8 +296,8 @@ impl UdpTunnelListenerData {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ring_for_send_udp = Arc::new(RingTunnel::new(64));
|
let ring_for_send_udp = Arc::new(RingTunnel::new(128));
|
||||||
let ring_for_recv_udp = Arc::new(RingTunnel::new(64));
|
let ring_for_recv_udp = Arc::new(RingTunnel::new(128));
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
?ring_for_send_udp,
|
?ring_for_send_udp,
|
||||||
?ring_for_recv_udp,
|
?ring_for_recv_udp,
|
||||||
@@ -559,8 +559,8 @@ impl UdpTunnelConnector {
|
|||||||
dst_addr: SocketAddr,
|
dst_addr: SocketAddr,
|
||||||
conn_id: u32,
|
conn_id: u32,
|
||||||
) -> Result<Box<dyn super::Tunnel>, super::TunnelError> {
|
) -> Result<Box<dyn super::Tunnel>, super::TunnelError> {
|
||||||
let ring_for_send_udp = Arc::new(RingTunnel::new(32));
|
let ring_for_send_udp = Arc::new(RingTunnel::new(128));
|
||||||
let ring_for_recv_udp = Arc::new(RingTunnel::new(32));
|
let ring_for_recv_udp = Arc::new(RingTunnel::new(128));
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
?ring_for_send_udp,
|
?ring_for_send_udp,
|
||||||
?ring_for_recv_udp,
|
?ring_for_recv_udp,
|
||||||
|
@@ -81,7 +81,7 @@ impl WireGuardImpl {
|
|||||||
wg_peer_ip_table: WgPeerIpTable,
|
wg_peer_ip_table: WgPeerIpTable,
|
||||||
) {
|
) {
|
||||||
let info = t.info().unwrap_or_default();
|
let info = t.info().unwrap_or_default();
|
||||||
let mut mpsc_tunnel = MpscTunnel::new(t);
|
let mut mpsc_tunnel = MpscTunnel::new(t, None);
|
||||||
let mut stream = mpsc_tunnel.get_stream();
|
let mut stream = mpsc_tunnel.get_stream();
|
||||||
let mut ip_registered = false;
|
let mut ip_registered = false;
|
||||||
|
|
||||||
@@ -284,13 +284,11 @@ impl VpnPortal for WireGuard {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
for ipv4 in routes
|
for ipv4 in routes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.ipv4_addr.clone())
|
.filter(|x| x.ipv4_addr.is_some())
|
||||||
.chain(global_ctx.get_ipv4().iter().map(|x| x.to_string()))
|
.map(|x| x.ipv4_addr.unwrap())
|
||||||
|
.chain(global_ctx.get_ipv4().into_iter().map(Into::into))
|
||||||
{
|
{
|
||||||
let Ok(ipv4) = ipv4.parse() else {
|
let inet = Ipv4Inet::from(ipv4);
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let inet = Ipv4Inet::new(ipv4, 24).unwrap();
|
|
||||||
allow_ips.push(inet.network().to_string());
|
allow_ips.push(inet.network().to_string());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user