bugfix before release 11x (#130)

* use correct i18n hook

* fix peer rpc panic

make sure server use correct transact id

* fix dhcp

recreate tun device after ip changed

* use upx correctly

* compile arm & armv7

* prepare to release v1.1.0
This commit is contained in:
Sijie.Sun 2024-06-03 23:07:44 +08:00 committed by GitHub
parent c1b725e64e
commit df17a7bb68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 470 additions and 336 deletions

View File

@ -14,18 +14,64 @@ rustflags = ["-C", "target-feature=+crt-static"]
[target.mipsel-unknown-linux-musl] [target.mipsel-unknown-linux-musl]
linker = "mipsel-linux-muslsf-gcc" linker = "mipsel-linux-muslsf-gcc"
rustflags = ["-C", "target-feature=+crt-static", rustflags = [
"-L", "./musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib", "-C",
"-L", "./musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1", "target-feature=+crt-static",
"-l", "atomic", "-L",
"-l", "ctz" "./musl_gcc/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/lib",
] "-L",
"./musl_gcc/mipsel-linux-muslsf-cross/lib/gcc/mipsel-linux-muslsf/11.2.1",
"-l",
"atomic",
"-l",
"ctz",
]
[target.mips-unknown-linux-musl] [target.mips-unknown-linux-musl]
linker = "mips-linux-muslsf-gcc" linker = "mips-linux-muslsf-gcc"
rustflags = ["-C", "target-feature=+crt-static", rustflags = [
"-L", "./musl_gcc/mips-linux-muslsf-cross/mips-linux-muslsf/lib", "-C",
"-L", "./musl_gcc/mips-linux-muslsf-cross/lib/gcc/mips-linux-muslsf/11.2.1", "target-feature=+crt-static",
"-l", "atomic", "-L",
"-l", "ctz" "./musl_gcc/mips-linux-muslsf-cross/mips-linux-muslsf/lib",
] "-L",
"./musl_gcc/mips-linux-muslsf-cross/lib/gcc/mips-linux-muslsf/11.2.1",
"-l",
"atomic",
"-l",
"ctz",
]
[target.armv7-unknown-linux-musleabihf]
linker = "armv7l-linux-musleabihf-gcc"
rustflags = ["-C", "target-feature=+crt-static"]
[target.armv7-unknown-linux-musleabi]
linker = "armv7m-linux-musleabi-gcc"
rustflags = ["-C", "target-feature=+crt-static"]
[target.arm-unknown-linux-musleabihf]
linker = "arm-linux-musleabihf-gcc"
rustflags = [
"-C",
"target-feature=+crt-static",
"-L",
"./musl_gcc/arm-linux-musleabihf-cross/arm-linux-musleabihf/lib",
"-L",
"./musl_gcc/arm-linux-musleabihf-cross/lib/gcc/arm-linux-musleabihf/11.2.1",
"-l",
"atomic",
]
[target.arm-unknown-linux-musleabi]
linker = "arm-linux-musleabi-gcc"
rustflags = [
"-C",
"target-feature=+crt-static",
"-L",
"./musl_gcc/arm-linux-musleabi-cross/arm-linux-musleabi/lib",
"-L",
"./musl_gcc/arm-linux-musleabi-cross/lib/gcc/arm-linux-musleabi/11.2.1",
"-l",
"atomic",
]

View File

@ -50,6 +50,15 @@ jobs:
- TARGET: x86_64-pc-windows-msvc - TARGET: x86_64-pc-windows-msvc
OS: windows-latest OS: windows-latest
- TARGET: armv7-unknown-linux-musleabihf # raspberry pi 2-3-4, not tested
OS: ubuntu-latest
- TARGET: armv7-unknown-linux-musleabi # raspberry pi 2-3-4, not tested
OS: ubuntu-latest
- TARGET: arm-unknown-linux-musleabihf # raspberry pi 0-1, not tested
OS: ubuntu-latest
- TARGET: arm-unknown-linux-musleabi # raspberry pi 0-1, not tested
OS: ubuntu-latest
runs-on: ${{ matrix.OS }} runs-on: ${{ matrix.OS }}
env: env:
NAME: easytier NAME: easytier
@ -90,15 +99,12 @@ jobs:
cargo build --release --verbose --target $TARGET cargo build --release --verbose --target $TARGET
fi fi
- name: Run UPX - name: Install UPX
if: ${{ matrix.OS != 'macos-latest' }} if: ${{ matrix.OS != 'macos-latest' }}
uses: crazy-max/ghaction-upx@v3 uses: crazy-max/ghaction-upx@v3
with: with:
version: latest version: latest
files: | install-only: true
./target/$TARGET/release/easytier-core*
./target/$TARGET/release/easytier-cli*
args: -9
- name: Compress - name: Compress
run: | run: |
@ -114,6 +120,12 @@ jobs:
else else
TAG=$GITHUB_SHA TAG=$GITHUB_SHA
fi fi
if [[ $OS =~ ^windows.*$ ]]; then
upx --lzma --best ./target/$TARGET/release/easytier-core"$SUFFIX"
upx --lzma --best ./target/$TARGET/release/easytier-cli"$SUFFIX"
fi
mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/ mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/
mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/ mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/

View File

@ -27,17 +27,23 @@ if [[ $OS =~ ^ubuntu.*$ ]]; then
mipsel-unknown-linux-musl) mipsel-unknown-linux-musl)
MUSL_URI=mipsel-linux-muslsf MUSL_URI=mipsel-linux-muslsf
;; ;;
mips-unknown-linux-musl)
MUSL_URI=mips-linux-muslsf
;;
aarch64-unknown-linux-musl) aarch64-unknown-linux-musl)
MUSL_URI=aarch64-linux-musl MUSL_URI=aarch64-linux-musl
;; ;;
armv7-unknown-linux-musleabihf) armv7-unknown-linux-musleabihf)
MUSL_URI=armv7l-linux-musleabihf MUSL_URI=armv7l-linux-musleabihf
;; ;;
armv7-unknown-linux-musleabi)
MUSL_URI=armv7m-linux-musleabi
;;
arm-unknown-linux-musleabihf) arm-unknown-linux-musleabihf)
MUSL_URI=arm-linux-musleabihf MUSL_URI=arm-linux-musleabihf
;; ;;
mips-unknown-linux-musl) arm-unknown-linux-musleabi)
MUSL_URI=mips-linux-muslsf MUSL_URI=arm-linux-musleabi
;; ;;
esac esac

16
Cargo.lock generated
View File

