mirror of
https://github.com/EasyTier/EasyTier.git
synced 2025-10-04 00:16:41 +08:00
Compare commits
9 Commits
manage-con
...
v2.4.5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c0d2045e52 | ||
![]() |
835cd407bf | ||
![]() |
f5ba5bb146 | ||
![]() |
7a694257d9 | ||
![]() |
67abf4446d | ||
![]() |
7035a3fef4 | ||
![]() |
4445916ba7 | ||
![]() |
a102a8bfc7 | ||
![]() |
c9e8c35e77 |
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
image_tag:
|
||||
description: 'Tag for this image build'
|
||||
type: string
|
||||
default: 'v2.4.4'
|
||||
default: 'v2.4.5'
|
||||
required: true
|
||||
mark_latest:
|
||||
description: 'Mark this image as latest'
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ on:
|
||||
version:
|
||||
description: 'Version for this release'
|
||||
type: string
|
||||
default: 'v2.4.4'
|
||||
default: 'v2.4.5'
|
||||
required: true
|
||||
make_latest:
|
||||
description: 'Mark this release as latest'
|
||||
|
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2108,7 +2108,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||
|
||||
[[package]]
|
||||
name = "easytier"
|
||||
version = "2.4.4"
|
||||
version = "2.4.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -2258,7 +2258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "easytier-gui"
|
||||
version = "2.4.4"
|
||||
version = "2.4.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2345,7 +2345,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "easytier-web"
|
||||
version = "2.4.4"
|
||||
version = "2.4.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@@ -105,9 +105,9 @@ After successful execution, you can check the network status using `easytier-cli
|
||||
```text
|
||||
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
|
||||
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
|
||||
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.4-70e69a38~ |
|
||||
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.4-70e69a38~ |
|
||||
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.4-70e69a38~ |
|
||||
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.5-70e69a38~ |
|
||||
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.5-70e69a38~ |
|
||||
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.5-70e69a38~ |
|
||||
```
|
||||
|
||||
You can test connectivity between nodes:
|
||||
|
@@ -106,9 +106,9 @@ sudo easytier-core -d --network-name abc --network-secret abc -p tcp://public.ea
|
||||
```text
|
||||
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
|
||||
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
|
||||
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.4-70e69a38~ |
|
||||
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.4-70e69a38~ |
|
||||
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.4-70e69a38~ |
|
||||
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.5-70e69a38~ |
|
||||
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.5-70e69a38~ |
|
||||
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.5-70e69a38~ |
|
||||
```
|
||||
|
||||
您可以测试节点之间的连通性:
|
||||
|
@@ -22,7 +22,10 @@ get_tun_iface() {
|
||||
ip link | awk -F': ' '/ tun[[:alnum:]]+/ {print $2; exit}'
|
||||
}
|
||||
get_hot_iface() {
|
||||
ip link | awk -F': ' '/(^| )(swlan[[:alnum:]_]*|softap[[:alnum:]_]*|ap[[:alnum:]_]*)\:/ {print $2; exit}' | cut -d'@' -f1 | head -n1
|
||||
ip link | awk -F': ' '/(^| )(swlan[[:alnum:]_]*|softap[[:alnum:]_]*|p2p-wlan[[:alnum:]_]*|ap[[:alnum:]_]*)\:/ {print $2; exit}' | cut -d'@' -f1 | head -n1
|
||||
}
|
||||
get_usb_iface() {
|
||||
ip link | awk -F': ' '/(^| )(usb[[:alnum:]_]*|rndis[[:alnum:]_]*|eth[[:alnum:]_]*)\:/ {print $2; exit}' | cut -d'@' -f1 | head -n1
|
||||
}
|
||||
get_hot_cidr() {
|
||||
ip -4 addr show dev "$1" | awk '/inet /{print $2; exit}'
|
||||
@@ -33,10 +36,12 @@ set_nat_rules() {
|
||||
ET_IFACE=$(get_et_iface)
|
||||
[ -z "$ET_IFACE" ] && ET_IFACE="$(get_tun_iface)"
|
||||
HOT_IFACE=$(get_hot_iface)
|
||||
USB_IFACE=$(get_usb_iface)
|
||||
HOT_CIDR=$(get_hot_cidr "$HOT_IFACE")
|
||||
USB_CIDR=$(get_hot_cidr "$USB_IFACE")
|
||||
|
||||
# 如果热点关闭就删除自定义链
|
||||
[ -n "$ET_IFACE" ] && [ -n "$HOT_CIDR" ] || return 1
|
||||
[ -n "$ET_IFACE" ] && { [ -n "$HOT_CIDR" ] || [ -n "$USB_CIDR" ]; } || return 1
|
||||
|
||||
# 创建自定义链(如不存在)
|
||||
iptables -t nat -N ET_NAT 2>/dev/null
|
||||
@@ -49,13 +54,22 @@ set_nat_rules() {
|
||||
iptables -I FORWARD 1 -j ET_FWD
|
||||
|
||||
# 添加规则
|
||||
iptables -t nat -A ET_NAT -s "$HOT_CIDR" -o "$ET_IFACE" -j MASQUERADE
|
||||
iptables -A ET_FWD -i "$HOT_IFACE" -o "$ET_IFACE" \
|
||||
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A ET_FWD -i "$ET_IFACE" -o "$HOT_IFACE" \
|
||||
-m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
echo "[ET-NAT] Rules applied: $HOT_IFACE $HOT_CIDR ↔ $ET_IFACE" >> "$LOG_FILE"
|
||||
if [ -n "$HOT_CIDR" ]; then
|
||||
iptables -t nat -A ET_NAT -s "$HOT_CIDR" -o "$ET_IFACE" -j MASQUERADE
|
||||
iptables -A ET_FWD -i "$HOT_IFACE" -o "$ET_IFACE" \
|
||||
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A ET_FWD -i "$ET_IFACE" -o "$HOT_IFACE" \
|
||||
-m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
echo "[ET-NAT] Rules applied: $HOT_IFACE $HOT_CIDR ↔ $ET_IFACE" >> "$LOG_FILE"
|
||||
fi
|
||||
if [ -n "$USB_CIDR" ]; then
|
||||
iptables -t nat -A ET_NAT -s "$USB_CIDR" -o "$ET_IFACE" -j MASQUERADE
|
||||
iptables -A ET_FWD -i "$USB_IFACE" -o "$ET_IFACE" \
|
||||
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A ET_FWD -i "$ET_IFACE" -o "$USB_IFACE" \
|
||||
-m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
echo "[ET-NAT] Rules applied: $USB_IFACE $USB_CIDR ↔ $ET_IFACE" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
flush_rules() {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
id=easytier_magisk
|
||||
name=EasyTier_Magisk
|
||||
version=v2.4.4
|
||||
version=v2.4.5
|
||||
versionCode=1
|
||||
author=EasyTier
|
||||
description=easytier magisk module @EasyTier(https://github.com/EasyTier/EasyTier)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "easytier-gui",
|
||||
"type": "module",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.5",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
||||
"scripts": {
|
||||
@@ -13,18 +13,17 @@
|
||||
"lint:fix": "eslint . --ignore-pattern src-tauri --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primevue/themes": "4.3.3",
|
||||
"@primeuix/themes": "^1.2.3",
|
||||
"@tauri-apps/plugin-autostart": "2.0.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.3.0",
|
||||
"@tauri-apps/plugin-os": "2.3.0",
|
||||
"@tauri-apps/plugin-process": "2.3.0",
|
||||
"@tauri-apps/plugin-shell": "2.3.0",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"aura": "link:@primevue\\themes\\aura",
|
||||
"easytier-frontend-lib": "workspace:*",
|
||||
"ip-num": "1.5.1",
|
||||
"pinia": "^2.2.4",
|
||||
"primevue": "4.3.3",
|
||||
"primevue": "^4.3.9",
|
||||
"tauri-plugin-vpnservice-api": "workspace:*",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5"
|
||||
@@ -32,7 +31,7 @@
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.7.3",
|
||||
"@intlify/unplugin-vue-i18n": "^5.2.0",
|
||||
"@primevue/auto-import-resolver": "4.3.3",
|
||||
"@primevue/auto-import-resolver": "4.3.9",
|
||||
"@tauri-apps/api": "2.7.0",
|
||||
"@tauri-apps/cli": "2.7.1",
|
||||
"@types/default-gateway": "^7.2.2",
|
||||
|
7220
easytier-gui/pnpm-lock.yaml
generated
7220
easytier-gui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "easytier-gui"
|
||||
version = "2.4.4"
|
||||
version = "2.4.5"
|
||||
description = "EasyTier GUI"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
@@ -17,9 +17,13 @@
|
||||
"createUpdaterArtifacts": false
|
||||
},
|
||||
"productName": "easytier-gui",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.5",
|
||||
"identifier": "com.kkrainbow.easytier",
|
||||
"plugins": {},
|
||||
"plugins": {
|
||||
"shell": {
|
||||
"open": "^.+"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
|
12
easytier-gui/src/auto-imports.d.ts
vendored
12
easytier-gui/src/auto-imports.d.ts
vendored
@@ -9,7 +9,6 @@ declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const MenuItemExit: typeof import('./composables/tray')['MenuItemExit']
|
||||
const MenuItemShow: typeof import('./composables/tray')['MenuItemShow']
|
||||
const ReinitTray: typeof import('./composables/tray')['ReinitTray']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const collectNetworkInfos: typeof import('./composables/network')['collectNetworkInfos']
|
||||
const computed: typeof import('vue')['computed']
|
||||
@@ -18,10 +17,8 @@ declare global {
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
|
||||
const defineStore: typeof import('pinia')['defineStore']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const event2human: typeof import('./composables/utils')['event2human']
|
||||
const generateMenuItem: typeof import('./composables/tray')['generateMenuItem']
|
||||
const generateNetworkConfig: typeof import('./composables/network')['generateNetworkConfig']
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
@@ -30,7 +27,6 @@ declare global {
|
||||
const getEasytierVersion: typeof import('./composables/network')['getEasytierVersion']
|
||||
const getOsHostname: typeof import('./composables/network')['getOsHostname']
|
||||
const h: typeof import('vue')['h']
|
||||
const initMobileService: typeof import('./composables/mobile_vpn')['initMobileService']
|
||||
const initMobileVpnService: typeof import('./composables/mobile_vpn')['initMobileVpnService']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isAutostart: typeof import('./composables/network')['isAutostart']
|
||||
@@ -38,7 +34,6 @@ declare global {
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const loadRunningInstanceIdsFromLocalStorage: typeof import('./stores/network')['loadRunningInstanceIdsFromLocalStorage']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
@@ -46,8 +41,6 @@ declare global {
|
||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
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 onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
@@ -74,7 +67,6 @@ declare global {
|
||||
const retainNetworkInstance: typeof import('./composables/network')['retainNetworkInstance']
|
||||
const runNetworkInstance: typeof import('./composables/network')['runNetworkInstance']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setAutoLaunchStatus: typeof import('./composables/network')['setAutoLaunchStatus']
|
||||
const setLoggingLevel: typeof import('./composables/network')['setLoggingLevel']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const setTrayMenu: typeof import('./composables/tray')['setTrayMenu']
|
||||
@@ -85,7 +77,6 @@ declare global {
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||
const timeAgoCn: typeof import('./composables/utils')['timeAgoCn']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
@@ -116,6 +107,7 @@ declare global {
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
|
||||
// for vue template auto import
|
||||
import { UnwrapRef } from 'vue'
|
||||
declare module 'vue' {
|
||||
@@ -216,4 +208,4 @@ declare module 'vue' {
|
||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||
}
|
||||
}
|
||||
}
|
@@ -93,7 +93,7 @@ async function registerVpnServiceListener() {
|
||||
)
|
||||
}
|
||||
|
||||
function getRoutesForVpn(routes: Route[]): string[] {
|
||||
function getRoutesForVpn(routes: Route[], node_config: NetworkTypes.NetworkConfig): string[] {
|
||||
if (!routes) {
|
||||
return []
|
||||
}
|
||||
@@ -108,6 +108,10 @@ function getRoutesForVpn(routes: Route[]): string[] {
|
||||
}
|
||||
}
|
||||
|
||||
node_config.routes.forEach(r => {
|
||||
ret.push(r)
|
||||
})
|
||||
|
||||
// sort and dedup
|
||||
return Array.from(new Set(ret)).sort()
|
||||
}
|
||||
@@ -142,7 +146,7 @@ async function onNetworkInstanceChange() {
|
||||
network_length = 24
|
||||
}
|
||||
|
||||
const routes = getRoutesForVpn(curNetworkInfo?.routes)
|
||||
const routes = getRoutesForVpn(curNetworkInfo?.routes, networkStore.curNetwork)
|
||||
|
||||
const ipChanged = virtual_ip !== curVpnStatus.ipv4Addr
|
||||
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Aura from '@primevue/themes/aura'
|
||||
import Aura from '@primeuix/themes/aura';
|
||||
import PrimeVue from 'primevue/config'
|
||||
import ToastService from 'primevue/toastservice'
|
||||
|
||||
|
@@ -292,61 +292,63 @@ async function saveTomlConfig(tomlConfig: string) {
|
||||
<About />
|
||||
</Dialog>
|
||||
|
||||
<div>
|
||||
<Toolbar>
|
||||
<template #start>
|
||||
<div class="flex items-center">
|
||||
<Button icon="pi pi-plus" severity="primary" :label="t('add_new_network')" @click="addNewNetwork" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full">
|
||||
<div class="flex items-center gap-4 p-4 h-20">
|
||||
<!-- 网络按钮 -->
|
||||
<div class="flex shrink-0 items-center">
|
||||
<Button icon="pi pi-plus" severity="primary" :label="t('add_new_network')" class="hidden md:inline-flex"
|
||||
@click="addNewNetwork" />
|
||||
<Button icon="pi pi-plus" severity="primary" class="md:hidden px-6" @click="addNewNetwork" />
|
||||
</div>
|
||||
|
||||
<template #center>
|
||||
<div class="min-w-40">
|
||||
<Select v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
||||
:placeholder="t('select_network')" class="w-full">
|
||||
<template #value="slotProps">
|
||||
<div class="flex items-start content-center">
|
||||
<div class="mr-4 flex-col">
|
||||
<span>{{ slotProps.value.network_name }}</span>
|
||||
</div>
|
||||
<Tag class="my-auto leading-3" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
||||
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||
<!-- 网络选择 - 占据中间剩余空间 -->
|
||||
<Select v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
||||
:placeholder="t('select_network')" class="flex-1 h-full min-w-0">
|
||||
<template #value="slotProps">
|
||||
<div class="flex items-center content-center min-w-0">
|
||||
<div class="mr-4 flex-col min-w-0 flex-1">
|
||||
<span class="truncate block"> {{ slotProps.value.network_name }}</span>
|
||||
</div>
|
||||
<Tag class="my-auto leading-3 shrink-0"
|
||||
:severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
||||
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #option="slotProps">
|
||||
<div class="flex flex-col items-start content-center max-w-full">
|
||||
<div class="flex items-center min-w-0 w-full">
|
||||
<div class="mr-4 min-w-0 flex-1">
|
||||
<span class="truncate block">{{ t('network_name') }}: {{ slotProps.option.network_name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #option="slotProps">
|
||||
<div class="flex flex-col items-start content-center max-w-full">
|
||||
<div class="flex">
|
||||
<div class="mr-4">
|
||||
{{ t('network_name') }}: {{ slotProps.option.network_name }}
|
||||
</div>
|
||||
<Tag class="my-auto leading-3"
|
||||
:severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
||||
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||
</div>
|
||||
<div v-if="slotProps.option.networking_method !== NetworkTypes.NetworkingMethod.Standalone"
|
||||
class="max-w-full overflow-hidden text-ellipsis">
|
||||
{{ slotProps.option.networking_method === NetworkTypes.NetworkingMethod.Manual
|
||||
? slotProps.option.peer_urls.join(', ')
|
||||
: slotProps.option.public_server_url }}
|
||||
</div>
|
||||
<div
|
||||
v-if="isRunning(slotProps.option.instance_id) && networkStore.instances[slotProps.option.instance_id].detail && (!!networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)">
|
||||
{{
|
||||
Utils.ipv4InetToString(networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
</div>
|
||||
</template>
|
||||
<Tag class="my-auto leading-3 shrink-0"
|
||||
:severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
||||
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||
</div>
|
||||
<div v-if="slotProps.option.networking_method !== NetworkTypes.NetworkingMethod.Standalone"
|
||||
class="max-w-full overflow-hidden text-ellipsis">
|
||||
{{ slotProps.option.networking_method === NetworkTypes.NetworkingMethod.Manual
|
||||
? slotProps.option.peer_urls.join(', ')
|
||||
: slotProps.option.public_server_url }}
|
||||
</div>
|
||||
<div
|
||||
v-if="isRunning(slotProps.option.instance_id) && networkStore.instances[slotProps.option.instance_id].detail && (!!networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)">
|
||||
{{
|
||||
Utils.ipv4InetToString(networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
|
||||
<template #end>
|
||||
<!-- 设置按钮 -->
|
||||
<div class="flex items-center shrink-0">
|
||||
<Button icon="pi pi-cog" severity="secondary" aria-haspopup="true" :label="t('settings')"
|
||||
class="hidden md:inline-flex" aria-controls="overlay_setting_menu" @click="toggle_setting_menu" />
|
||||
<Button icon="pi pi-cog" severity="secondary" aria-haspopup="true" class="md:hidden px-6"
|
||||
aria-controls="overlay_setting_menu" @click="toggle_setting_menu" />
|
||||
<TieredMenu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
||||
</template>
|
||||
</Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Panel class="h-full overflow-y-auto">
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "easytier-web"
|
||||
version = "2.4.4"
|
||||
version = "2.4.5"
|
||||
edition = "2021"
|
||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||
|
||||
|
@@ -18,18 +18,19 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primevue/themes": "4.3.3",
|
||||
"@primeuix/themes": "^1.2.3",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"aura": "link:@primevue\\themes\\aura",
|
||||
"axios": "^1.7.7",
|
||||
"chart.js": "^4.5.0",
|
||||
"floating-vue": "^5.2",
|
||||
"ip-num": "1.5.1",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "4.3.3",
|
||||
"primevue": "^4.3.9",
|
||||
"tailwindcss-primeui": "^0.3.4",
|
||||
"ts-md5": "^1.3.1",
|
||||
"uuid": "^11.0.2",
|
||||
"vue": "^3.5.12",
|
||||
"vue-chartjs": "^5.3.2",
|
||||
"vue-i18n": "^10.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -170,7 +170,7 @@ const bool_flags: BoolFlag[] = [
|
||||
{ field: 'enable_private_mode', help: 'enable_private_mode_help' },
|
||||
]
|
||||
|
||||
const portForwardProtocolOptions = ref(["tcp","udp"]);
|
||||
const portForwardProtocolOptions = ref(["tcp", "udp"]);
|
||||
|
||||
</script>
|
||||
|
||||
@@ -178,7 +178,7 @@ const portForwardProtocolOptions = ref(["tcp","udp"]);
|
||||
<div class="frontend-lib">
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex flex-col">
|
||||
<div class="w-11/12 self-center ">
|
||||
<div class="w-full self-center ">
|
||||
<Panel :header="t('basic_settings')">
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
||||
@@ -227,9 +227,8 @@ const portForwardProtocolOptions = ref(["tcp","udp"]);
|
||||
class="grow" multiple fluid :suggestions="peerSuggestions" @complete="searchPeerSuggestions" />
|
||||
|
||||
<AutoComplete v-if="curNetwork.networking_method === NetworkingMethod.PublicServer"
|
||||
v-model="curNetwork.public_server_url" :suggestions="publicServerSuggestions"
|
||||
class="grow" dropdown :complete-on-focus="false"
|
||||
@complete="searchPresetPublicServers" />
|
||||
v-model="curNetwork.public_server_url" :suggestions="publicServerSuggestions" class="grow"
|
||||
dropdown :complete-on-focus="false" @complete="searchPresetPublicServers" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -436,56 +435,36 @@ const portForwardProtocolOptions = ref(["tcp","udp"]);
|
||||
</div>
|
||||
<div v-for="(row, index) in curNetwork.port_forwards" class="form-row">
|
||||
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
|
||||
<SelectButton v-model="row.proto" :options="portForwardProtocolOptions" :allow-empty="false"/>
|
||||
<SelectButton v-model="row.proto" :options="portForwardProtocolOptions" :allow-empty="false" />
|
||||
<div style="flex-grow: 4;">
|
||||
<InputGroup>
|
||||
<InputText
|
||||
v-model="row.bind_ip"
|
||||
:placeholder="t('port_forwards_bind_addr')"
|
||||
/>
|
||||
<InputText v-model="row.bind_ip" :placeholder="t('port_forwards_bind_addr')" />
|
||||
<InputGroupAddon>
|
||||
<span style="font-weight: bold">:</span>
|
||||
</InputGroupAddon>
|
||||
<InputNumber v-model="row.bind_port" :format="false"
|
||||
inputId="horizontal-buttons" :step="1" mode="decimal" :min="1"
|
||||
:max="65535" fluid
|
||||
class="max-w-20"/>
|
||||
<InputNumber v-model="row.bind_port" :format="false" inputId="horizontal-buttons" :step="1"
|
||||
mode="decimal" :min="1" :max="65535" fluid class="max-w-20" />
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div style="flex-grow: 4;">
|
||||
<InputGroup>
|
||||
<InputText
|
||||
v-model="row.dst_ip"
|
||||
:placeholder="t('port_forwards_dst_addr')"
|
||||
/>
|
||||
<InputText v-model="row.dst_ip" :placeholder="t('port_forwards_dst_addr')" />
|
||||
<InputGroupAddon>
|
||||
<span style="font-weight: bold">:</span>
|
||||
</InputGroupAddon>
|
||||
<InputNumber v-model="row.dst_port" :format="false"
|
||||
inputId="horizontal-buttons" :step="1" mode="decimal" :min="1"
|
||||
:max="65535" fluid
|
||||
class="max-w-20"/>
|
||||
<InputNumber v-model="row.dst_port" :format="false" inputId="horizontal-buttons" :step="1"
|
||||
mode="decimal" :min="1" :max="65535" fluid class="max-w-20" />
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div style="flex-grow: 1;">
|
||||
<Button
|
||||
v-if="curNetwork.port_forwards.length > 0"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
text
|
||||
rounded
|
||||
@click="removeRow(index,curNetwork.port_forwards)"
|
||||
/>
|
||||
<Button v-if="curNetwork.port_forwards.length > 0" icon="pi pi-trash" severity="danger" text
|
||||
rounded @click="removeRow(index, curNetwork.port_forwards)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-content-end mt-4">
|
||||
<Button
|
||||
icon="pi pi-plus"
|
||||
:label="t('port_forwards_add_btn')"
|
||||
severity="success"
|
||||
@click="addRow(curNetwork.port_forwards)"
|
||||
/>
|
||||
<Button icon="pi pi-plus" :label="t('port_forwards_add_btn')" severity="success"
|
||||
@click="addRow(curNetwork.port_forwards)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
279
easytier-web/frontend-lib/src/components/NetworkChart.vue
Normal file
279
easytier-web/frontend-lib/src/components/NetworkChart.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-blue-900/20 dark:to-indigo-800/20 rounded-xl p-4 border border-blue-200 dark:border-blue-700 shadow-md hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-center justify-center mb-3">
|
||||
<div class="flex gap-2 text-sm">
|
||||
<span class="flex items-center gap-1 w-32">
|
||||
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span class="text-green-600 dark:text-green-400 truncate">{{ t('upload') }}: {{ currentUpload }}/s</span>
|
||||
</span>
|
||||
<span class="flex items-center gap-1 w-32">
|
||||
<div class="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span class="text-blue-600 dark:text-blue-400 truncate">{{ t('download') }}: {{ currentDownload }}/s</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-32">
|
||||
<canvas ref="chartCanvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
LineController,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Filler
|
||||
} from 'chart.js'
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 注册Chart.js组件
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
LineController,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Filler
|
||||
)
|
||||
|
||||
interface Props {
|
||||
uploadRate: string
|
||||
downloadRate: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const chartCanvas = ref<HTMLCanvasElement>()
|
||||
let chart: ChartJS | null = null
|
||||
let updateTimer: number | null = null
|
||||
|
||||
// 存储历史数据,最多保存30个数据点(1分钟历史)
|
||||
const maxDataPoints = 120
|
||||
const uploadHistory: number[] = []
|
||||
const downloadHistory: number[] = []
|
||||
const timeLabels: string[] = []
|
||||
|
||||
const currentUpload = ref('0')
|
||||
const currentDownload = ref('0')
|
||||
|
||||
// 将带单位的速率字符串转换为字节数
|
||||
function parseRateToBytes(rateStr: string): number {
|
||||
if (!rateStr || rateStr === '0') return 0
|
||||
|
||||
const match = rateStr.match(/([0-9.]+)\s*([KMGT]?i?B)/i)
|
||||
if (!match) return 0
|
||||
|
||||
const value = parseFloat(match[1])
|
||||
const unit = match[2].toUpperCase()
|
||||
|
||||
const multipliers: { [key: string]: number } = {
|
||||
'B': 1,
|
||||
'KB': 1000,
|
||||
'KIB': 1024,
|
||||
'MB': 1000000,
|
||||
'MIB': 1024 * 1024,
|
||||
'GB': 1000000000,
|
||||
'GIB': 1024 * 1024 * 1024,
|
||||
'TB': 1000000000000,
|
||||
'TIB': 1024 * 1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
return value * (multipliers[unit] || 1)
|
||||
}
|
||||
|
||||
// 格式化字节为可读格式
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes < 1) return bytes.toFixed(1) + ' B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
function updateData() {
|
||||
const uploadBytes = parseRateToBytes(props.uploadRate)
|
||||
const downloadBytes = parseRateToBytes(props.downloadRate)
|
||||
|
||||
currentUpload.value = formatBytes(uploadBytes)
|
||||
currentDownload.value = formatBytes(downloadBytes)
|
||||
|
||||
// 添加新数据点
|
||||
uploadHistory.push(uploadBytes)
|
||||
downloadHistory.push(downloadBytes)
|
||||
|
||||
// 生成时间标签
|
||||
const now = new Date()
|
||||
const timeStr = now.toLocaleTimeString('zh-CN', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
timeLabels.push(timeStr)
|
||||
|
||||
// 保持数据点数量不超过最大值
|
||||
if (uploadHistory.length > maxDataPoints) {
|
||||
uploadHistory.shift()
|
||||
downloadHistory.shift()
|
||||
timeLabels.shift()
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
if (chart) {
|
||||
chart.data.labels = timeLabels
|
||||
chart.data.datasets[0].data = uploadHistory
|
||||
chart.data.datasets[1].data = downloadHistory
|
||||
chart.update('none')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
function initChart() {
|
||||
if (!chartCanvas.value) return
|
||||
|
||||
const ctx = chartCanvas.value.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
chart = new ChartJS(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: timeLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: t('upload'),
|
||||
data: uploadHistory,
|
||||
borderColor: 'rgb(34, 197, 94)',
|
||||
backgroundColor: 'rgba(34, 197, 94, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4
|
||||
},
|
||||
{
|
||||
label: t('download'),
|
||||
data: downloadHistory,
|
||||
borderColor: 'rgb(59, 130, 246)',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context: any) {
|
||||
const value = context.parsed.y
|
||||
return `${context.dataset.label}: ${formatBytes(value)}/s`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 3,
|
||||
font: {
|
||||
size: 8
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
beginAtZero: true,
|
||||
min: 0,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.1)'
|
||||
},
|
||||
ticks: {
|
||||
callback: function (value: any) {
|
||||
return formatBytes(value as number)
|
||||
},
|
||||
font: {
|
||||
size: 8
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 10
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 监听props变化
|
||||
watch([() => props.uploadRate, () => props.downloadRate], () => {
|
||||
updateData()
|
||||
}, { immediate: true })
|
||||
|
||||
onMounted(async () => {
|
||||
// add initial point
|
||||
const now = new Date();
|
||||
for (let i = 0; i < maxDataPoints; i++) {
|
||||
let date = new Date(now.getTime() - (maxDataPoints - i) * 2000)
|
||||
const timeStr = date.toLocaleTimeString(navigator.language, {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
uploadHistory.push(0)
|
||||
downloadHistory.push(0)
|
||||
timeLabels.push(timeStr)
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
initChart()
|
||||
updateData()
|
||||
|
||||
// 启动定时器,每2秒更新一次图表
|
||||
updateTimer = window.setInterval(() => {
|
||||
updateData()
|
||||
}, 2000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chart) {
|
||||
chart.destroy()
|
||||
}
|
||||
if (updateTimer) {
|
||||
clearInterval(updateTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
@@ -5,7 +5,8 @@ import { NetworkInstance, type TunnelInfo, type NodeInfo, type PeerRoutePair } f
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { ipv4InetToString, ipv4ToString, ipv6ToString } from '../modules/utils';
|
||||
import { DataTable, Column, Tag, Chip, Button, Dialog, ScrollPanel, Timeline, Divider, Card, } from 'primevue';
|
||||
import { Badge, DataTable, Column, Tag, Chip, Button, Dialog, ScrollPanel, Timeline, Divider, Card, } from 'primevue';
|
||||
import NetworkChart from './NetworkChart.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
curNetworkInst: NetworkInstance | null,
|
||||
@@ -285,6 +286,10 @@ let prevTxSum = 0
|
||||
let prevRxSum = 0
|
||||
const txRate = ref('0')
|
||||
const rxRate = ref('0')
|
||||
|
||||
// 控制节点详细信息chips的显示/隐藏
|
||||
const showNodeDetails = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
rateIntervalId = window.setInterval(() => {
|
||||
const curTxSum = txGlobalSum()
|
||||
@@ -365,36 +370,23 @@ function showEventLogs() {
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="flex w-full flex-col gap-y-5">
|
||||
<div class="m-0 flex flex-row justify-center gap-x-5">
|
||||
<div class="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid green">
|
||||
<div class="font-bold">
|
||||
{{ t('peer_count') }}
|
||||
</div>
|
||||
<div class="text-5xl mt-1">
|
||||
{{ peerCount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid purple">
|
||||
<div class="font-bold">
|
||||
{{ t('upload') }}
|
||||
</div>
|
||||
<div class="text-xl mt-2">
|
||||
{{ txRate }}/s
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid fuchsia">
|
||||
<div class="font-bold">
|
||||
{{ t('download') }}
|
||||
</div>
|
||||
<div class="text-xl mt-2">
|
||||
{{ rxRate }}/s
|
||||
</div>
|
||||
<div class="gap-4">
|
||||
<!-- 网络流量图表 -->
|
||||
<div class="w-full">
|
||||
<NetworkChart :upload-rate="txRate" :download-rate="rxRate" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center flex-wrap w-full max-h-40 overflow-scroll">
|
||||
<!-- 展开/收起节点详细信息的divider按钮 -->
|
||||
<div class="w-full">
|
||||
<Button @click="showNodeDetails = !showNodeDetails"
|
||||
:icon="showNodeDetails ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
|
||||
:label="showNodeDetails ? t('hide_node_details') : t('show_node_details')" severity="secondary" outlined
|
||||
class="w-full justify-center" size="small" />
|
||||
</div>
|
||||
|
||||
<!-- 节点详细信息chips,根据showNodeDetails状态显示/隐藏 -->
|
||||
<div v-show="showNodeDetails" class="flex flex-row items-center flex-wrap w-full max-h-40 overflow-scroll">
|
||||
<Chip v-for="(chip, i) in myNodeInfoChips" :key="i" :label="chip.label" :icon="chip.icon"
|
||||
class="mr-2 mt-2 text-sm" />
|
||||
</div>
|
||||
@@ -411,7 +403,15 @@ function showEventLogs() {
|
||||
|
||||
<Card>
|
||||
<template #title>
|
||||
{{ t('peer_info') }}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ t('peer_info') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Badge :value="peerCount" severity="info"
|
||||
class="text-lg font-semibold px-2 py-1 rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<DataTable :value="peerRouteInfos" column-resize-mode="fit" table-class="w-full">
|
||||
|
@@ -2,7 +2,7 @@ import './style.css'
|
||||
|
||||
import type { App } from 'vue';
|
||||
import { Config, Status, ConfigEditDialog } from "./components";
|
||||
import Aura from '@primevue/themes/aura'
|
||||
import Aura from '@primeuix/themes/aura';
|
||||
import PrimeVue from 'primevue/config'
|
||||
|
||||
import I18nUtils from './modules/i18n'
|
||||
|
@@ -48,6 +48,8 @@ hide_dock_icon: 隐藏 Dock 图标
|
||||
show_dock_icon: 显示 Dock 图标
|
||||
exit: 退出
|
||||
chips_placeholder: 例如: {0}, 输入后在下拉框中选择生效
|
||||
show_node_details: 显示节点详细信息
|
||||
hide_node_details: 隐藏节点详细信息
|
||||
hostname_placeholder: '留空默认为主机名: {0}'
|
||||
dev_name_placeholder: 注意:当多个网络同时使用相同的TUN接口名称时,将会在设置TUN的IP时产生冲突,留空以自动生成随机名称
|
||||
off_text: 点击关闭
|
||||
|
@@ -48,6 +48,8 @@ hide_dock_icon: Hide Dock Icon
|
||||
show_dock_icon: Show Dock Icon
|
||||
exit: Exit
|
||||
use_latency_first: Latency First Mode
|
||||
show_node_details: Show Node Details
|
||||
hide_node_details: Hide Node Details
|
||||
chips_placeholder: 'e.g: {0}, select from the dropdown after input'
|
||||
hostname_placeholder: 'Leave blank and default to host name: {0}'
|
||||
dev_name_placeholder: 'Note: When multiple networks use the same TUN interface name at the same time, there will be a conflict when setting the TUN''s IP. Leave blank to automatically generate a random name.'
|
||||
|
@@ -9,18 +9,18 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primevue/themes": "4.3.3",
|
||||
"aura": "link:@primevue/themes/aura",
|
||||
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
||||
"@primeuix/themes": "^1.2.3",
|
||||
"axios": "^1.7.7",
|
||||
"easytier-frontend-lib": "workspace:*",
|
||||
"primevue": "4.3.3",
|
||||
"primevue": "^4.3.9",
|
||||
"tailwindcss-primeui": "^0.3.4",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "4",
|
||||
"vue-i18n": "^9.9.1",
|
||||
"@modyfi/vite-plugin-yaml": "^1.1.0"
|
||||
"vue-router": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@primevue/auto-import-resolver": "4.3.9",
|
||||
"@types/node": "^22.8.6",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { NetworkTypes } from 'easytier-frontend-lib';
|
||||
import {computed, ref} from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Api } from 'easytier-frontend-lib'
|
||||
import {AutoComplete, Divider, Button, Textarea} from "primevue";
|
||||
import {getInitialApiHost, cleanAndLoadApiHosts, saveApiHost} from "../modules/api-host"
|
||||
import { AutoComplete, Divider, Button, Textarea } from "primevue";
|
||||
import { getInitialApiHost, cleanAndLoadApiHosts, saveApiHost } from "../modules/api-host"
|
||||
|
||||
const api = computed<Api.ApiClient>(() => new Api.ApiClient(apiHost.value));
|
||||
|
||||
@@ -28,18 +28,18 @@ const generateConfig = (config: NetworkTypes.NetworkConfig) => {
|
||||
saveApiHost(apiHost.value)
|
||||
errorMessage.value = "";
|
||||
api.value?.generate_config({
|
||||
config: config
|
||||
}).then((res) => {
|
||||
if (res.error) {
|
||||
errorMessage.value = "Generation failed: " + res.error;
|
||||
} else if (res.toml_config) {
|
||||
toml_config.value = res.toml_config;
|
||||
} else {
|
||||
errorMessage.value = "Api server returned an unexpected response";
|
||||
}
|
||||
}).catch(err => {
|
||||
errorMessage.value = "Generate request failed: " + (err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
config: config
|
||||
}).then((res) => {
|
||||
if (res.error) {
|
||||
errorMessage.value = "Generation failed: " + res.error;
|
||||
} else if (res.toml_config) {
|
||||
toml_config.value = res.toml_config;
|
||||
} else {
|
||||
errorMessage.value = "Api server returned an unexpected response";
|
||||
}
|
||||
}).catch(err => {
|
||||
errorMessage.value = "Generate request failed: " + (err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
};
|
||||
|
||||
const parseConfig = async () => {
|
||||
@@ -48,7 +48,7 @@ const parseConfig = async () => {
|
||||
const res = await api.value?.parse_config({
|
||||
toml_config: toml_config.value
|
||||
});
|
||||
|
||||
|
||||
if (res.error) {
|
||||
errorMessage.value = "Parse failed: " + res.error;
|
||||
} else if (res.config) {
|
||||
@@ -64,31 +64,29 @@ const parseConfig = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-center m-5">
|
||||
<div class="sm:block md:flex w-full">
|
||||
<div class="sm:w-full md:w-1/2 p-4">
|
||||
<div class="flex flex-col">
|
||||
<div class="w-11/12 self-center ">
|
||||
<label>ApiHost</label>
|
||||
<AutoComplete id="api-host" v-model="apiHost" dropdown :suggestions="apiHostSuggestions"
|
||||
@complete="apiHostSearch" class="w-full" />
|
||||
<Divider />
|
||||
</div>
|
||||
</div>
|
||||
<Config :cur-network="newNetworkConfig" @run-network="generateConfig" />
|
||||
</div>
|
||||
<div class="sm:w-full md:w-1/2 p-4 flex flex-col h-[calc(100vh-80px)]">
|
||||
<pre v-if="errorMessage" class="mb-2 p-2 rounded text-sm overflow-auto bg-red-100 text-red-700 max-h-40">{{ errorMessage }}</pre>
|
||||
<Textarea
|
||||
v-model="toml_config"
|
||||
spellcheck="false"
|
||||
class="w-full flex-grow p-2 whitespace-pre-wrap font-mono resize-none"
|
||||
placeholder="Press 'Run Network' to generate TOML configuration, or paste your TOML configuration here to parse it"
|
||||
></Textarea>
|
||||
<div class="mt-3 flex justify-center">
|
||||
<Button label="Parse Config" icon="pi pi-arrow-left" icon-pos="left" @click="parseConfig" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-center m-5">
|
||||
<div class="sm:block md:flex w-full">
|
||||
<div class="sm:w-full md:w-1/2 p-4">
|
||||
<div class="flex flex-col">
|
||||
<div class="w-full self-center ">
|
||||
<label>ApiHost</label>
|
||||
<AutoComplete id="api-host" v-model="apiHost" dropdown :suggestions="apiHostSuggestions"
|
||||
@complete="apiHostSearch" class="w-full" />
|
||||
<Divider />
|
||||
</div>
|
||||
</div>
|
||||
<Config :cur-network="newNetworkConfig" @run-network="generateConfig" />
|
||||
</div>
|
||||
<div class="sm:w-full md:w-1/2 p-4 flex flex-col h-[calc(100vh-80px)]">
|
||||
<pre v-if="errorMessage"
|
||||
class="mb-2 p-2 rounded text-sm overflow-auto bg-red-100 text-red-700 max-h-40">{{ errorMessage }}</pre>
|
||||
<Textarea v-model="toml_config" spellcheck="false"
|
||||
class="w-full flex-grow p-2 whitespace-pre-wrap font-mono resize-none"
|
||||
placeholder="Press 'Run Network' to generate TOML configuration, or paste your TOML configuration here to parse it"></Textarea>
|
||||
<div class="mt-3 flex justify-center">
|
||||
<Button label="Parse Config" icon="pi pi-arrow-left" icon-pos="left" @click="parseConfig" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -4,7 +4,7 @@ import './style.css'
|
||||
import App from './App.vue'
|
||||
import EasytierFrontendLib from 'easytier-frontend-lib'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Aura from '@primevue/themes/aura'
|
||||
import Aura from '@primeuix/themes/aura';
|
||||
import ConfirmationService from 'primevue/confirmationservice';
|
||||
import { I18nUtils } from 'easytier-frontend-lib'
|
||||
|
||||
|
@@ -3,7 +3,7 @@ name = "easytier"
|
||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
version = "2.4.4"
|
||||
version = "2.4.5"
|
||||
edition = "2021"
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
@@ -237,6 +237,7 @@ windows = { version = "0.52.0", features = [
|
||||
"Win32_System_Com",
|
||||
"Win32_Networking",
|
||||
"Win32_System_Ole",
|
||||
"Win32_System_Variant",
|
||||
"Win32_Networking_WinSock",
|
||||
"Win32_System_IO",
|
||||
] }
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::{io, net::SocketAddr, os::windows::io::AsRawSocket};
|
||||
use std::{io, mem::ManuallyDrop, net::SocketAddr, os::windows::io::AsRawSocket};
|
||||
|
||||
use anyhow::Context;
|
||||
use network_interface::NetworkInterfaceConfig;
|
||||
@@ -18,6 +18,8 @@ use windows::{
|
||||
System::Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_MULTITHREADED,
|
||||
},
|
||||
System::Ole::{SafeArrayCreateVector, SafeArrayPutElement},
|
||||
System::Variant::{VARENUM, VARIANT, VT_ARRAY, VT_BSTR, VT_VARIANT},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -247,20 +249,20 @@ pub fn add_interface_to_firewall_allowlist(interface_name: &str) -> anyhow::Resu
|
||||
);
|
||||
|
||||
// Create rules for each protocol type
|
||||
add_protocol_firewall_rules(&policy, interface_name, "TCP", 6)?; // TCP protocol number 6
|
||||
add_protocol_firewall_rules(&policy, interface_name, "TCP", Some(6))?; // TCP protocol number 6
|
||||
tracing::debug!("Added TCP firewall rules for interface: {}", interface_name);
|
||||
|
||||
add_protocol_firewall_rules(&policy, interface_name, "UDP", 17)?; // UDP protocol number 17
|
||||
add_protocol_firewall_rules(&policy, interface_name, "UDP", Some(17))?; // UDP protocol number 17
|
||||
tracing::debug!("Added UDP firewall rules for interface: {}", interface_name);
|
||||
|
||||
add_protocol_firewall_rules(&policy, interface_name, "ICMP", 1)?; // ICMP protocol number 1
|
||||
add_protocol_firewall_rules(&policy, interface_name, "ICMP", Some(1))?; // ICMP protocol number 1
|
||||
tracing::debug!(
|
||||
"Added ICMP firewall rules for interface: {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
// Add fallback rules for all protocols
|
||||
add_all_protocols_firewall_rules(&policy, interface_name)?;
|
||||
add_protocol_firewall_rules(&policy, interface_name, "ALL", None)?;
|
||||
tracing::debug!(
|
||||
"Added fallback all-protocols rules for interface: {}",
|
||||
interface_name
|
||||
@@ -279,7 +281,7 @@ fn add_protocol_firewall_rules(
|
||||
policy: &INetFwPolicy2,
|
||||
interface_name: &str,
|
||||
protocol_name: &str,
|
||||
protocol_number: i32,
|
||||
protocol_number: Option<i32>,
|
||||
) -> anyhow::Result<()> {
|
||||
// Create rules for both inbound and outbound traffic
|
||||
for (is_inbound, direction_name) in [(true, "Inbound"), (false, "Outbound")] {
|
||||
@@ -307,7 +309,9 @@ fn add_protocol_firewall_rules(
|
||||
unsafe {
|
||||
rule.SetName(&name_bstr)?;
|
||||
rule.SetDescription(&desc_bstr)?;
|
||||
rule.SetProtocol(protocol_number)?;
|
||||
if let Some(protocol_number) = protocol_number {
|
||||
rule.SetProtocol(protocol_number)?;
|
||||
}
|
||||
rule.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||
|
||||
if is_inbound {
|
||||
@@ -322,61 +326,35 @@ fn add_protocol_firewall_rules(
|
||||
)?;
|
||||
rule.SetGrouping(&BSTR::from("EasyTier"))?;
|
||||
|
||||
// Get rule collection and add new rule
|
||||
let rules = policy.Rules()?;
|
||||
rules.Remove(&name_bstr)?; // Remove existing rule with same name first
|
||||
rules.Add(&rule)?;
|
||||
}
|
||||
}
|
||||
// Set the interface for this rule to apply to the specific network interface
|
||||
// According to Microsoft docs, interfaces should be represented by their friendly name
|
||||
// We need to create a SAFEARRAY of VARIANT strings containing the interface name
|
||||
let interface_bstr = BSTR::from(interface_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add fallback rules for all protocols
|
||||
fn add_all_protocols_firewall_rules(
|
||||
policy: &INetFwPolicy2,
|
||||
interface_name: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// Create rules for both inbound and outbound traffic
|
||||
for (is_inbound, direction_name) in [(true, "Inbound"), (false, "Outbound")] {
|
||||
// Create firewall rule instance
|
||||
let rule: INetFwRule = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwRule,
|
||||
None,
|
||||
CLSCTX_ALL,
|
||||
)
|
||||
}?;
|
||||
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - All Protocols ({})",
|
||||
interface_name, direction_name
|
||||
);
|
||||
let description = format!(
|
||||
"Allow all protocol traffic on EasyTier interface {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
let name_bstr = BSTR::from(&rule_name);
|
||||
let desc_bstr = BSTR::from(&description);
|
||||
|
||||
unsafe {
|
||||
rule.SetName(&name_bstr)?;
|
||||
rule.SetDescription(&desc_bstr)?;
|
||||
// Don't set protocol - allows all protocols by default
|
||||
rule.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||
|
||||
if is_inbound {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_IN)?;
|
||||
} else {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_OUT)?;
|
||||
// Create a SAFEARRAY containing one interface name
|
||||
let interface_array = SafeArrayCreateVector(VT_VARIANT, 0, 1);
|
||||
if interface_array.is_null() {
|
||||
return Err(anyhow::anyhow!("Failed to create SAFEARRAY"));
|
||||
}
|
||||
|
||||
rule.SetEnabled(windows::Win32::Foundation::VARIANT_TRUE)?;
|
||||
rule.SetProfiles(
|
||||
NET_FW_PROFILE2_PRIVATE.0 | NET_FW_PROFILE2_PUBLIC.0 | NET_FW_PROFILE2_DOMAIN.0,
|
||||
let index = 0i32;
|
||||
let mut variant_interface = VARIANT::default();
|
||||
(*variant_interface.Anonymous.Anonymous).vt = VT_BSTR;
|
||||
(*variant_interface.Anonymous.Anonymous).Anonymous.bstrVal =
|
||||
ManuallyDrop::new(interface_bstr);
|
||||
|
||||
SafeArrayPutElement(
|
||||
interface_array,
|
||||
&index as *const _ as *const i32,
|
||||
&variant_interface as *const _ as *const std::ffi::c_void,
|
||||
)?;
|
||||
rule.SetGrouping(&BSTR::from("EasyTier"))?;
|
||||
|
||||
// Create the VARIANT that contains the SAFEARRAY
|
||||
let mut interface_variant = VARIANT::default();
|
||||
(*interface_variant.Anonymous.Anonymous).vt = VARENUM(VT_ARRAY.0 | VT_VARIANT.0);
|
||||
(*interface_variant.Anonymous.Anonymous).Anonymous.parray = interface_array;
|
||||
|
||||
rule.SetInterfaces(interface_variant)?;
|
||||
|
||||
// Get rule collection and add new rule
|
||||
let rules = policy.Rules()?;
|
||||
@@ -402,8 +380,7 @@ pub fn remove_interface_firewall_rules(interface_name: &str) -> anyhow::Result<(
|
||||
|
||||
let rules = unsafe { policy.Rules()? };
|
||||
|
||||
// Remove protocol-specific rules
|
||||
for protocol_name in ["TCP", "UDP", "ICMP"] {
|
||||
for protocol_name in ["TCP", "UDP", "ICMP", "ALL"] {
|
||||
for direction in ["Inbound", "Outbound"] {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - {} Protocol ({})",
|
||||
@@ -416,18 +393,6 @@ pub fn remove_interface_firewall_rules(interface_name: &str) -> anyhow::Result<(
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fallback protocol rules
|
||||
for direction in ["Inbound", "Outbound"] {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - All Protocols ({})",
|
||||
interface_name, direction
|
||||
);
|
||||
let name_bstr = BSTR::from(&rule_name);
|
||||
unsafe {
|
||||
let _ = rules.Remove(&name_bstr); // Ignore errors, rule might not exist
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@@ -968,8 +968,17 @@ impl NetworkOptions {
|
||||
old_udp_whitelist.extend(self.udp_whitelist.clone());
|
||||
cfg.set_udp_whitelist(old_udp_whitelist);
|
||||
|
||||
cfg.set_stun_servers(self.stun_servers.clone());
|
||||
cfg.set_stun_servers_v6(self.stun_servers_v6.clone());
|
||||
if let Some(stun_servers) = &self.stun_servers {
|
||||
let mut old_stun_servers = cfg.get_stun_servers().unwrap_or_default();
|
||||
old_stun_servers.extend(stun_servers.iter().cloned());
|
||||
cfg.set_stun_servers(Some(old_stun_servers));
|
||||
}
|
||||
|
||||
if let Some(stun_servers_v6) = &self.stun_servers_v6 {
|
||||
let mut old_stun_servers_v6 = cfg.get_stun_servers_v6().unwrap_or_default();
|
||||
old_stun_servers_v6.extend(stun_servers_v6.iter().cloned());
|
||||
cfg.set_stun_servers_v6(Some(old_stun_servers_v6));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@@ -450,7 +450,9 @@ impl PeerPacketFilter for Socks5Server {
|
||||
|
||||
let entry_key = match ipv4.get_next_level_protocol() {
|
||||
IpNextHeaderProtocols::Tcp => {
|
||||
let tcp_packet = TcpPacket::new(ipv4.payload()).unwrap();
|
||||
let Some(tcp_packet) = TcpPacket::new(ipv4.payload()) else {
|
||||
return Some(packet);
|
||||
};
|
||||
Socks5Entry {
|
||||
dst: SocketAddr::new(ipv4.get_source().into(), tcp_packet.get_source()),
|
||||
src: SocketAddr::new(
|
||||
@@ -479,7 +481,9 @@ impl PeerPacketFilter for Socks5Server {
|
||||
return Some(packet);
|
||||
}
|
||||
|
||||
let udp_packet = UdpPacket::new(ipv4.payload()).unwrap();
|
||||
let Some(udp_packet) = UdpPacket::new(ipv4.payload()) else {
|
||||
return Some(packet);
|
||||
};
|
||||
Socks5Entry {
|
||||
dst: SocketAddr::new(ipv4.get_source().into(), udp_packet.get_source()),
|
||||
src: SocketAddr::new(
|
||||
|
@@ -25,7 +25,7 @@ pub fn del_netns(name: &str) {
|
||||
.output();
|
||||
}
|
||||
|
||||
pub fn create_netns(name: &str, ipv4: &str) {
|
||||
pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) {
|
||||
// create netns
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["netns", "add", name])
|
||||
@@ -76,20 +76,22 @@ pub fn create_netns(name: &str, ipv4: &str) {
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args([
|
||||
"netns",
|
||||
"exec",
|
||||
name,
|
||||
"ip",
|
||||
"addr",
|
||||
"add",
|
||||
ipv4,
|
||||
"dev",
|
||||
get_guest_veth_name(name),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
for ip in [ipv4, ipv6] {
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args([
|
||||
"netns",
|
||||
"exec",
|
||||
name,
|
||||
"ip",
|
||||
"addr",
|
||||
"add",
|
||||
ip,
|
||||
"dev",
|
||||
get_guest_veth_name(name),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_bridge(name: &str) {
|
||||
|
@@ -40,10 +40,10 @@ pub fn prepare_linux_namespaces() {
|
||||
del_netns("net_c");
|
||||
del_netns("net_d");
|
||||
|
||||
create_netns("net_a", "10.1.1.1/24");
|
||||
create_netns("net_b", "10.1.1.2/24");
|
||||
create_netns("net_c", "10.1.2.3/24");
|
||||
create_netns("net_d", "10.1.2.4/24");
|
||||
create_netns("net_a", "10.1.1.1/24", "fd11::1/64");
|
||||
create_netns("net_b", "10.1.1.2/24", "fd11::2/64");
|
||||
create_netns("net_c", "10.1.2.3/24", "fd12::3/64");
|
||||
create_netns("net_d", "10.1.2.4/24", "fd12::4/64");
|
||||
|
||||
prepare_bridge("br_a");
|
||||
prepare_bridge("br_b");
|
||||
@@ -931,10 +931,18 @@ fn run_wireguard_client(
|
||||
}
|
||||
|
||||
#[cfg(feature = "wireguard")]
|
||||
#[rstest::rstest]
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
pub async fn wireguard_vpn_portal() {
|
||||
pub async fn wireguard_vpn_portal(#[values(true, false)] test_v6: bool) {
|
||||
let mut insts = init_three_node("tcp").await;
|
||||
|
||||
if test_v6 {
|
||||
ping6_test("net_d", "fd12::3", None).await;
|
||||
} else {
|
||||
ping_test("net_d", "10.1.2.3", None).await;
|
||||
}
|
||||
|
||||
let net_ns = NetNS::new(Some("net_d".into()));
|
||||
let _g = net_ns.guard();
|
||||
insts[2]
|
||||
@@ -946,11 +954,17 @@ pub async fn wireguard_vpn_portal() {
|
||||
});
|
||||
insts[2].run_vpn_portal().await.unwrap();
|
||||
|
||||
let dst_socket_addr = if test_v6 {
|
||||
"[fd12::3]:22121".parse().unwrap()
|
||||
} else {
|
||||
"10.1.2.3:22121".parse().unwrap()
|
||||
};
|
||||
|
||||
let net_ns = NetNS::new(Some("net_d".into()));
|
||||
let _g = net_ns.guard();
|
||||
let wg_cfg = get_wg_config_for_portal(&insts[2].get_global_ctx().get_network_identity());
|
||||
run_wireguard_client(
|
||||
"10.1.2.3:22121".parse().unwrap(),
|
||||
dst_socket_addr,
|
||||
Key::try_from(wg_cfg.my_public_key()).unwrap(),
|
||||
Key::try_from(wg_cfg.peer_secret_key()).unwrap(),
|
||||
vec!["10.14.14.0/24".to_string(), "10.144.144.0/24".to_string()],
|
||||
|
@@ -384,7 +384,11 @@ pub(crate) fn setup_sokcet2_ext(
|
||||
unsafe {
|
||||
let dev_idx = nix::libc::if_nametoindex(dev_name.as_str().as_ptr() as *const i8);
|
||||
tracing::warn!(?dev_idx, ?dev_name, "bind device");
|
||||
socket2_socket.bind_device_by_index_v4(std::num::NonZeroU32::new(dev_idx))?;
|
||||
if bind_addr.is_ipv4() {
|
||||
socket2_socket.bind_device_by_index_v4(std::num::NonZeroU32::new(dev_idx))?;
|
||||
} else {
|
||||
socket2_socket.bind_device_by_index_v6(std::num::NonZeroU32::new(dev_idx))?;
|
||||
}
|
||||
tracing::warn!(?dev_idx, ?dev_name, "bind device doen");
|
||||
}
|
||||
}
|
||||
|
@@ -81,9 +81,14 @@ pub fn init_logger(
|
||||
});
|
||||
}
|
||||
|
||||
let dir = file_config.dir.as_deref().unwrap_or(".");
|
||||
let file = file_config.file.as_deref().unwrap_or("easytier.log");
|
||||
let path = std::path::Path::new(dir).join(file);
|
||||
let path_str = path.to_string_lossy().into_owned();
|
||||
|
||||
let builder = RollingFileAppenderBase::builder();
|
||||
let file_appender = builder
|
||||
.filename(file_config.file.unwrap_or("easytier.log".to_string()))
|
||||
.filename(path_str)
|
||||
.condition_daily()
|
||||
.max_filecount(file_config.count.unwrap_or(10))
|
||||
.condition_max_file_size(file_config.size_mb.unwrap_or(100) * 1024 * 1024)
|
||||
|
6097
pnpm-lock.yaml
generated
6097
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user