@ -385,9 +385,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "boringtun" name = "boringtun-easytier"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/EasyTier/boringtun.git?rev=449204c#449204c3eca736dc23b075d81426527a357e2f2a" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a62bfb866a2a03e8aea22e83a0c1e385304563ee77c89ebd2043c67d0a73065"
dependencies = [ dependencies = [
"aead", "aead",
"atomic-shim", "atomic-shim",
@ -1177,7 +1178,7 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]] [[package]]
name = "easytier" name = "easytier"
version = "1.0.1-pre" version = "1.1.0"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
@ -1189,7 +1190,7 @@ dependencies = [
"auto_impl", "auto_impl",
"base64 0.21.7", "base64 0.21.7",
"bitflags 2.5.0", "bitflags 2.5.0",
"boringtun", "boringtun-easytier",
"bytecodec", "bytecodec",
"byteorder", "byteorder",
"bytes", "bytes",
@ -1246,7 +1247,7 @@ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
"tracing-subscriber", "tracing-subscriber",
"tun", "tun-easytier",
"url", "url",
"uuid", "uuid",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -5489,9 +5490,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "tun" name = "tun-easytier"
version = "0.6.1" version = "0.6.1"
source = "git+https://github.com/EasyTier/rust-tun.git?rev=e4fd1cd#e4fd1cdd5321ac1ad6f19954e982194688b95aff" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6d01bd11265e1cb5ca22e9103daf57194afa43b1dc4c8cd49b950c969ffbe7c"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",

View File

@ -8,6 +8,8 @@
[简体中文](/README_CN.md) | [English](/README.md) [简体中文](/README_CN.md) | [English](/README.md)
**Please visit the [EasyTier Official Website](https://www.easytier.top/en/) to view the full documentation.**
EasyTier is a simple, safe and decentralized VPN networking solution implemented with the Rust language and Tokio framework. EasyTier is a simple, safe and decentralized VPN networking solution implemented with the Rust language and Tokio framework.
<p align="center"> <p align="center">
@ -28,7 +30,7 @@
- **TCP Support**: Provides reliable data transmission through concurrent TCP links when UDP is limited, optimizing performance. - **TCP Support**: Provides reliable data transmission through concurrent TCP links when UDP is limited, optimizing performance.
- **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected. - **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected.
- **IPv6 Support**: Supports networking using IPv6. - **IPv6 Support**: Supports networking using IPv6.
- **Multiple Protocol Types**: Supports communication between nodes using protocols such as WebSocket and QUIC.
## Installation ## Installation

View File

@ -8,6 +8,8 @@
[简体中文](/README_CN.md) | [English](/README.md) [简体中文](/README_CN.md) | [English](/README.md)
**请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。**
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。 一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
<p align="center"> <p align="center">
@ -28,6 +30,7 @@
- **TCP 支持**:在 UDP 受限的情况下,通过并发 TCP 链接提供可靠的数据传输,优化性能。 - **TCP 支持**:在 UDP 受限的情况下,通过并发 TCP 链接提供可靠的数据传输,优化性能。
- **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。 - **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。
- **IPV6 支持**:支持利用 IPV6 组网。 - **IPV6 支持**:支持利用 IPV6 组网。
- **多协议类型**: 支持使用 WebSocket、QUIC 等协议进行节点间通信。
## 安装 ## 安装

View File

@ -3,6 +3,7 @@ import InputGroup from 'primevue/inputgroup'
import InputGroupAddon from 'primevue/inputgroupaddon' import InputGroupAddon from 'primevue/inputgroupaddon'
import { getOsHostname } from '~/composables/network' import { getOsHostname } from '~/composables/network'
import { NetworkingMethod } from '~/types/network' import { NetworkingMethod } from '~/types/network'
const { t } = useI18n()
const props = defineProps<{ const props = defineProps<{
configInvalid?: boolean configInvalid?: boolean
@ -11,8 +12,6 @@ const props = defineProps<{
defineEmits(['runNetwork']) defineEmits(['runNetwork'])
const { t } = useI18n()
const networking_methods = ref([ const networking_methods = ref([
{ value: NetworkingMethod.PublicServer, label: t('public_server') }, { value: NetworkingMethod.PublicServer, label: t('public_server') },
{ value: NetworkingMethod.Manual, label: t('manual') }, { value: NetworkingMethod.Manual, label: t('manual') },
@ -59,16 +58,16 @@ onMounted(async () => {
<div class="flex flex-column"> <div class="flex flex-column">
<div class="w-7/12 self-center "> <div class="w-7/12 self-center ">
<Message severity="warn"> <Message severity="warn">
{{ $t('dhcp_experimental_warning') }} {{ t('dhcp_experimental_warning') }}
</Message> </Message>
</div> </div>
<div class="w-7/12 self-center "> <div class="w-7/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">
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<div class="flex align-items-center" for="virtual_ip"> <div class="flex align-items-center" for="virtual_ip">
<label class="mr-2"> {{ $t('virtual_ipv4') }} </label> <label class="mr-2"> {{ t('virtual_ipv4') }} </label>
<Checkbox v-model="curNetwork.dhcp" input-id="virtual_ip_auto" :binary="true" /> <Checkbox v-model="curNetwork.dhcp" input-id="virtual_ip_auto" :binary="true" />
<label for="virtual_ip_auto" class="ml-2"> <label for="virtual_ip_auto" class="ml-2">
@ -89,11 +88,11 @@ onMounted(async () => {
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="network_name">{{ $t('network_name') }}</label> <label for="network_name">{{ t('network_name') }}</label>
<InputText id="network_name" v-model="curNetwork.network_name" aria-describedby="network_name-help" /> <InputText id="network_name" v-model="curNetwork.network_name" aria-describedby="network_name-help" />
</div> </div>
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="network_secret">{{ $t('network_secret') }}</label> <label for="network_secret">{{ t('network_secret') }}</label>
<InputText <InputText
id="network_secret" v-model="curNetwork.network_secret" id="network_secret" v-model="curNetwork.network_secret"
aria-describedby=" network_secret-help" aria-describedby=" network_secret-help"
@ -103,7 +102,7 @@ onMounted(async () => {
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="nm">{{ $t('networking_method') }}</label> <label for="nm">{{ t('networking_method') }}</label>
<div class="items-center flex flex-row p-fluid gap-x-1"> <div class="items-center flex flex-row p-fluid gap-x-1">
<Dropdown <Dropdown
v-model="curNetwork.networking_method" :options="networking_methods" option-label="label" v-model="curNetwork.networking_method" :options="networking_methods" option-label="label"
@ -111,7 +110,7 @@ onMounted(async () => {
/> />
<Chips <Chips
v-if="curNetwork.networking_method === NetworkingMethod.Manual" id="chips" v-if="curNetwork.networking_method === NetworkingMethod.Manual" id="chips"
v-model="curNetwork.peer_urls" :placeholder="$t('chips_placeholder', ['tcp://8.8.8.8:11010'])" v-model="curNetwork.peer_urls" :placeholder="t('chips_placeholder', ['tcp://8.8.8.8:11010'])"
separator=" " class="grow" separator=" " class="grow"
/> />
@ -128,24 +127,24 @@ onMounted(async () => {
<Divider /> <Divider />
<Panel :header="$t('advanced_settings')" toggleable collapsed> <Panel :header="t('advanced_settings')" toggleable collapsed>
<div class="flex flex-column gap-y-2"> <div class="flex flex-column gap-y-2">
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="hostname">{{ $t('hostname') }}</label> <label for="hostname">{{ t('hostname') }}</label>
<InputText <InputText
id="hostname" v-model="curNetwork.hostname" aria-describedby="hostname-help" :format="true" id="hostname" v-model="curNetwork.hostname" aria-describedby="hostname-help" :format="true"
:placeholder="$t('hostname_placeholder', [osHostname])" @blur="validateHostname" :placeholder="t('hostname_placeholder', [osHostname])" @blur="validateHostname"
/> />
</div> </div>
</div> </div>
<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 <Chips
id="chips" v-model="curNetwork.proxy_cidrs" id="chips" v-model="curNetwork.proxy_cidrs"
:placeholder="$t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full" :placeholder="t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full"
/> />
</div> </div>
</div> </div>
@ -156,13 +155,13 @@ onMounted(async () => {
<div class="items-center flex flex-row gap-x-4"> <div class="items-center flex flex-row gap-x-4">
<ToggleButton <ToggleButton
v-model="curNetwork.enable_vpn_portal" on-icon="pi pi-check" off-icon="pi pi-times" v-model="curNetwork.enable_vpn_portal" on-icon="pi pi-check" off-icon="pi pi-times"
:on-label="$t('off_text')" :off-label="$t('on_text')" :on-label="t('off_text')" :off-label="t('on_text')"
/> />
<div v-if="curNetwork.enable_vpn_portal" class="grow"> <div v-if="curNetwork.enable_vpn_portal" class="grow">
<InputGroup> <InputGroup>
<InputText <InputText
v-model="curNetwork.vpn_portal_client_network_addr" v-model="curNetwork.vpn_portal_client_network_addr"
:placeholder="$t('vpn_portal_client_network')" :placeholder="t('vpn_portal_client_network')"
/> />
<InputGroupAddon> <InputGroupAddon>
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span> <span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
@ -171,7 +170,7 @@ onMounted(async () => {
</div> </div>
<InputNumber <InputNumber
v-if="curNetwork.enable_vpn_portal" v-model="curNetwork.vpn_portal_listen_port" v-if="curNetwork.enable_vpn_portal" v-model="curNetwork.vpn_portal_listen_port"
:placeholder="$t('vpn_portal_listen_port')" class="" :format="false" :min="0" :max="65535" :placeholder="t('vpn_portal_listen_port')" class="" :format="false" :min="0" :max="65535"
/> />
</div> </div>
</div> </div>
@ -179,17 +178,17 @@ onMounted(async () => {
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 grow p-fluid"> <div class="flex flex-column gap-2 grow p-fluid">
<label for="listener_urls">{{ $t('listener_urls') }}</label> <label for="listener_urls">{{ t('listener_urls') }}</label>
<Chips <Chips
id="listener_urls" v-model="curNetwork.listener_urls" id="listener_urls" v-model="curNetwork.listener_urls"
:placeholder="$t('chips_placeholder', ['tcp://1.1.1.1:11010'])" separator=" " class="w-full" :placeholder="t('chips_placeholder', ['tcp://1.1.1.1:11010'])" separator=" " class="w-full"
/> />
</div> </div>
</div> </div>
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="rpc_port">{{ $t('rpc_port') }}</label> <label for="rpc_port">{{ t('rpc_port') }}</label>
<InputNumber <InputNumber
id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="username-help" id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="username-help"
:format="false" :min="0" :max="65535" :format="false" :min="0" :max="65535"
@ -201,7 +200,7 @@ onMounted(async () => {
<div class="flex pt-4 justify-content-center"> <div class="flex pt-4 justify-content-center">
<Button <Button
:label="$t('run_network')" icon="pi pi-arrow-right" icon-pos="right" :disabled="configInvalid" :label="t('run_network')" icon="pi pi-arrow-right" icon-pos="right" :disabled="configInvalid"
@click="$emit('runNetwork', curNetwork)" @click="$emit('runNetwork', curNetwork)"
/> />
</div> </div>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NodeInfo } from '~/types/network' import type { NodeInfo } from '~/types/network'
const { t } = useI18n()
const props = defineProps<{ const props = defineProps<{
instanceId?: string instanceId?: string
@ -272,7 +273,7 @@ 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)" :style="{ width: '70%' }">
<Panel> <Panel>
<ScrollPanel style="width: 100%; height: 400px"> <ScrollPanel style="width: 100%; height: 400px">
<pre>{{ dialogContent }}</pre> <pre>{{ dialogContent }}</pre>
@ -280,7 +281,7 @@ function showEventLogs() {
</Panel> </Panel>
<Divider /> <Divider />
<div class="flex justify-content-end gap-2"> <div class="flex justify-content-end gap-2">
<Button type="button" :label="$t('close')" @click="dialogVisible = false" /> <Button type="button" :label="t('close')" @click="dialogVisible = false" />
</div> </div>
</Dialog> </Dialog>
@ -300,7 +301,7 @@ function showEventLogs() {
<template v-else> <template v-else>
<Card> <Card>
<template #title> <template #title>
{{ $t('my_node_info') }} {{ t('my_node_info') }}
</template> </template>
<template #content> <template #content>
<div class="flex w-full flex-column gap-y-5"> <div class="flex w-full flex-column gap-y-5">
@ -310,7 +311,7 @@ function showEventLogs() {
style="border: 1px solid green" style="border: 1px solid green"
> >
<div class="font-bold"> <div class="font-bold">
{{ $t('peer_count') }} {{ t('peer_count') }}
</div> </div>
<div class="text-5xl mt-1"> <div class="text-5xl mt-1">
{{ peerCount }} {{ peerCount }}
@ -322,7 +323,7 @@ function showEventLogs() {
style="border: 1px solid purple" style="border: 1px solid purple"
> >
<div class="font-bold"> <div class="font-bold">
{{ $t('upload') }} {{ t('upload') }}
</div> </div>
<div class="text-xl mt-2"> <div class="text-xl mt-2">
{{ txRate }}/s {{ txRate }}/s
@ -334,7 +335,7 @@ function showEventLogs() {
style="border: 1px solid fuchsia" style="border: 1px solid fuchsia"
> >
<div class="font-bold"> <div class="font-bold">
{{ $t('download') }} {{ t('download') }}
</div> </div>
<div class="text-xl mt-2"> <div class="text-xl mt-2">
{{ rxRate }}/s {{ rxRate }}/s
@ -350,8 +351,8 @@ function showEventLogs() {
</div> </div>
<div v-if="myNodeInfo" class="m-0 flex flex-row justify-center gap-x-5 text-sm"> <div v-if="myNodeInfo" class="m-0 flex flex-row justify-center gap-x-5 text-sm">
<Button severity="info" :label="$t('show_vpn_portal_config')" @click="showVpnPortalConfig" /> <Button severity="info" :label="t('show_vpn_portal_config')" @click="showVpnPortalConfig" />
<Button severity="info" :label="$t('show_event_log')" @click="showEventLogs" /> <Button severity="info" :label="t('show_event_log')" @click="showEventLogs" />
</div> </div>
</div> </div>
</template> </template>
@ -361,17 +362,17 @@ function showEventLogs() {
<Card> <Card>
<template #title> <template #title>
{{ $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-style="width: 100%">
<Column field="route.ipv4_addr" style="width: 100px;" :header="$t('virtual_ipv4')" /> <Column field="route.ipv4_addr" style="width: 100px;" :header="t('virtual_ipv4')" />
<Column field="route.hostname" style="max-width: 250px;" :header="$t('hostname')" /> <Column field="route.hostname" style="max-width: 250px;" :header="t('hostname')" />
<Column :field="routeCost" style="width: 100px;" :header="$t('route_cost')" /> <Column :field="routeCost" style="width: 100px;" :header="t('route_cost')" />
<Column :field="latencyMs" style="width: 80px;" :header="$t('latency')" /> <Column :field="latencyMs" style="width: 80px;" :header="t('latency')" />
<Column :field="txBytes" style="width: 80px;" :header="$t('upload_bytes')" /> <Column :field="txBytes" style="width: 80px;" :header="t('upload_bytes')" />
<Column :field="rxBytes" style="width: 80px;" :header="$t('download_bytes')" /> <Column :field="rxBytes" style="width: 80px;" :header="t('download_bytes')" />
<Column :field="lossRate" style="width: 100px;" :header="$t('loss_rate')" /> <Column :field="lossRate" style="width: 100px;" :header="t('loss_rate')" />
</DataTable> </DataTable>
</template> </template>
</Card> </Card>

View File

@ -160,7 +160,7 @@ function isRunning(id: string) {
</Panel> </Panel>
<Divider /> <Divider />
<div class="flex justify-content-end gap-2"> <div class="flex justify-content-end gap-2">
<Button type="button" :label="$t('close')" @click="visible = false" /> <Button type="button" :label="t('close')" @click="visible = false" />
</div> </div>
</Dialog> </Dialog>
@ -169,7 +169,7 @@ function isRunning(id: string) {
<template #start> <template #start>
<div class="flex align-items-center gap-2"> <div class="flex align-items-center gap-2">
<Button <Button
icon="pi pi-plus" class="mr-2" severity="primary" :label="$t('add_new_network')" icon="pi pi-plus" class="mr-2" severity="primary" :label="t('add_new_network')"
@click="addNewNetwork" @click="addNewNetwork"
/> />
</div> </div>
@ -179,7 +179,7 @@ function isRunning(id: string) {
<div class="min-w-80 mr-20"> <div class="min-w-80 mr-20">
<Dropdown <Dropdown
v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false" v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
:placeholder="$t('select_network')" class="w-full" :placeholder="t('select_network')" class="w-full"
> >
<template #value="slotProps"> <template #value="slotProps">
<div class="flex items-start content-center"> <div class="flex items-start content-center">
@ -195,7 +195,7 @@ function isRunning(id: string) {
</div> </div>
<Tag <Tag
class="my-auto" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'" class="my-auto" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
:value="$t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')" :value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')"
/> />
</div> </div>
</template> </template>
@ -203,11 +203,11 @@ function isRunning(id: string) {
<div class="flex flex-col items-start content-center"> <div class="flex flex-col items-start content-center">
<div class="flex"> <div class="flex">
<div class="mr-3"> <div class="mr-3">
{{ $t('network_name') }}: {{ slotProps.option.network_name }} {{ t('network_name') }}: {{ slotProps.option.network_name }}
</div> </div>
<Tag <Tag
class="my-auto" :severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'" class="my-auto" :severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
:value="$t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')" :value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')"
/> />
</div> </div>
<div>{{ slotProps.option.public_server_url }}</div> <div>{{ slotProps.option.public_server_url }}</div>
@ -219,7 +219,7 @@ function isRunning(id: string) {
<template #end> <template #end>
<Button <Button
icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" :label="$t('settings')" icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" :label="t('settings')"
aria-controls="overlay_setting_menu" @click="toggle_setting_menu" aria-controls="overlay_setting_menu" @click="toggle_setting_menu"
/> />
<Menu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" /> <Menu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
@ -228,7 +228,7 @@ function isRunning(id: string) {
</div> </div>
<Stepper class="h-full overflow-y-auto" :active-step="activeStep"> <Stepper class="h-full overflow-y-auto" :active-step="activeStep">
<StepperPanel :header="$t('config_network')"> <StepperPanel :header="t('config_network')">
<template #content="{ nextCallback }"> <template #content="{ nextCallback }">
<Config <Config
:instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None" :instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
@ -236,14 +236,14 @@ function isRunning(id: string) {
/> />
</template> </template>
</StepperPanel> </StepperPanel>
<StepperPanel :header="$t('running')"> <StepperPanel :header="t('running')">
<template #content="{ prevCallback }"> <template #content="{ prevCallback }">
<div class="flex flex-column"> <div class="flex flex-column">
<Status :instance-id="networkStore.curNetworkId" /> <Status :instance-id="networkStore.curNetworkId" />
</div> </div>
<div class="flex pt-4 justify-content-center"> <div class="flex pt-4 justify-content-center">
<Button <Button
:label="$t('stop_network')" severity="danger" icon="pi pi-arrow-left" :label="t('stop_network')" severity="danger" icon="pi pi-arrow-left"
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)" @click="stopNetworkCb(networkStore.curNetwork, prevCallback)"
/> />
</div> </div>

View File

@ -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/KKRainbow/EasyTier" homepage = "https://github.com/KKRainbow/EasyTier"
repository = "https://github.com/KKRainbow/EasyTier" repository = "https://github.com/KKRainbow/EasyTier"
version = "1.0.1-pre" version = "1.1.0"
edition = "2021" edition = "2021"
authors = ["kkrainbow"] authors = ["kkrainbow"]
keywords = ["vpn", "p2p", "network", "easytier"] keywords = ["vpn", "p2p", "network", "easytier"]
@ -85,7 +85,7 @@ http = { version = "1", default-features = false, features = [
tokio-rustls = { version = "0.26", default-features = false, optional = true } tokio-rustls = { version = "0.26", default-features = false, optional = true }
# for tap device # for tap device
tun = { git = "https://github.com/EasyTier/rust-tun.git", features = ["async"], rev = "e4fd1cd" } tun = { package = "tun-easytier", version = "0.6.1", features = ["async"] }
# for net ns # for net ns
nix = { version = "0.27", features = ["sched", "socket", "ioctl"] } nix = { version = "0.27", features = ["sched", "socket", "ioctl"] }
@ -136,8 +136,7 @@ network-interface = "1.1.1"
# for ospf route # for ospf route
petgraph = "0.6.5" petgraph = "0.6.5"
# for encryption boringtun = { package = "boringtun-easytier", version = "*", optional = true } # for encryption
boringtun = { git = "https://github.com/EasyTier/boringtun.git", optional = true, rev = "449204c" }
ring = { version = "0.17", optional = true } ring = { version = "0.17", optional = true }
bitflags = "2.5" bitflags = "2.5"
aes-gcm = { version = "0.10.3", optional = true } aes-gcm = { version = "0.10.3", optional = true }

View File

@ -1,4 +1,3 @@
use std::borrow::BorrowMut;
use std::collections::HashSet; use std::collections::HashSet;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::pin::Pin; use std::pin::Pin;
@ -82,16 +81,216 @@ impl IpProxy {
} }
} }
struct NicCtx {
global_ctx: ArcGlobalCtx,
peer_mgr: Weak<PeerManager>,
peer_packet_receiver: Arc<Mutex<PacketRecvChanReceiver>>,
nic: Arc<Mutex<virtual_nic::VirtualNic>>,
tasks: JoinSet<()>,
}
impl NicCtx {
fn new(
global_ctx: ArcGlobalCtx,
peer_manager: &Arc<PeerManager>,
peer_packet_receiver: Arc<Mutex<PacketRecvChanReceiver>>,
) -> Self {
NicCtx {
global_ctx: global_ctx.clone(),
peer_mgr: Arc::downgrade(&peer_manager),
peer_packet_receiver,
nic: Arc::new(Mutex::new(virtual_nic::VirtualNic::new(global_ctx))),
tasks: JoinSet::new(),
}
}
async fn assign_ipv4_to_tun_device(&self, ipv4_addr: Ipv4Addr) -> Result<(), Error> {
let nic = self.nic.lock().await;
nic.link_up().await?;
nic.remove_ip(None).await?;
nic.add_ip(ipv4_addr, 24).await?;
if cfg!(target_os = "macos") {
nic.add_route(ipv4_addr, 24).await?;
}
Ok(())
}
async fn do_forward_nic_to_peers_ipv4(ret: ZCPacket, mgr: &PeerManager) {
if let Some(ipv4) = Ipv4Packet::new(ret.payload()) {
if ipv4.get_version() != 4 {
tracing::info!("[USER_PACKET] not ipv4 packet: {:?}", ipv4);
return;
}
let dst_ipv4 = ipv4.get_destination();
tracing::trace!(
?ret,
"[USER_PACKET] recv new packet from tun device and forward to peers."
);
// TODO: use zero-copy
let send_ret = mgr.send_msg_ipv4(ret, dst_ipv4).await;
if send_ret.is_err() {
tracing::trace!(?send_ret, "[USER_PACKET] send_msg_ipv4 failed")
}
} else {
tracing::warn!(?ret, "[USER_PACKET] not ipv4 packet");
}
}
fn do_forward_nic_to_peers(
&mut self,
mut stream: Pin<Box<dyn ZCPacketStream>>,
) -> Result<(), Error> {
// read from nic and write to corresponding tunnel
let Some(mgr) = self.peer_mgr.upgrade() else {
return Err(anyhow::anyhow!("peer manager not available").into());
};
self.tasks.spawn(async move {
while let Some(ret) = stream.next().await {
if ret.is_err() {
log::error!("read from nic failed: {:?}", ret);
break;
}
Self::do_forward_nic_to_peers_ipv4(ret.unwrap(), mgr.as_ref()).await;
}
});
Ok(())
}
fn do_forward_peers_to_nic(&mut self, mut sink: Pin<Box<dyn ZCPacketSink>>) {
let channel = self.peer_packet_receiver.clone();
self.tasks.spawn(async move {
// unlock until coroutine finished
let mut channel = channel.lock().await;
while let Some(packet) = channel.recv().await {
tracing::trace!(
"[USER_PACKET] forward packet from peers to nic. packet: {:?}",
packet
);
let ret = sink.send(packet).await;
if ret.is_err() {
tracing::error!(?ret, "do_forward_tunnel_to_nic sink error");
}
}
});
}
async fn run_proxy_cidrs_route_updater(&mut self) -> Result<(), Error> {
let Some(peer_mgr) = self.peer_mgr.upgrade() else {
return Err(anyhow::anyhow!("peer manager not available").into());
};
let global_ctx = self.global_ctx.clone();
let net_ns = self.global_ctx.net_ns.clone();
let nic = self.nic.lock().await;
let ifcfg = nic.get_ifcfg();
let ifname = nic.ifname().to_owned();
self.tasks.spawn(async move {
let mut cur_proxy_cidrs = vec![];
loop {
let mut proxy_cidrs = vec![];
let routes = peer_mgr.list_routes().await;
for r in routes {
for cidr in r.proxy_cidrs {
let Ok(cidr) = cidr.parse::<cidr::Ipv4Cidr>() else {
continue;
};
proxy_cidrs.push(cidr);
}
}
// add vpn portal cidr to proxy_cidrs
if let Some(vpn_cfg) = global_ctx.config.get_vpn_portal_config() {
proxy_cidrs.push(vpn_cfg.client_cidr);
}
// if route is in cur_proxy_cidrs but not in proxy_cidrs, delete it.
for cidr in cur_proxy_cidrs.iter() {
if proxy_cidrs.contains(cidr) {
continue;
}
let _g = net_ns.guard();
let ret = ifcfg
.remove_ipv4_route(
ifname.as_str(),
cidr.first_address(),
cidr.network_length(),
)
.await;
if ret.is_err() {
tracing::trace!(
cidr = ?cidr,
err = ?ret,
"remove route failed.",
);
}
}
for cidr in proxy_cidrs.iter() {
if cur_proxy_cidrs.contains(cidr) {
continue;
}
let _g = net_ns.guard();
let ret = ifcfg
.add_ipv4_route(
ifname.as_str(),
cidr.first_address(),
cidr.network_length(),
)
.await;
if ret.is_err() {
tracing::trace!(
cidr = ?cidr,
err = ?ret,
"add route failed.",
);
}
}
cur_proxy_cidrs = proxy_cidrs;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
Ok(())
}
async fn run(&mut self, ipv4_addr: Ipv4Addr) -> Result<(), Error> {
let tunnel = {
let mut nic = self.nic.lock().await;
let ret = nic.create_dev().await?;
self.global_ctx
.issue_event(GlobalCtxEvent::TunDeviceReady(nic.ifname().to_string()));
ret
};
let (stream, sink) = tunnel.split();
self.do_forward_nic_to_peers(stream)?;
self.do_forward_peers_to_nic(sink);
self.assign_ipv4_to_tun_device(ipv4_addr).await?;
self.run_proxy_cidrs_route_updater().await?;
Ok(())
}
}
type ArcNicCtx = Arc<Mutex<Option<NicCtx>>>;
pub struct Instance { pub struct Instance {
inst_name: String, inst_name: String,
id: uuid::Uuid, id: uuid::Uuid,
virtual_nic: Option<Arc<virtual_nic::VirtualNic>>, nic_ctx: ArcNicCtx,
peer_packet_receiver: Option<PacketRecvChanReceiver>,
tasks: JoinSet<()>, tasks: JoinSet<()>,
peer_packet_receiver: Arc<Mutex<PacketRecvChanReceiver>>,
peer_manager: Arc<PeerManager>, peer_manager: Arc<PeerManager>,
listener_manager: Arc<Mutex<ListenerManager<PeerManager>>>, listener_manager: Arc<Mutex<ListenerManager<PeerManager>>>,
conn_manager: Arc<ManualConnectorManager>, conn_manager: Arc<ManualConnectorManager>,
@ -153,8 +352,8 @@ impl Instance {
inst_name: global_ctx.inst_name.clone(), inst_name: global_ctx.inst_name.clone(),
id, id,
virtual_nic: None, peer_packet_receiver: Arc::new(Mutex::new(peer_packet_receiver)),
peer_packet_receiver: Some(peer_packet_receiver), nic_ctx: Arc::new(Mutex::new(None)),
tasks: JoinSet::new(), tasks: JoinSet::new(),
peer_manager, peer_manager,
@ -177,78 +376,6 @@ impl Instance {
self.conn_manager.clone() self.conn_manager.clone()
} }
async fn do_forward_nic_to_peers_ipv4(ret: ZCPacket, mgr: &PeerManager) {
if let Some(ipv4) = Ipv4Packet::new(ret.payload()) {
if ipv4.get_version() != 4 {
tracing::info!("[USER_PACKET] not ipv4 packet: {:?}", ipv4);
return;
}
let dst_ipv4 = ipv4.get_destination();
tracing::trace!(
?ret,
"[USER_PACKET] recv new packet from tun device and forward to peers."
);
// TODO: use zero-copy
let send_ret = mgr.send_msg_ipv4(ret, dst_ipv4).await;
if send_ret.is_err() {
tracing::trace!(?send_ret, "[USER_PACKET] send_msg_ipv4 failed")
}
} else {
tracing::warn!(?ret, "[USER_PACKET] not ipv4 packet");
}
}
// async fn do_forward_nic_to_peers_ethernet(mut ret: BytesMut, mgr: &PeerManager) {
// if let Some(eth) = EthernetPacket::new(&ret) {
// log::warn!("begin to forward: {:?}, type: {}", eth, eth.get_ethertype());
// Self::do_forward_nic_to_peers_ipv4(ret.split_off(14), mgr).await;
// } else {
// log::warn!("not ipv4 packet: {:?}", ret);
// }
// }
fn do_forward_nic_to_peers(
&mut self,
mut stream: Pin<Box<dyn ZCPacketStream>>,
) -> Result<(), Error> {
// read from nic and write to corresponding tunnel
let mgr = self.peer_manager.clone();
self.tasks.spawn(async move {
while let Some(ret) = stream.next().await {
if ret.is_err() {
log::error!("read from nic failed: {:?}", ret);
break;
}
Self::do_forward_nic_to_peers_ipv4(ret.unwrap(), mgr.as_ref()).await;
// Self::do_forward_nic_to_peers_ethernet(ret.into(), mgr.as_ref()).await;
}
});
Ok(())
}
fn do_forward_peers_to_nic(
tasks: &mut JoinSet<()>,
mut sink: Pin<Box<dyn ZCPacketSink>>,
channel: Option<PacketRecvChanReceiver>,
) {
tasks.spawn(async move {
let mut channel = channel.unwrap();
while let Some(packet) = channel.recv().await {
tracing::trace!(
"[USER_PACKET] forward packet from peers to nic. packet: {:?}",
packet
);
let ret = sink.send(packet).await;
if ret.is_err() {
tracing::error!(?ret, "do_forward_tunnel_to_nic sink error");
}
}
});
}
async fn add_initial_peers(&mut self) -> Result<(), Error> { async fn add_initial_peers(&mut self) -> Result<(), Error> {
for peer in self.global_ctx.config.get_peers().iter() { for peer in self.global_ctx.config.get_peers().iter() {
self.get_conn_manager() self.get_conn_manager()
@ -258,35 +385,13 @@ impl Instance {
Ok(()) Ok(())
} }
async fn prepare_tun_device(&mut self) -> Result<(), Error> { async fn clear_nic_ctx(arc_nic_ctx: ArcNicCtx) {
let mut nic = virtual_nic::VirtualNic::new(self.get_global_ctx()); let _ = arc_nic_ctx.lock().await.take();
let tunnel = nic.create_dev().await?;
self.global_ctx
.issue_event(GlobalCtxEvent::TunDeviceReady(nic.ifname().to_string()));
let (stream, sink) = tunnel.split();
self.virtual_nic = Some(Arc::new(nic));
self.do_forward_nic_to_peers(stream).unwrap();
Self::do_forward_peers_to_nic(
self.tasks.borrow_mut(),
sink,
self.peer_packet_receiver.take(),
);
Ok(())
} }
async fn assign_ipv4_to_tun_device(&mut self, ipv4_addr: Ipv4Addr) -> Result<(), Error> { async fn use_new_nic_ctx(arc_nic_ctx: ArcNicCtx, nic_ctx: NicCtx) {
let nic = self.virtual_nic.as_ref().unwrap().clone(); let mut g = arc_nic_ctx.lock().await;
nic.link_up().await?; *g = Some(nic_ctx);
nic.remove_ip(None).await?;
nic.add_ip(ipv4_addr, 24).await?;
if cfg!(target_os = "macos") {
nic.add_route(ipv4_addr, 24).await?;
}
Ok(())
} }
// Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed. // Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed.
@ -294,7 +399,8 @@ impl Instance {
use rand::Rng; use rand::Rng;
let peer_manager_c = self.peer_manager.clone(); let peer_manager_c = self.peer_manager.clone();
let global_ctx_c = self.get_global_ctx(); let global_ctx_c = self.get_global_ctx();
let nic_c = self.virtual_nic.as_ref().unwrap().clone(); let nic_ctx = self.nic_ctx.clone();
let peer_packet_receiver = self.peer_packet_receiver.clone();
tokio::spawn(async move { tokio::spawn(async move {
let default_ipv4_addr = Ipv4Addr::new(10, 0, 0, 0); let default_ipv4_addr = Ipv4Addr::new(10, 0, 0, 0);
let mut dhcp_ip: Option<Ipv4Inet> = None; let mut dhcp_ip: Option<Ipv4Inet> = None;
@ -348,42 +454,30 @@ impl Instance {
if dhcp_ip != ipv4_addr { if dhcp_ip != ipv4_addr {
let last_ip = dhcp_ip.map(|p| p.address()); let last_ip = dhcp_ip.map(|p| p.address());
tracing::debug!("last_ip: {:?}", last_ip); tracing::debug!("last_ip: {:?}", last_ip);
let _ = nic_c.remove_ip(last_ip).await;
#[cfg(target_os = "macos")]
if last_ip.is_some() {
let _g = global_ctx_c.net_ns.guard();
let ret = nic_c
.get_ifcfg()
.remove_ipv4_route(nic_c.ifname(), last_ip.unwrap(), 24)
.await;
if ret.is_err() { Self::clear_nic_ctx(nic_ctx.clone()).await;
tracing::trace!(
cidr = 24,
err = ?ret,
"remove route failed.",
);
}
}
if let Some(ip) = ipv4_addr { if let Some(ip) = ipv4_addr {
let _ = nic_c.link_up().await; let mut new_nic_ctx = NicCtx::new(
global_ctx_c.clone(),
&peer_manager_c,
peer_packet_receiver.clone(),
);
dhcp_ip = Some(ip); dhcp_ip = Some(ip);
tries = 1; tries = 1;
if let Err(e) = nic_c.add_ip(ip.address(), 24).await { if let Err(e) = new_nic_ctx.run(ip.address()).await {
tracing::error!("add ip failed: {:?}", e); tracing::error!("add ip failed: {:?}", e);
global_ctx_c.set_ipv4(None); global_ctx_c.set_ipv4(None);
let sleep: u64 = rand::thread_rng().gen_range(200..500); let sleep: u64 = rand::thread_rng().gen_range(200..500);
tokio::time::sleep(std::time::Duration::from_millis(sleep)).await; tokio::time::sleep(std::time::Duration::from_millis(sleep)).await;
continue; continue;
} }
#[cfg(target_os = "macos")]
let _ = nic_c.add_route(ip.address(), 24).await;
global_ctx_c.set_ipv4(Some(ip.address())); global_ctx_c.set_ipv4(Some(ip.address()));
global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Changed( global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Changed(
last_ip, last_ip,
Some(ip.address()), Some(ip.address()),
)); ));
Self::use_new_nic_ctx(nic_ctx.clone(), new_nic_ctx).await;
} else { } else {
global_ctx_c.set_ipv4(None); global_ctx_c.set_ipv4(None);
global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Conflicted(last_ip)); global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Conflicted(last_ip));
@ -409,13 +503,15 @@ impl Instance {
self.peer_manager.run().await?; self.peer_manager.run().await?;
if self.global_ctx.config.get_dhcp() { if self.global_ctx.config.get_dhcp() {
self.prepare_tun_device().await?;
self.run_proxy_cidrs_route_updater();
self.check_dhcp_ip_conflict(); self.check_dhcp_ip_conflict();
} else if let Some(ipv4_addr) = self.global_ctx.get_ipv4() { } else if let Some(ipv4_addr) = self.global_ctx.get_ipv4() {
self.prepare_tun_device().await?; let mut new_nic_ctx = NicCtx::new(
self.assign_ipv4_to_tun_device(ipv4_addr).await?; self.global_ctx.clone(),
self.run_proxy_cidrs_route_updater(); &self.peer_manager,
self.peer_packet_receiver.clone(),
);
new_nic_ctx.run(ipv4_addr).await?;
Self::use_new_nic_ctx(self.nic_ctx.clone(), new_nic_ctx).await;
} }
self.run_rpc_server()?; self.run_rpc_server()?;
@ -577,84 +673,6 @@ impl Instance {
Ok(()) Ok(())
} }
fn run_proxy_cidrs_route_updater(&mut self) {
let peer_mgr = self.peer_manager.clone();
let global_ctx = self.global_ctx.clone();
let net_ns = self.global_ctx.net_ns.clone();
let nic = self.virtual_nic.as_ref().unwrap().clone();
let ifcfg = nic.get_ifcfg();
let ifname = nic.ifname().to_owned();
self.tasks.spawn(async move {
let mut cur_proxy_cidrs = vec![];
loop {
let mut proxy_cidrs = vec![];
let routes = peer_mgr.list_routes().await;
for r in routes {
for cidr in r.proxy_cidrs {
let Ok(cidr) = cidr.parse::<cidr::Ipv4Cidr>() else {
continue;
};
proxy_cidrs.push(cidr);
}
}
// add vpn portal cidr to proxy_cidrs
if let Some(vpn_cfg) = global_ctx.config.get_vpn_portal_config() {
proxy_cidrs.push(vpn_cfg.client_cidr);
}
// if route is in cur_proxy_cidrs but not in proxy_cidrs, delete it.
for cidr in cur_proxy_cidrs.iter() {
if proxy_cidrs.contains(cidr) {
continue;
}
let _g = net_ns.guard();
let ret = ifcfg
.remove_ipv4_route(
ifname.as_str(),
cidr.first_address(),
cidr.network_length(),
)
.await;
if ret.is_err() {
tracing::trace!(
cidr = ?cidr,
err = ?ret,
"remove route failed.",
);
}
}
for cidr in proxy_cidrs.iter() {
if cur_proxy_cidrs.contains(cidr) {
continue;
}
let _g = net_ns.guard();
let ret = ifcfg
.add_ipv4_route(
ifname.as_str(),
cidr.first_address(),
cidr.network_length(),
)
.await;
if ret.is_err() {
tracing::trace!(
cidr = ?cidr,
err = ?ret,
"add route failed.",
);
}
}
cur_proxy_cidrs = proxy_cidrs;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
}
pub fn get_global_ctx(&self) -> ArcGlobalCtx { pub fn get_global_ctx(&self) -> ArcGlobalCtx {
self.global_ctx.clone() self.global_ctx.clone()
} }

View File

@ -260,9 +260,8 @@ impl VirtualNic {
Ok(self) Ok(self)
} }
async fn create_dev_ret_err(&mut self) -> Result<Box<dyn Tunnel>, Error> { async fn create_tun(&mut self) -> Result<AsyncDevice, Error> {
let mut config = Configuration::default(); let mut config = Configuration::default();
let has_packet_info = cfg!(target_os = "macos");
config.layer(Layer::L3); config.layer(Layer::L3);
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -303,11 +302,12 @@ impl VirtualNic {
config.queues(self.queue_num); config.queues(self.queue_num);
config.up(); config.up();
let dev = { let _g = self.global_ctx.net_ns.guard();
let _g = self.global_ctx.net_ns.guard(); Ok(create_as_async(&config)?)
create_as_async(&config)? }
};
async fn create_dev_ret_err(&mut self) -> Result<Box<dyn Tunnel>, Error> {
let dev = self.create_tun().await?;
let ifname = dev.get_ref().name()?; let ifname = dev.get_ref().name()?;
self.ifcfg.wait_interface_show(ifname.as_str()).await?; self.ifcfg.wait_interface_show(ifname.as_str()).await?;
@ -324,8 +324,8 @@ impl VirtualNic {
.await?; .await?;
} }
let has_packet_info = cfg!(target_os = "macos");
let (a, b) = BiLock::new(dev); let (a, b) = BiLock::new(dev);
let ft = TunnelWrapper::new( let ft = TunnelWrapper::new(
TunStream::new(a, has_packet_info), TunStream::new(a, has_packet_info),
FramedWriter::new_with_converter( FramedWriter::new_with_converter(

View File

@ -1,5 +1,8 @@
use std::{ use std::{
sync::{atomic::AtomicU32, Arc}, sync::{
atomic::{AtomicBool, AtomicU32, Ordering},
Arc,
},
time::Instant, time::Instant,
}; };
@ -40,11 +43,13 @@ type PacketSender = UnboundedSender<ZCPacket>;
struct PeerRpcEndPoint { struct PeerRpcEndPoint {
peer_id: PeerId, peer_id: PeerId,
packet_sender: PacketSender, packet_sender: PacketSender,
last_used: AtomicCell<Instant>, create_time: AtomicCell<Instant>,
finished: Arc<AtomicBool>,
tasks: JoinSet<()>, tasks: JoinSet<()>,
} }
type PeerRpcEndPointCreator = Box<dyn Fn(PeerId) -> PeerRpcEndPoint + Send + Sync + 'static>; type PeerRpcEndPointCreator =
Box<dyn Fn(PeerId, PeerRpcTransactId) -> PeerRpcEndPoint + Send + Sync + 'static>;
#[derive(Hash, Eq, PartialEq, Clone)] #[derive(Hash, Eq, PartialEq, Clone)]
struct PeerRpcClientCtxKey(PeerId, PeerRpcServiceId, PeerRpcTransactId); struct PeerRpcClientCtxKey(PeerId, PeerRpcServiceId, PeerRpcTransactId);
@ -55,8 +60,8 @@ pub struct PeerRpcManager {
tspt: Arc<Box<dyn PeerRpcManagerTransport>>, tspt: Arc<Box<dyn PeerRpcManagerTransport>>,
service_registry: Arc<DashMap<PeerRpcServiceId, PeerRpcEndPointCreator>>, service_registry: Arc<DashMap<PeerRpcServiceId, PeerRpcEndPointCreator>>,
peer_rpc_endpoints: Arc<DashMap<(PeerId, PeerRpcServiceId), PeerRpcEndPoint>>,
peer_rpc_endpoints: Arc<DashMap<PeerRpcClientCtxKey, PeerRpcEndPoint>>,
client_resp_receivers: Arc<DashMap<PeerRpcClientCtxKey, PacketSender>>, client_resp_receivers: Arc<DashMap<PeerRpcClientCtxKey, PacketSender>>,
transact_id: AtomicU32, transact_id: AtomicU32,
@ -109,11 +114,19 @@ impl PacketMerger {
Some(tmpl_packet) Some(tmpl_packet)
} }
fn feed(&mut self, packet: ZCPacket) -> Result<Option<TaRpcPacket>, Error> { fn feed(
&mut self,
packet: ZCPacket,
expected_tid: Option<PeerRpcTransactId>,
) -> Result<Option<TaRpcPacket>, Error> {
let payload = packet.payload(); let payload = packet.payload();
let rpc_packet = let rpc_packet =
TaRpcPacket::decode(payload).map_err(|e| Error::MessageDecodeError(e.to_string()))?; TaRpcPacket::decode(payload).map_err(|e| Error::MessageDecodeError(e.to_string()))?;
if expected_tid.is_some() && rpc_packet.transact_id != expected_tid.unwrap() {
return Ok(None);
}
let total_pieces = rpc_packet.total_pieces; let total_pieces = rpc_packet.total_pieces;
let piece_idx = rpc_packet.piece_idx; let piece_idx = rpc_packet.piece_idx;
@ -176,11 +189,12 @@ impl PeerRpcManager {
S::Fut: Send + 'static, S::Fut: Send + 'static,
{ {
let tspt = self.tspt.clone(); let tspt = self.tspt.clone();
let creator = Box::new(move |peer_id: PeerId| { let creator = Box::new(move |peer_id: PeerId, transact_id: PeerRpcTransactId| {
let mut tasks = JoinSet::new(); let mut tasks = JoinSet::new();
let (packet_sender, mut packet_receiver) = mpsc::unbounded_channel(); let (packet_sender, mut packet_receiver) = mpsc::unbounded_channel();
let (mut client_transport, server_transport) = tarpc::transport::channel::unbounded(); let (mut client_transport, server_transport) = tarpc::transport::channel::unbounded();
let server = tarpc::server::BaseChannel::with_defaults(server_transport); let server = tarpc::server::BaseChannel::with_defaults(server_transport);
let finished = Arc::new(AtomicBool::new(false));
let my_peer_id_clone = tspt.my_peer_id(); let my_peer_id_clone = tspt.my_peer_id();
let peer_id_clone = peer_id.clone(); let peer_id_clone = peer_id.clone();
@ -189,19 +203,13 @@ impl PeerRpcManager {
tasks.spawn(o); tasks.spawn(o);
let tspt = tspt.clone(); let tspt = tspt.clone();
let finished_clone = finished.clone();
tasks.spawn(async move { tasks.spawn(async move {
let mut cur_req_peer_id = None;
let mut cur_transact_id = 0;
let mut packet_merger = PacketMerger::new(); let mut packet_merger = PacketMerger::new();
loop { loop {
tokio::select! { tokio::select! {
Some(resp) = client_transport.next() => { Some(resp) = client_transport.next() => {
let Some(cur_req_peer_id) = cur_req_peer_id.take() else { tracing::debug!(resp = ?resp, ?transact_id, ?peer_id, "server recv packet from service provider");
tracing::error!("[PEER RPC MGR] cur_req_peer_id is none, ignore this resp");
continue;
};
tracing::debug!(resp = ?resp, "server recv packet from service provider");
if resp.is_err() { if resp.is_err() {
tracing::warn!(err = ?resp.err(), tracing::warn!(err = ?resp.err(),
"[PEER RPC MGR] client_transport in server side got channel error, ignore it."); "[PEER RPC MGR] client_transport in server side got channel error, ignore it.");
@ -217,11 +225,11 @@ impl PeerRpcManager {
let msgs = Self::build_rpc_packet( let msgs = Self::build_rpc_packet(
tspt.my_peer_id(), tspt.my_peer_id(),
cur_req_peer_id, peer_id,
service_id, service_id,
cur_transact_id, transact_id,
false, false,
serialized_resp.unwrap(), serialized_resp.as_ref().unwrap(),
); );
for msg in msgs { for msg in msgs {
@ -230,11 +238,13 @@ impl PeerRpcManager {
break; break;
} }
} }
finished_clone.store(true, Ordering::Relaxed);
} }
Some(packet) = packet_receiver.recv() => { Some(packet) = packet_receiver.recv() => {
tracing::trace!("recv packet from peer, packet: {:?}", packet); tracing::trace!("recv packet from peer, packet: {:?}", packet);
let info = match packet_merger.feed(packet) { let info = match packet_merger.feed(packet, None) {
Err(e) => { Err(e) => {
tracing::error!(error = ?e, "feed packet to merger failed"); tracing::error!(error = ?e, "feed packet to merger failed");
continue; continue;
@ -247,10 +257,9 @@ impl PeerRpcManager {
} }
}; };
cur_req_peer_id = Some(info.from_peer);
cur_transact_id = info.transact_id;
assert_eq!(info.service_id, service_id); assert_eq!(info.service_id, service_id);
assert_eq!(info.from_peer, peer_id);
assert_eq!(info.transact_id, transact_id);
let decoded_ret = postcard::from_bytes(&info.content.as_slice()); let decoded_ret = postcard::from_bytes(&info.content.as_slice());
if let Err(e) = decoded_ret { if let Err(e) = decoded_ret {
@ -279,7 +288,8 @@ impl PeerRpcManager {
return PeerRpcEndPoint { return PeerRpcEndPoint {
peer_id, peer_id,
packet_sender, packet_sender,
last_used: AtomicCell::new(Instant::now()), create_time: AtomicCell::new(Instant::now()),
finished,
tasks, tasks,
}; };
// let resp = client_transport.next().await; // let resp = client_transport.next().await;
@ -310,7 +320,7 @@ impl PeerRpcManager {
service_id: PeerRpcServiceId, service_id: PeerRpcServiceId,
transact_id: PeerRpcTransactId, transact_id: PeerRpcTransactId,
is_req: bool, is_req: bool,
content: Vec<u8>, content: &Vec<u8>,
) -> Vec<ZCPacket> { ) -> Vec<ZCPacket> {
let mut ret = Vec::new(); let mut ret = Vec::new();
let content_mtu = RPC_PACKET_CONTENT_MTU; let content_mtu = RPC_PACKET_CONTENT_MTU;
@ -373,12 +383,18 @@ impl PeerRpcManager {
} }
let endpoint = peer_rpc_endpoints let endpoint = peer_rpc_endpoints
.entry((info.from_peer, info.service_id)) .entry(PeerRpcClientCtxKey(
info.from_peer,
info.service_id,
info.transact_id,
))
.or_insert_with(|| { .or_insert_with(|| {
service_registry.get(&info.service_id).unwrap()(info.from_peer) service_registry.get(&info.service_id).unwrap()(
info.from_peer,
info.transact_id,
)
}); });
endpoint.last_used.store(Instant::now());
endpoint.packet_sender.send(o).unwrap(); endpoint.packet_sender.send(o).unwrap();
} else { } else {
if let Some(a) = client_resp_receivers.get(&PeerRpcClientCtxKey( if let Some(a) = client_resp_receivers.get(&PeerRpcClientCtxKey(
@ -400,29 +416,42 @@ impl PeerRpcManager {
let peer_rpc_endpoints = self.peer_rpc_endpoints.clone(); let peer_rpc_endpoints = self.peer_rpc_endpoints.clone();
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
peer_rpc_endpoints.retain(|_, v| v.last_used.load().elapsed().as_secs() < 60); peer_rpc_endpoints.retain(|_, v| {
v.create_time.load().elapsed().as_secs() < 30
&& !v.finished.load(Ordering::Relaxed)
});
} }
}); });
} }
#[tracing::instrument(skip(f))] #[tracing::instrument(skip(f))]
pub async fn do_client_rpc_scoped<CM, Req, RpcRet, Fut>( pub async fn do_client_rpc_scoped<Resp, Req, RpcRet, Fut>(
&self, &self,
service_id: PeerRpcServiceId, service_id: PeerRpcServiceId,
dst_peer_id: PeerId, dst_peer_id: PeerId,
f: impl FnOnce(UnboundedChannel<CM, Req>) -> Fut, f: impl FnOnce(UnboundedChannel<Resp, Req>) -> Fut,
) -> RpcRet ) -> RpcRet
where where
CM: serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync + 'static, Resp: serde::Serialize
Req: serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync + 'static, + for<'a> serde::Deserialize<'a>
+ Send
+ Sync
+ std::fmt::Debug
+ 'static,
Req: serde::Serialize
+ for<'a> serde::Deserialize<'a>
+ Send
+ Sync
+ std::fmt::Debug
+ 'static,
Fut: std::future::Future<Output = RpcRet>, Fut: std::future::Future<Output = RpcRet>,
{ {
let mut tasks = JoinSet::new(); let mut tasks = JoinSet::new();
let (packet_sender, mut packet_receiver) = mpsc::unbounded_channel(); let (packet_sender, mut packet_receiver) = mpsc::unbounded_channel();
let (client_transport, server_transport) = let (client_transport, server_transport) =
tarpc::transport::channel::unbounded::<CM, Req>(); tarpc::transport::channel::unbounded::<Resp, Req>();
let (mut server_s, mut server_r) = server_transport.split(); let (mut server_s, mut server_r) = server_transport.split();
@ -438,9 +467,9 @@ impl PeerRpcManager {
continue; continue;
} }
let a = postcard::to_allocvec(&a.unwrap()); let req = postcard::to_allocvec(&a.unwrap());
if a.is_err() { if req.is_err() {
tracing::error!(error = ?a.err(), "bincode serialize failed"); tracing::error!(error = ?req.err(), "bincode serialize failed");
continue; continue;
} }
@ -450,10 +479,10 @@ impl PeerRpcManager {
service_id, service_id,
transact_id, transact_id,
true, true,
a.unwrap(), req.as_ref().unwrap(),
); );
tracing::debug!(?packets, "client send rpc packet to peer"); tracing::debug!(?packets, ?req, ?transact_id, "client send rpc packet to peer");
for packet in packets { for packet in packets {
if let Err(e) = tspt.send(packet, dst_peer_id).await { if let Err(e) = tspt.send(packet, dst_peer_id).await {
@ -471,7 +500,7 @@ impl PeerRpcManager {
while let Some(packet) = packet_receiver.recv().await { while let Some(packet) = packet_receiver.recv().await {
tracing::trace!("tunnel recv: {:?}", packet); tracing::trace!("tunnel recv: {:?}", packet);
let info = match packet_merger.feed(packet) { let info = match packet_merger.feed(packet, Some(transact_id)) {
Err(e) => { Err(e) => {
tracing::error!(error = ?e, "feed packet to merger failed"); tracing::error!(error = ?e, "feed packet to merger failed");
continue; continue;
@ -482,9 +511,11 @@ impl PeerRpcManager {
Ok(Some(info)) => info, Ok(Some(info)) => info,
}; };
tracing::debug!(?info, "client recv rpc packet from peer");
let decoded = postcard::from_bytes(&info.content.as_slice()); let decoded = postcard::from_bytes(&info.content.as_slice());
tracing::debug!(?info, ?decoded, "client recv rpc packet from peer");
assert_eq!(info.transact_id, transact_id);
if let Err(e) = decoded { if let Err(e) = decoded {
tracing::error!(error = ?e, "decode rpc packet failed"); tracing::error!(error = ?e, "decode rpc packet failed");
continue; continue;
@ -517,7 +548,7 @@ impl PeerRpcManager {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use std::{pin::Pin, sync::Arc}; use std::{pin::Pin, sync::Arc, time::Duration};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -526,7 +557,10 @@ pub mod tests {
common::{error::Error, new_peer_id, PeerId}, common::{error::Error, new_peer_id, PeerId},
peers::{ peers::{
peer_rpc::PeerRpcManager, peer_rpc::PeerRpcManager,
tests::{connect_peer_manager, create_mock_peer_manager, wait_route_appear}, tests::{
connect_peer_manager, create_mock_peer_manager, wait_for_condition,
wait_route_appear,
},
}, },
tunnel::{ tunnel::{
packet_def::ZCPacket, ring::create_ring_tunnel_pair, Tunnel, ZCPacketSink, packet_def::ZCPacket, ring::create_ring_tunnel_pair, Tunnel, ZCPacketSink,
@ -634,6 +668,12 @@ pub mod tests {
println!("ret: {:?}", ret); println!("ret: {:?}", ret);
assert_eq!(ret.unwrap(), format!("hello {}", msg)); assert_eq!(ret.unwrap(), format!("hello {}", msg));
wait_for_condition(
|| async { server_rpc_mgr.peer_rpc_endpoints.is_empty() },
Duration::from_secs(10),
)
.await;
} }
#[tokio::test] #[tokio::test]
@ -751,5 +791,11 @@ pub mod tests {
.await; .await;
assert_eq!(ip_list.unwrap(), format!("hello_b {}", msg)); assert_eq!(ip_list.unwrap(), format!("hello_b {}", msg));
wait_for_condition(
|| async { peer_mgr_b.get_peer_rpc_mgr().peer_rpc_endpoints.is_empty() },
Duration::from_secs(10),
)
.await;
} }
} }