Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d880dfbbca | ||
|
|
b46a200f8d | ||
|
|
81490d0662 | ||
|
|
3d1e841cc5 | ||
|
|
f52936a103 | ||
|
|
23f69ce6a4 | ||
|
|
f84ae228fc | ||
|
|
74c716ccaa | ||
|
|
445b02b2ca | ||
|
|
bb17ffa9fc | ||
|
|
389ea709ce | ||
|
|
c2f535ead4 | ||
|
|
0318f55322 | ||
|
|
1f4340e82f | ||
|
|
ed08707c98 | ||
|
|
7397abcb94 | ||
|
|
98d321f8ac | ||
|
|
e78b0ef869 | ||
|
|
8d654330ac | ||
|
|
00d61333d3 | ||
|
|
03b55b61e7 | ||
|
|
745e44cc87 | ||
|
|
24213a874a | ||
|
|
155f8a2ba2 | ||
|
|
568dca6f9c | ||
|
|
673c34cf5a | ||
|
|
2050ed78d0 | ||
|
|
2632c44195 | ||
|
|
5449eabf2a | ||
|
|
dd5b00faf4 | ||
|
|
0caec3e4da | ||
|
|
e48e62cac0 | ||
|
|
06ebda2e2f | ||
|
|
53c449b9fb | ||
|
|
51e0fac72c |
4
.github/workflows/Dockerfile
vendored
@@ -18,7 +18,7 @@ RUN mkdir -p /tmp/output; \
|
|||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
RUN apk add --no-cache tzdata
|
RUN apk add --no-cache tzdata tini
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder --chmod=755 /tmp/output/* /usr/local/bin
|
COPY --from=builder --chmod=755 /tmp/output/* /usr/local/bin
|
||||||
|
|
||||||
@@ -36,4 +36,4 @@ EXPOSE 11011/tcp
|
|||||||
# wss
|
# wss
|
||||||
EXPOSE 11012/tcp
|
EXPOSE 11012/tcp
|
||||||
|
|
||||||
ENTRYPOINT ["easytier-core"]
|
ENTRYPOINT ["/sbin/tini", "--", "easytier-core"]
|
||||||
|
|||||||
12
.github/workflows/core.yml
vendored
@@ -208,18 +208,6 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
./artifacts/*
|
./artifacts/*
|
||||||
|
|
||||||
- name: Upload OSS
|
|
||||||
if: ${{ env.OSS_BUCKET != '' }}
|
|
||||||
uses: Menci/upload-to-oss@main
|
|
||||||
with:
|
|
||||||
access-key-id: ${{ secrets.ALIYUN_OSS_ACCESS_ID }}
|
|
||||||
access-key-secret: ${{ secrets.ALIYUN_OSS_ACCESS_KEY }}
|
|
||||||
endpoint: ${{ secrets.ALIYUN_OSS_ENDPOINT }}
|
|
||||||
bucket: ${{ secrets.ALIYUN_OSS_BUCKET }}
|
|
||||||
local-path: ./artifacts/
|
|
||||||
remote-path: /easytier-releases/${{env.GIT_DESC}}/easytier-${{ matrix.ARTIFACT_NAME }}
|
|
||||||
no-delete-remote-files: true
|
|
||||||
retry: 5
|
|
||||||
core-result:
|
core-result:
|
||||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
12
.github/workflows/docker.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
image_tag:
|
image_tag:
|
||||||
description: 'Tag for this image build'
|
description: 'Tag for this image build'
|
||||||
type: string
|
type: string
|
||||||
default: 'v2.2.1'
|
default: 'v2.2.4'
|
||||||
required: true
|
required: true
|
||||||
mark_latest:
|
mark_latest:
|
||||||
description: 'Mark this image as latest'
|
description: 'Mark this image as latest'
|
||||||
@@ -39,6 +39,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: login github container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
id: download-artifact
|
id: download-artifact
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v6
|
||||||
@@ -58,4 +64,6 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
file: .github/workflows/Dockerfile
|
file: .github/workflows/Dockerfile
|
||||||
tags: easytier/easytier:${{ inputs.image_tag }}${{ inputs.mark_latest && ',easytier/easytier:latest' || '' }},
|
tags: |
|
||||||
|
easytier/easytier:${{ inputs.image_tag }}${{ inputs.mark_latest && ',easytier/easytier:latest' || '' }},
|
||||||
|
ghcr.io/easytier/easytier:${{ inputs.image_tag }}${{ inputs.mark_latest && ',easytier/easytier:latest' || '' }},
|
||||||
|
|||||||
12
.github/workflows/gui.yml
vendored
@@ -221,18 +221,6 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
./artifacts/*
|
./artifacts/*
|
||||||
|
|
||||||
- name: Upload OSS
|
|
||||||
if: ${{ env.OSS_BUCKET != '' }}
|
|
||||||
uses: Menci/upload-to-oss@main
|
|
||||||
with:
|
|
||||||
access-key-id: ${{ secrets.ALIYUN_OSS_ACCESS_ID }}
|
|
||||||
access-key-secret: ${{ secrets.ALIYUN_OSS_ACCESS_KEY }}
|
|
||||||
endpoint: ${{ secrets.ALIYUN_OSS_ENDPOINT }}
|
|
||||||
bucket: ${{ secrets.ALIYUN_OSS_BUCKET }}
|
|
||||||
local-path: ./artifacts/
|
|
||||||
remote-path: /easytier-releases/${{env.GIT_DESC}}/easytier-gui-${{ matrix.ARTIFACT_NAME }}
|
|
||||||
no-delete-remote-files: true
|
|
||||||
retry: 5
|
|
||||||
gui-result:
|
gui-result:
|
||||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
2
.github/workflows/install_rust.sh
vendored
@@ -36,7 +36,7 @@ if [[ $OS =~ ^ubuntu.*$ ]]; then
|
|||||||
|
|
||||||
if [ -n "$MUSL_URI" ]; then
|
if [ -n "$MUSL_URI" ]; then
|
||||||
mkdir -p ./musl_gcc
|
mkdir -p ./musl_gcc
|
||||||
wget -c https://musl.cc/${MUSL_URI}-cross.tgz -P ./musl_gcc/
|
wget --inet4-only -c https://musl.cc/${MUSL_URI}-cross.tgz -P ./musl_gcc/
|
||||||
tar zxf ./musl_gcc/${MUSL_URI}-cross.tgz -C ./musl_gcc/
|
tar zxf ./musl_gcc/${MUSL_URI}-cross.tgz -C ./musl_gcc/
|
||||||
sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/bin/*gcc /usr/bin/
|
sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/bin/*gcc /usr/bin/
|
||||||
sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/${MUSL_URI}/include/ /usr/include/musl-cross
|
sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/${MUSL_URI}/include/ /usr/include/musl-cross
|
||||||
|
|||||||
12
.github/workflows/mobile.yml
vendored
@@ -146,18 +146,6 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
./artifacts/*
|
./artifacts/*
|
||||||
|
|
||||||
- name: Upload OSS
|
|
||||||
if: ${{ env.OSS_BUCKET != '' }}
|
|
||||||
uses: Menci/upload-to-oss@main
|
|
||||||
with:
|
|
||||||
access-key-id: ${{ secrets.ALIYUN_OSS_ACCESS_ID }}
|
|
||||||
access-key-secret: ${{ secrets.ALIYUN_OSS_ACCESS_KEY }}
|
|
||||||
endpoint: ${{ secrets.ALIYUN_OSS_ENDPOINT }}
|
|
||||||
bucket: ${{ secrets.ALIYUN_OSS_BUCKET }}
|
|
||||||
local-path: ./artifacts/
|
|
||||||
remote-path: /easytier-releases/${{env.GIT_DESC}}/easytier-gui-${{ matrix.ARTIFACT_NAME }}
|
|
||||||
no-delete-remote-files: true
|
|
||||||
retry: 5
|
|
||||||
mobile-result:
|
mobile-result:
|
||||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: 'Version for this release'
|
description: 'Version for this release'
|
||||||
type: string
|
type: string
|
||||||
default: 'v2.2.1'
|
default: 'v2.2.4'
|
||||||
required: true
|
required: true
|
||||||
make_latest:
|
make_latest:
|
||||||
description: 'Mark this release as latest'
|
description: 'Mark this release as latest'
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
@@ -62,6 +62,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
sudo -E env "PATH=$PATH" cargo test --no-default-features --features=full --verbose
|
sudo -E env "PATH=$PATH" cargo test --no-default-features --features=full --verbose -- --test-threads=1 --nocapture
|
||||||
sudo chown -R $USER:$USER ./target
|
sudo chown -R $USER:$USER ./target
|
||||||
sudo chown -R $USER:$USER ~/.cargo
|
sudo chown -R $USER:$USER ~/.cargo
|
||||||
|
|||||||
941
Cargo.lock
generated
@@ -5,10 +5,11 @@
|
|||||||
[](https://github.com/EasyTier/EasyTier/issues)
|
[](https://github.com/EasyTier/EasyTier/issues)
|
||||||
[](https://github.com/EasyTier/EasyTier/actions/workflows/core.yml)
|
[](https://github.com/EasyTier/EasyTier/actions/workflows/core.yml)
|
||||||
[](https://github.com/EasyTier/EasyTier/actions/workflows/gui.yml)
|
[](https://github.com/EasyTier/EasyTier/actions/workflows/gui.yml)
|
||||||
|
[](https://github.com/EasyTier/EasyTier/actions/workflows/test.yml)
|
||||||
|
|
||||||
[简体中文](/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.**
|
**Please visit the [EasyTier Official Website](https://easytier.cn/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.
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ EasyTier is a simple, safe and decentralized VPN networking solution implemented
|
|||||||
|
|
||||||
4. **Install by Docker Compose**
|
4. **Install by Docker Compose**
|
||||||
|
|
||||||
Please visit the [EasyTier Official Website](https://www.easytier.cn/en/) to view the full documentation.
|
Please visit the [EasyTier Official Website](https://easytier.cn/en/) to view the full documentation.
|
||||||
|
|
||||||
5. **Install by script (For Linux Only)**
|
5. **Install by script (For Linux Only)**
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
[简体中文](/README_CN.md) | [English](/README.md)
|
[简体中文](/README_CN.md) | [English](/README.md)
|
||||||
|
|
||||||
**请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。**
|
**请访问 [EasyTier 官网](https://easytier.cn/) 以查看完整的文档。**
|
||||||
|
|
||||||
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
4. **通过Docker Compose安装**
|
4. **通过Docker Compose安装**
|
||||||
|
|
||||||
请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。
|
请访问 [EasyTier 官网](https://easytier.cn/) 以查看完整的文档。
|
||||||
|
|
||||||
5. **使用一键脚本安装 (仅适用于 Linux)**
|
5. **使用一键脚本安装 (仅适用于 Linux)**
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "easytier-gui",
|
"name": "easytier-gui",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.2.1",
|
"version": "2.2.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"eslint": "^9.12.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-format": "^0.1.2",
|
"eslint-plugin-format": "^0.1.2",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "=3.4.17",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"unplugin-auto-import": "^0.18.3",
|
"unplugin-auto-import": "^0.18.3",
|
||||||
"unplugin-vue-components": "^0.27.4",
|
"unplugin-vue-components": "^0.27.4",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "2.2.1"
|
version = "2.2.4"
|
||||||
description = "EasyTier GUI"
|
description = "EasyTier GUI"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
@@ -17,7 +17,7 @@
|
|||||||
"createUpdaterArtifacts": false
|
"createUpdaterArtifacts": false
|
||||||
},
|
},
|
||||||
"productName": "easytier-gui",
|
"productName": "easytier-gui",
|
||||||
"version": "2.2.1",
|
"version": "2.2.4",
|
||||||
"identifier": "com.kkrainbow.easytier",
|
"identifier": "com.kkrainbow.easytier",
|
||||||
"plugins": {},
|
"plugins": {},
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ async function main() {
|
|||||||
darkModeSelector: 'system',
|
darkModeSelector: 'system',
|
||||||
cssLayer: {
|
cssLayer: {
|
||||||
name: 'primevue',
|
name: 'primevue',
|
||||||
order: 'tailwind-base, primevue, tailwind-utilities'
|
order: 'tailwind-base, primevue, tailwind-utilities',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "easytier-web"
|
name = "easytier-web"
|
||||||
version = "2.2.1"
|
version = "2.2.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||||
|
|
||||||
@@ -24,7 +24,11 @@ tower-sessions = { version = "0.13.0", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
tower-http = { version = "0.6", features = ["cors", "compression-full"] }
|
tower-http = { version = "0.6", features = ["cors", "compression-full"] }
|
||||||
sqlx = { version = "0.8", features = ["sqlite"] }
|
sqlx = { version = "0.8", features = ["sqlite"] }
|
||||||
sea-orm = { version = "1.1", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ] }
|
sea-orm = { version = "1.1", features = [
|
||||||
|
"sqlx-sqlite",
|
||||||
|
"runtime-tokio-rustls",
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
sea-orm-migration = { version = "1.1" }
|
sea-orm-migration = { version = "1.1" }
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +36,7 @@ sea-orm-migration = { version = "1.1" }
|
|||||||
rust-embed = { version = "8.5.0", features = ["debug-embed"] }
|
rust-embed = { version = "8.5.0", features = ["debug-embed"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
image = {version="0.24", default-features = false, features = ["png"]}
|
image = { version = "0.24", default-features = false, features = ["png"] }
|
||||||
rusttype = "0.9.3"
|
rusttype = "0.9.3"
|
||||||
imageproc = "0.23.0"
|
imageproc = "0.23.0"
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@
|
|||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-nested": "^7.0.2",
|
"postcss-nested": "^7.0.2",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "=3.4.17",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-dts": "^4.3.0",
|
"vite-plugin-dts": "^4.3.0",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,6 +120,23 @@ function searchListenerSuggestions(e: { query: string }) {
|
|||||||
listenerSuggestions.value = ret
|
listenerSuggestions.value = ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const exitNodesSuggestions = ref([''])
|
||||||
|
|
||||||
|
function searchExitNodesSuggestions(e: { query: string }) {
|
||||||
|
const ret = []
|
||||||
|
ret.push(e.query)
|
||||||
|
exitNodesSuggestions.value = ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const whitelistSuggestions = ref([''])
|
||||||
|
|
||||||
|
function searchWhitelistSuggestions(e: { query: string }) {
|
||||||
|
const ret = []
|
||||||
|
ret.push(e.query)
|
||||||
|
whitelistSuggestions.value = ret
|
||||||
|
}
|
||||||
|
|
||||||
interface BoolFlag {
|
interface BoolFlag {
|
||||||
field: keyof NetworkConfig
|
field: keyof NetworkConfig
|
||||||
help: string
|
help: string
|
||||||
@@ -133,6 +150,11 @@ const bool_flags: BoolFlag[] = [
|
|||||||
{ field: 'disable_p2p', help: 'disable_p2p_help' },
|
{ field: 'disable_p2p', help: 'disable_p2p_help' },
|
||||||
{ field: 'bind_device', help: 'bind_device_help' },
|
{ field: 'bind_device', help: 'bind_device_help' },
|
||||||
{ field: 'no_tun', help: 'no_tun_help' },
|
{ field: 'no_tun', help: 'no_tun_help' },
|
||||||
|
{ field: 'enable_exit_node', help: 'enable_exit_node_help' },
|
||||||
|
{ field: 'relay_all_peer_rpc', help: 'relay_all_peer_rpc_help' },
|
||||||
|
{ field: 'multi_thread', help: 'multi_thread_help' },
|
||||||
|
{ field: 'proxy_forward_by_system', help: 'proxy_forward_by_system_help' },
|
||||||
|
{ field: 'disable_encryption', help: 'disable_encryption_help' },
|
||||||
]
|
]
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -141,7 +163,7 @@ const bool_flags: BoolFlag[] = [
|
|||||||
<div class="frontend-lib">
|
<div class="frontend-lib">
|
||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="w-10/12 self-center ">
|
<div class="w-11/12 self-center ">
|
||||||
<Panel :header="t('basic_settings')">
|
<Panel :header="t('basic_settings')">
|
||||||
<div class="flex flex-col gap-y-2">
|
<div class="flex flex-col gap-y-2">
|
||||||
<div class="flex flex-row gap-x-9 flex-wrap">
|
<div class="flex flex-row gap-x-9 flex-wrap">
|
||||||
@@ -209,7 +231,7 @@ const bool_flags: BoolFlag[] = [
|
|||||||
<label> {{ t('flags_switch') }} </label>
|
<label> {{ t('flags_switch') }} </label>
|
||||||
<div class="flex flex-row flex-wrap">
|
<div class="flex flex-row flex-wrap">
|
||||||
|
|
||||||
<div class="basis-64 flex" v-for="flag in bool_flags">
|
<div class="basis-[20rem] flex items-center" v-for="flag in bool_flags">
|
||||||
<Checkbox v-model="curNetwork[flag.field]" :input-id="flag.field" :binary="true" />
|
<Checkbox v-model="curNetwork[flag.field]" :input-id="flag.field" :binary="true" />
|
||||||
<label :for="flag.field" class="ml-2"> {{ t(flag.field) }} </label>
|
<label :for="flag.field" class="ml-2"> {{ t(flag.field) }} </label>
|
||||||
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t(flag.help)"></span>
|
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t(flag.help)"></span>
|
||||||
@@ -242,17 +264,20 @@ const bool_flags: BoolFlag[] = [
|
|||||||
<ToggleButton v-model="curNetwork.enable_vpn_portal" on-icon="pi pi-check" off-icon="pi pi-times"
|
<ToggleButton 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')" class="w-48" />
|
:on-label="t('off_text')" :off-label="t('on_text')" class="w-48" />
|
||||||
<div v-if="curNetwork.enable_vpn_portal" class="items-center flex flex-row gap-x-4">
|
<div v-if="curNetwork.enable_vpn_portal" class="items-center flex flex-row gap-x-4">
|
||||||
<div class="min-w-64">
|
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
||||||
<InputGroup>
|
<div class="flex flex-col gap-2 basis-8/12 grow">
|
||||||
<InputText v-model="curNetwork.vpn_portal_client_network_addr"
|
<InputGroup>
|
||||||
:placeholder="t('vpn_portal_client_network')" />
|
<InputText v-model="curNetwork.vpn_portal_client_network_addr"
|
||||||
<InputGroupAddon>
|
:placeholder="t('vpn_portal_client_network')" />
|
||||||
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
|
<InputGroupAddon>
|
||||||
</InputGroupAddon>
|
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
|
||||||
</InputGroup>
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
<InputNumber v-model="curNetwork.vpn_portal_listen_port" :allow-empty="false" :format="false"
|
</div>
|
||||||
:min="0" :max="65535" class="w-8/12" fluid />
|
<div class="flex flex-col gap-2 basis-3/12 grow">
|
||||||
|
<InputNumber v-model="curNetwork.vpn_portal_listen_port" :allow-empty="false" :format="false"
|
||||||
|
:min="0" :max="65535" fluid />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,6 +308,73 @@ const bool_flags: BoolFlag[] = [
|
|||||||
:placeholder="t('dev_name_placeholder')" />
|
:placeholder="t('dev_name_placeholder')" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-x-9 flex-wrap">
|
||||||
|
<div class="flex flex-col gap-2 basis-5/12 grow">
|
||||||
|
<div class="flex">
|
||||||
|
<label for="relay_network_whitelist">{{ t('relay_network_whitelist') }}</label>
|
||||||
|
<span class="pi pi-question-circle ml-2 self-center"
|
||||||
|
v-tooltip="t('relay_network_whitelist_help')"></span>
|
||||||
|
</div>
|
||||||
|
<ToggleButton v-model="curNetwork.enable_relay_network_whitelist" on-icon="pi pi-check" off-icon="pi pi-times"
|
||||||
|
:on-label="t('off_text')" :off-label="t('on_text')" class="w-48" />
|
||||||
|
<div v-if="curNetwork.enable_relay_network_whitelist" class="items-center flex flex-row gap-x-4">
|
||||||
|
<div class="min-w-64 w-full">
|
||||||
|
<AutoComplete id="relay_network_whitelist" v-model="curNetwork.relay_network_whitelist"
|
||||||
|
:placeholder="t('relay_network_whitelist')" class="w-full" multiple fluid
|
||||||
|
:suggestions="whitelistSuggestions" @complete="searchWhitelistSuggestions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-x-9 flex-wrap ">
|
||||||
|
<div class="flex flex-col gap-2 grow">
|
||||||
|
<div class="flex">
|
||||||
|
<label for="routes">{{ t('manual_routes') }}</label>
|
||||||
|
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t('manual_routes_help')"></span>
|
||||||
|
</div>
|
||||||
|
<ToggleButton v-model="curNetwork.enable_manual_routes" on-icon="pi pi-check" off-icon="pi pi-times"
|
||||||
|
:on-label="t('off_text')" :off-label="t('on_text')" class="w-48" />
|
||||||
|
<div v-if="curNetwork.enable_manual_routes" class="items-center flex flex-row gap-x-4">
|
||||||
|
<div class="min-w-64 w-full">
|
||||||
|
<AutoComplete id="routes" v-model="curNetwork.routes"
|
||||||
|
:placeholder="t('chips_placeholder', ['192.168.0.0/16'])" class="w-full" multiple fluid
|
||||||
|
:suggestions="inetSuggestions" @complete="searchInetSuggestions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-x-9 flex-wrap ">
|
||||||
|
<div class="flex flex-col gap-2 grow">
|
||||||
|
<div class="flex">
|
||||||
|
<label for="socks5_port">{{ t('socks5') }}</label>
|
||||||
|
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t('socks5_help')"></span>
|
||||||
|
</div>
|
||||||
|
<ToggleButton v-model="curNetwork.enable_socks5" on-icon="pi pi-check" off-icon="pi pi-times"
|
||||||
|
:on-label="t('off_text')" :off-label="t('on_text')" class="w-48" />
|
||||||
|
<div v-if="curNetwork.enable_socks5" class="items-center flex flex-row gap-x-4">
|
||||||
|
<div class="min-w-64 w-full">
|
||||||
|
<InputNumber id="socks5_port" v-model="curNetwork.socks5_port" aria-describedby="rpc_port-help"
|
||||||
|
:format="false" :allow-empty="false" :min="0" :max="65535" class="w-full"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-x-9 flex-wrap w-full">
|
||||||
|
<div class="flex flex-col gap-2 grow p-fluid">
|
||||||
|
<div class="flex">
|
||||||
|
<label for="exit_nodes">{{ t('exit_nodes') }}</label>
|
||||||
|
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t('exit_nodes_help')"></span>
|
||||||
|
</div>
|
||||||
|
<AutoComplete id="exit_nodes" v-model="curNetwork.exit_nodes"
|
||||||
|
:placeholder="t('chips_placeholder', ['192.168.8.8'])" class="w-full" multiple fluid
|
||||||
|
:suggestions="exitNodesSuggestions" @complete="searchExitNodesSuggestions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { NetworkInstance, type NodeInfo, type PeerRoutePair } from '../types/net
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import { ipv4InetToString, ipv4ToString, ipv6ToString } from '../modules/utils';
|
import { ipv4InetToString, ipv4ToString, ipv6ToString } from '../modules/utils';
|
||||||
import { DataTable, Column, Tag, Chip, Button, Dialog, ScrollPanel, Timeline, Divider, Card, } from 'primevue';
|
import { DataTable, Column, Tag, Chip, Button, Dialog, ScrollPanel, Timeline, Divider, Panel, } from 'primevue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
curNetworkInst: NetworkInstance | null,
|
curNetworkInst: NetworkInstance | null,
|
||||||
@@ -303,14 +303,15 @@ function showEventLogs() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="frontend-lib">
|
<div class="frontend-lib">
|
||||||
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto max-w-full">
|
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-full h-auto max-h-full"
|
||||||
|
:baseZIndex="2000">
|
||||||
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
||||||
<pre>{{ dialogContent }}</pre>
|
<pre>{{ dialogContent }}</pre>
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
<Timeline v-else :value="dialogContent">
|
<Timeline v-else :value="dialogContent">
|
||||||
<template #opposite="slotProps">
|
<template #opposite="slotProps">
|
||||||
<small class="text-surface-500 dark:text-surface-400">{{ useTimeAgo(Date.parse(slotProps.item.time))
|
<small class="text-surface-500 dark:text-surface-400">{{ useTimeAgo(Date.parse(slotProps.item.time))
|
||||||
}}</small>
|
}}</small>
|
||||||
</template>
|
</template>
|
||||||
<template #content="slotProps">
|
<template #content="slotProps">
|
||||||
<HumanEvent :event="slotProps.item.event" />
|
<HumanEvent :event="slotProps.item.event" />
|
||||||
@@ -318,107 +319,101 @@ function showEventLogs() {
|
|||||||
</Timeline>
|
</Timeline>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Card v-if="curNetworkInst?.error_msg">
|
<Panel v-if="curNetworkInst?.error_msg">
|
||||||
<template #title>
|
<template #header>
|
||||||
Run Network Error
|
Run Network Error
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<div class="flex flex-col gap-y-5">
|
||||||
<div class="flex flex-col gap-y-5">
|
<div class="text-red-500">
|
||||||
<div class="text-red-500">
|
{{ curNetworkInst.error_msg }}
|
||||||
{{ curNetworkInst.error_msg }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</Card>
|
</Panel>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Card>
|
<Panel>
|
||||||
<template #title>
|
<template #header>
|
||||||
{{ t('my_node_info') }}
|
{{ t('my_node_info') }}
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<div class="flex w-full flex-col gap-y-5">
|
||||||
<div class="flex w-full flex-col gap-y-5">
|
<div class="m-0 flex flex-row justify-center gap-x-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="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid green">
|
<div class="font-bold">
|
||||||
<div class="font-bold">
|
{{ t('peer_count') }}
|
||||||
{{ t('peer_count') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-5xl mt-1">
|
|
||||||
{{ peerCount }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-5xl mt-1">
|
||||||
<div class="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid purple">
|
{{ peerCount }}
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row items-center flex-wrap w-full max-h-40 overflow-scroll">
|
<div class="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid purple">
|
||||||
<Chip v-for="(chip, i) in myNodeInfoChips" :key="i" :label="chip.label" :icon="chip.icon"
|
<div class="font-bold">
|
||||||
class="mr-2 mt-2 text-sm" />
|
{{ t('upload') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xl mt-2">
|
||||||
|
{{ txRate }}/s
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="myNodeInfo" class="m-0 flex flex-row justify-center gap-x-5 text-sm">
|
<div class="rounded-full w-32 h-32 flex flex-col items-center pt-6" style="border: 1px solid fuchsia">
|
||||||
<Button severity="info" :label="t('show_vpn_portal_config')" @click="showVpnPortalConfig" />
|
<div class="font-bold">
|
||||||
<Button severity="info" :label="t('show_event_log')" @click="showEventLogs" />
|
{{ t('download') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xl mt-2">
|
||||||
|
{{ rxRate }}/s
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</Card>
|
<div 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>
|
||||||
|
|
||||||
|
<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_event_log')" @click="showEventLogs" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Card>
|
<Panel>
|
||||||
<template #title>
|
<template #header>
|
||||||
{{ t('peer_info') }}
|
{{ t('peer_info') }}
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<DataTable :value="peerRouteInfos" column-resize-mode="fit" table-class="w-full">
|
||||||
<DataTable :value="peerRouteInfos" column-resize-mode="fit" table-class="w-full">
|
<Column :field="ipFormat" :header="t('virtual_ipv4')" />
|
||||||
<Column :field="ipFormat" :header="t('virtual_ipv4')" />
|
<Column :header="t('hostname')">
|
||||||
<Column :header="t('hostname')">
|
<template #body="slotProps">
|
||||||
<template #body="slotProps">
|
<div v-if="!slotProps.data.route.cost || !slotProps.data.route.feature_flag.is_public_server"
|
||||||
<div v-if="!slotProps.data.route.cost || !slotProps.data.route.feature_flag.is_public_server"
|
v-tooltip="slotProps.data.route.hostname">
|
||||||
v-tooltip="slotProps.data.route.hostname">
|
{{
|
||||||
{{
|
slotProps.data.route.hostname }}
|
||||||
slotProps.data.route.hostname }}
|
</div>
|
||||||
</div>
|
<div v-else v-tooltip="slotProps.data.route.hostname" class="space-x-1">
|
||||||
<div v-else v-tooltip="slotProps.data.route.hostname" class="space-x-1">
|
<Tag v-if="slotProps.data.route.feature_flag.is_public_server" severity="info" value="Info">
|
||||||
<Tag v-if="slotProps.data.route.feature_flag.is_public_server" severity="info" value="Info">
|
{{ t('status.server') }}
|
||||||
{{ t('status.server') }}
|
</Tag>
|
||||||
</Tag>
|
<Tag v-if="slotProps.data.route.feature_flag.avoid_relay_data" severity="warn" value="Warn">
|
||||||
<Tag v-if="slotProps.data.route.feature_flag.avoid_relay_data" severity="warn" value="Warn">
|
{{ t('status.relay') }}
|
||||||
{{ t('status.relay') }}
|
</Tag>
|
||||||
</Tag>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</Column>
|
||||||
</Column>
|
<Column :field="routeCost" :header="t('route_cost')" />
|
||||||
<Column :field="routeCost" :header="t('route_cost')" />
|
<Column :field="latencyMs" :header="t('latency')" />
|
||||||
<Column :field="latencyMs" :header="t('latency')" />
|
<Column :field="txBytes" :header="t('upload_bytes')" />
|
||||||
<Column :field="txBytes" :header="t('upload_bytes')" />
|
<Column :field="rxBytes" :header="t('download_bytes')" />
|
||||||
<Column :field="rxBytes" :header="t('download_bytes')" />
|
<Column :field="lossRate" :header="t('loss_rate')" />
|
||||||
<Column :field="lossRate" :header="t('loss_rate')" />
|
<Column :header="t('status.version')">
|
||||||
<Column :header="t('status.version')">
|
<template #body="slotProps">
|
||||||
<template #body="slotProps">
|
<span>{{ version(slotProps.data) }}</span>
|
||||||
<span>{{ version(slotProps.data) }}</span>
|
</template>
|
||||||
</template>
|
</Column>
|
||||||
</Column>
|
</DataTable>
|
||||||
</DataTable>
|
</Panel>
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -75,13 +75,13 @@ latency_first: 开启延迟优先模式
|
|||||||
latency_first_help: 忽略中转跳数,选择总延迟最低的路径
|
latency_first_help: 忽略中转跳数,选择总延迟最低的路径
|
||||||
|
|
||||||
use_smoltcp: 使用用户态协议栈
|
use_smoltcp: 使用用户态协议栈
|
||||||
use_smoltcp_help: 使用用户态 TCP/IP 协议栈,避免操作系统防火墙问题导致无法子网代理。
|
use_smoltcp_help: 使用用户态 TCP/IP 协议栈,避免操作系统防火墙问题导致无法子网代理 / KCP代理。
|
||||||
|
|
||||||
enable_kcp_proxy: 启用 KCP 代理
|
enable_kcp_proxy: 启用 KCP 代理
|
||||||
enable_kcp_proxy_help: 将 TCP 流量转为 KCP 流量,降低传输延迟,提升传输速度。
|
enable_kcp_proxy_help: 将 TCP 流量转为 KCP 流量,降低传输延迟,提升传输速度。
|
||||||
|
|
||||||
disable_kcp_input: 禁用 KCP 输入
|
disable_kcp_input: 禁用 KCP 输入
|
||||||
disable_kcp_input_help: 禁用 KCP 入站流量,其他开启 KCP 代理的节点无法连接到本节点。
|
disable_kcp_input_help: 禁用 KCP 入站流量,其他开启 KCP 代理的节点仍然使用 TCP 连接到本节点。
|
||||||
|
|
||||||
disable_p2p: 禁用 P2P
|
disable_p2p: 禁用 P2P
|
||||||
disable_p2p_help: 禁用 P2P 模式,所有流量通过手动指定的服务器中转。
|
disable_p2p_help: 禁用 P2P 模式,所有流量通过手动指定的服务器中转。
|
||||||
@@ -92,6 +92,39 @@ bind_device_help: 仅使用物理网卡,避免 EasyTier 通过其他虚拟网
|
|||||||
no_tun: 无 TUN 模式
|
no_tun: 无 TUN 模式
|
||||||
no_tun_help: 不使用 TUN 网卡,适合无管理员权限时使用。本节点仅允许被访问。访问其他节点需要使用 SOCK5
|
no_tun_help: 不使用 TUN 网卡,适合无管理员权限时使用。本节点仅允许被访问。访问其他节点需要使用 SOCK5
|
||||||
|
|
||||||
|
enable_exit_node: 启用出口节点
|
||||||
|
enable_exit_node_help: 允许此节点成为出口节点
|
||||||
|
|
||||||
|
relay_all_peer_rpc: 转发RPC包
|
||||||
|
relay_all_peer_rpc_help: |
|
||||||
|
允许转发所有对等节点的RPC数据包,即使对等节点不在转发网络白名单中。
|
||||||
|
这可以帮助白名单外网络中的对等节点建立P2P连接。
|
||||||
|
|
||||||
|
multi_thread: 启用多线程
|
||||||
|
multi_thread_help: 使用多线程运行时
|
||||||
|
|
||||||
|
proxy_forward_by_system: 系统转发
|
||||||
|
proxy_forward_by_system_help: 通过系统内核转发子网代理数据包,禁用内置NAT
|
||||||
|
|
||||||
|
disable_encryption: 禁用加密
|
||||||
|
disable_encryption_help: 禁用对等节点通信的加密,默认为false,必须与对等节点相同
|
||||||
|
|
||||||
|
relay_network_whitelist: 网络白名单
|
||||||
|
relay_network_whitelist_help: |
|
||||||
|
仅转发白名单网络的流量,支持通配符字符串。多个网络名称间可以使用英文空格间隔。
|
||||||
|
如果该参数为空,则禁用转发。默认允许所有网络。
|
||||||
|
例如:'*'(所有网络),'def*'(以def为前缀的网络),'net1 net2'(只允许net1和net2)
|
||||||
|
|
||||||
|
manual_routes: 自定义路由
|
||||||
|
manual_routes_help: 手动分配路由CIDR,将禁用子网代理和从对等节点传播的wireguard路由。例如:192.168.0.0/16
|
||||||
|
|
||||||
|
socks5: socks5服务器
|
||||||
|
socks5_help: |
|
||||||
|
启用 socks5 服务器,允许 socks5 客户端访问虚拟网络. 格式: <端口>,例如:1080
|
||||||
|
|
||||||
|
exit_nodes: 出口节点列表
|
||||||
|
exit_nodes_help: 转发所有流量的出口节点,虚拟IPv4地址,优先级由列表顺序决定
|
||||||
|
|
||||||
status:
|
status:
|
||||||
version: 内核版本
|
version: 内核版本
|
||||||
local: 本机
|
local: 本机
|
||||||
|
|||||||
@@ -68,6 +68,63 @@ upload_bytes: Upload
|
|||||||
download_bytes: Download
|
download_bytes: Download
|
||||||
loss_rate: Loss Rate
|
loss_rate: Loss Rate
|
||||||
|
|
||||||
|
flags_switch: Feature Switch
|
||||||
|
|
||||||
|
latency_first: Enable Latency-First Mode
|
||||||
|
latency_first_help: Ignore hop count and select the path with the lowest total latency
|
||||||
|
|
||||||
|
use_smoltcp: Use User-Space Protocol Stack
|
||||||
|
use_smoltcp_help: Use a user-space TCP/IP stack to avoid issues with operating system firewalls blocking subnet or KCP proxy functionality.
|
||||||
|
|
||||||
|
enable_kcp_proxy: Enable KCP Proxy
|
||||||
|
enable_kcp_proxy_help: Convert TCP traffic to KCP traffic to reduce latency and boost transmission speed.
|
||||||
|
|
||||||
|
disable_kcp_input: Disable KCP Input
|
||||||
|
disable_kcp_input_help: Disable inbound KCP traffic, while nodes with KCP proxy enabled continue to connect using TCP.
|
||||||
|
|
||||||
|
disable_p2p: Disable P2P
|
||||||
|
disable_p2p_help: Disable P2P mode; route all traffic through a manually specified relay server.
|
||||||
|
|
||||||
|
bind_device: Bind to Physical Device Only
|
||||||
|
bind_device_help: Use only the physical network interface to prevent EasyTier from connecting via virtual networks.
|
||||||
|
|
||||||
|
no_tun: No TUN Mode
|
||||||
|
no_tun_help: Do not use a TUN interface, suitable for environments without administrator privileges. This node is only accessible; accessing other nodes requires SOCKS5.
|
||||||
|
|
||||||
|
enable_exit_node: Enable Exit Node
|
||||||
|
enable_exit_node_help: Allow this node to be an exit node
|
||||||
|
|
||||||
|
relay_all_peer_rpc: Relay RPC Packets
|
||||||
|
relay_all_peer_rpc_help: |
|
||||||
|
Relay all peer rpc packets, even if the peer is not in the relay network whitelist.
|
||||||
|
This can help peers not in relay network whitelist to establish p2p connection.
|
||||||
|
|
||||||
|
multi_thread: Multi Thread
|
||||||
|
multi_thread_help: Use multi-thread runtime
|
||||||
|
|
||||||
|
proxy_forward_by_system: System Forward
|
||||||
|
proxy_forward_by_system_help: Forward packet to proxy networks via system kernel, disable internal nat for network proxy
|
||||||
|
|
||||||
|
disable_encryption: Disable Encryption
|
||||||
|
disable_encryption_help: Disable encryption for peers communication, default is false, must be same with peers
|
||||||
|
|
||||||
|
relay_network_whitelist: Network Whitelist
|
||||||
|
relay_network_whitelist_help: |
|
||||||
|
Only forward traffic from the whitelist networks, supporting wildcard strings, multiple network names can be separated by spaces.
|
||||||
|
If this parameter is empty, forwarding is disabled. By default, all networks are allowed.
|
||||||
|
e.g.: '*' (all networks), 'def*' (networks with the prefix 'def'), 'net1 net2' (only allow net1 and net2)
|
||||||
|
|
||||||
|
manual_routes: Manual Route
|
||||||
|
manual_routes_help: |
|
||||||
|
Assign routes cidr manually, will disable subnet proxy and wireguard routes propagated from peers. e.g.:192.168.0.0/16
|
||||||
|
|
||||||
|
socks5: Socks5 Server
|
||||||
|
socks5_help: |
|
||||||
|
Enable socks5 server, allow socks5 client to access virtual network. format: <port>, e.g.: 1080
|
||||||
|
|
||||||
|
exit_nodes: Exit Nodes
|
||||||
|
exit_nodes_help: Exit nodes to forward all traffic to, a virtual ipv4 address, priority is determined by the order of the list
|
||||||
|
|
||||||
status:
|
status:
|
||||||
version: Version
|
version: Version
|
||||||
local: Local
|
local: Local
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||||
import { Md5 } from 'ts-md5'
|
import { Md5 } from 'ts-md5'
|
||||||
import { UUID } from './utils';
|
import { UUID } from './utils';
|
||||||
|
import { NetworkConfig } from '../types/network';
|
||||||
|
|
||||||
export interface ValidateConfigResponse {
|
export interface ValidateConfigResponse {
|
||||||
toml_config: string;
|
toml_config: string;
|
||||||
@@ -37,6 +38,15 @@ export interface ListNetworkInstanceIdResponse {
|
|||||||
disabled_inst_ids: Array<UUID>,
|
disabled_inst_ids: Array<UUID>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GenerateConfigRequest {
|
||||||
|
config: NetworkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateConfigResponse {
|
||||||
|
toml_config?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ApiClient {
|
export class ApiClient {
|
||||||
private client: AxiosInstance;
|
private client: AxiosInstance;
|
||||||
private authFailedCb: Function | undefined;
|
private authFailedCb: Function | undefined;
|
||||||
@@ -193,6 +203,18 @@ export class ApiClient {
|
|||||||
public captcha_url() {
|
public captcha_url() {
|
||||||
return this.client.defaults.baseURL + '/auth/captcha';
|
return this.client.defaults.baseURL + '/auth/captcha';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async generate_config(config: GenerateConfigRequest): Promise<GenerateConfigResponse> {
|
||||||
|
try {
|
||||||
|
const response = await this.client.post<any, GenerateConfigResponse>('/generate-config', config);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
return { error: error.response?.data };
|
||||||
|
}
|
||||||
|
return { error: 'Unknown error: ' + error };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ApiClient;
|
export default ApiClient;
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
@import 'primeicons/primeicons.css';
|
@import 'primeicons/primeicons.css';
|
||||||
@import 'floating-vue/dist/style.css';
|
@import 'floating-vue/dist/style.css';
|
||||||
|
|
||||||
.frontend-lib {
|
|
||||||
|
|
||||||
@layer tailwind-base, primevue, tailwind-utilities;
|
@layer tailwind-base, primevue, tailwind-utilities;
|
||||||
|
|
||||||
@layer tailwind-base {
|
@layer tailwind-base {
|
||||||
@@ -51,4 +49,6 @@
|
|||||||
background-color: #0000005d;
|
background-color: #0000005d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v-popper__inner {
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,22 @@ export interface NetworkConfig {
|
|||||||
disable_p2p?: boolean
|
disable_p2p?: boolean
|
||||||
bind_device?: boolean
|
bind_device?: boolean
|
||||||
no_tun?: boolean
|
no_tun?: boolean
|
||||||
|
enable_exit_node?: boolean
|
||||||
|
relay_all_peer_rpc?: boolean
|
||||||
|
multi_thread?: boolean
|
||||||
|
proxy_forward_by_system?: boolean
|
||||||
|
disable_encryption?: boolean
|
||||||
|
|
||||||
|
enable_relay_network_whitelist?: boolean
|
||||||
|
relay_network_whitelist: string[]
|
||||||
|
|
||||||
|
enable_manual_routes: boolean
|
||||||
|
routes: string[]
|
||||||
|
|
||||||
|
exit_nodes: string[]
|
||||||
|
|
||||||
|
enable_socks5?: boolean
|
||||||
|
socks5_port: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||||
@@ -83,6 +99,18 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
|||||||
disable_p2p: false,
|
disable_p2p: false,
|
||||||
bind_device: true,
|
bind_device: true,
|
||||||
no_tun: false,
|
no_tun: false,
|
||||||
|
enable_exit_node: false,
|
||||||
|
relay_all_peer_rpc: false,
|
||||||
|
multi_thread: true,
|
||||||
|
proxy_forward_by_system: false,
|
||||||
|
disable_encryption: false,
|
||||||
|
enable_relay_network_whitelist: false,
|
||||||
|
relay_network_whitelist: [],
|
||||||
|
enable_manual_routes: false,
|
||||||
|
routes: [],
|
||||||
|
exit_nodes: [],
|
||||||
|
enable_socks5: false,
|
||||||
|
socks5_port: 1080,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "=3.4.17",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.4.10",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-singlefile": "^2.0.3",
|
"vite-plugin-singlefile": "^2.0.3",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
39
easytier-web/frontend/src/components/ConfigGenerator.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NetworkTypes } from 'easytier-frontend-lib';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Api } from 'easytier-frontend-lib'
|
||||||
|
|
||||||
|
const defaultApiHost = 'https://config-server.easytier.cn'
|
||||||
|
const api = new Api.ApiClient(defaultApiHost);
|
||||||
|
|
||||||
|
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
||||||
|
const toml_config = ref<string>("Press 'Run Network' to generate TOML configuration");
|
||||||
|
|
||||||
|
const generateConfig = (config: NetworkTypes.NetworkConfig) => {
|
||||||
|
api.generate_config({
|
||||||
|
config: config
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.error) {
|
||||||
|
toml_config.value = res.error;
|
||||||
|
} else if (res.toml_config) {
|
||||||
|
toml_config.value = res.toml_config;
|
||||||
|
} else {
|
||||||
|
toml_config.value = "Api server returned an unexpected response";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
</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">
|
||||||
|
<Config :cur-network="newNetworkConfig" @run-network="generateConfig" />
|
||||||
|
</div>
|
||||||
|
<div class="sm:w-full md:w-1/2 p-4 bg-gray-100">
|
||||||
|
<pre class="whitespace-pre-wrap">{{ toml_config }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -27,7 +27,7 @@ const loadDevices = async () => {
|
|||||||
public_ip: device.client_url,
|
public_ip: device.client_url,
|
||||||
running_network_instances: device.info?.running_network_instances.map((instance: any) => Utils.UuidToStr(instance)),
|
running_network_instances: device.info?.running_network_instances.map((instance: any) => Utils.UuidToStr(instance)),
|
||||||
running_network_count: device.info?.running_network_instances.length,
|
running_network_count: device.info?.running_network_instances.length,
|
||||||
report_time: device.info?.report_time,
|
report_time: new Date(device.info?.report_time).toLocaleString(),
|
||||||
easytier_version: device.info?.easytier_version,
|
easytier_version: device.info?.easytier_version,
|
||||||
machine_id: Utils.UuidToStr(device.info?.machine_id),
|
machine_id: Utils.UuidToStr(device.info?.machine_id),
|
||||||
});
|
});
|
||||||
@@ -102,7 +102,7 @@ const selectedDeviceHostname = computed<string | undefined>(() => {
|
|||||||
</DataTable>
|
</DataTable>
|
||||||
|
|
||||||
<Drawer v-model:visible="deviceManageVisible" :header="`Manage ${selectedDeviceHostname}`" position="right"
|
<Drawer v-model:visible="deviceManageVisible" :header="`Manage ${selectedDeviceHostname}`" position="right"
|
||||||
class="w-1/2 min-w-96">
|
:baseZIndex=1000 class="w-3/5 min-w-96">
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<component :is="Component" :api="api" :deviceList="deviceList" @update="loadDevices" />
|
<component :is="Component" :api="api" :deviceList="deviceList" @update="loadDevices" />
|
||||||
</RouterView>
|
</RouterView>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast } from 'primevue';
|
import {Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast, Divider} from 'primevue';
|
||||||
import { NetworkTypes, Status, Utils, Api, } from 'easytier-frontend-lib';
|
import { NetworkTypes, Status, Utils, Api, } from 'easytier-frontend-lib';
|
||||||
import { watch, computed, onMounted, onUnmounted, ref } from 'vue';
|
import { watch, computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
@@ -27,6 +27,8 @@ const deviceInfo = computed<Utils.DeviceInfo | undefined | null>(() => {
|
|||||||
return deviceId.value ? props.deviceList?.find((device) => device.machine_id === deviceId.value) : null;
|
return deviceId.value ? props.deviceList?.find((device) => device.machine_id === deviceId.value) : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const configFile = ref();
|
||||||
|
|
||||||
const curNetworkInfo = ref<NetworkTypes.NetworkInstance | null>(null);
|
const curNetworkInfo = ref<NetworkTypes.NetworkInstance | null>(null);
|
||||||
|
|
||||||
const isEditing = ref(false);
|
const isEditing = ref(false);
|
||||||
@@ -207,6 +209,65 @@ const loadDeviceInfo = async () => {
|
|||||||
} as NetworkTypes.NetworkInstance;
|
} as NetworkTypes.NetworkInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exportConfig = async () => {
|
||||||
|
if (!deviceId.value || !instanceId.value) {
|
||||||
|
toast.add({ severity: 'error', summary: 'Error', detail: 'No network instance selected', life: 2000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let ret = await props.api?.get_network_config(deviceId.value, instanceId.value);
|
||||||
|
delete ret.instance_id;
|
||||||
|
exportJsonFile(JSON.stringify(ret, null, 2),instanceId.value +'.json');
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to export network config, error: ' + JSON.stringify(e.response.data), life: 2000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const importConfig = () => {
|
||||||
|
configFile.value.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileUpload = (event: Event) => {
|
||||||
|
const files = (event.target as HTMLInputElement).files;
|
||||||
|
const file = files ? files[0] : null;
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
let str = e.target?.result?.toString();
|
||||||
|
if(str){
|
||||||
|
const config = JSON.parse(str);
|
||||||
|
if(config === null || typeof config !== "object"){
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
Object.assign(newNetworkConfig.value, config);
|
||||||
|
toast.add({ severity: 'success', summary: 'Import Success', detail: "Config file import success", life: 2000 });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({ severity: 'error', summary: 'Error', detail: 'Config file parse error.', life: 2000 });
|
||||||
|
}
|
||||||
|
configFile.value.value = null;
|
||||||
|
}
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportJsonFile = (context: string, name: string) => {
|
||||||
|
let url = window.URL.createObjectURL(new Blob([context], { type: 'application/json' }));
|
||||||
|
let link = document.createElement('a');
|
||||||
|
link.style.display = 'none';
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', name);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
let periodFunc = new Utils.PeriodicTask(async () => {
|
let periodFunc = new Utils.PeriodicTask(async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([loadNetworkInstanceIds(), loadDeviceInfo()]);
|
await Promise.all([loadNetworkInstanceIds(), loadDeviceInfo()]);
|
||||||
@@ -226,9 +287,16 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<input type="file" @change="handleFileUpload" class="hidden" accept="application/json" ref="configFile"/>
|
||||||
<ConfirmPopup></ConfirmPopup>
|
<ConfirmPopup></ConfirmPopup>
|
||||||
<Dialog v-model:visible="showCreateNetworkDialog" modal :header="!isEditing ? 'Create New Network' : 'Edit Network'"
|
<Dialog v-model:visible="showCreateNetworkDialog" modal :header="!isEditing ? 'Create New Network' : 'Edit Network'"
|
||||||
:style="{ width: '55rem' }">
|
:style="{ width: '55rem' }">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="w-11/12 self-center ">
|
||||||
|
<Button @click="importConfig" icon="pi pi-file-import" label="Import" iconPos="right" />
|
||||||
|
<Divider />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Config :cur-network="newNetworkConfig" @run-network="createNewNetwork"></Config>
|
<Config :cur-network="newNetworkConfig" @run-network="createNewNetwork"></Config>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -245,19 +313,23 @@ onUnmounted(() => {
|
|||||||
<div class="gap-x-3 flex">
|
<div class="gap-x-3 flex">
|
||||||
<Button @click="confirmDeleteNetwork($event)" icon="pi pi-minus" severity="danger" label="Delete"
|
<Button @click="confirmDeleteNetwork($event)" icon="pi pi-minus" severity="danger" label="Delete"
|
||||||
iconPos="right" />
|
iconPos="right" />
|
||||||
|
<Button @click="exportConfig" icon="pi pi-file-export" severity="help" label="Export" iconPos="right" />
|
||||||
<Button @click="editNetwork" icon="pi pi-pen-to-square" label="Edit" iconPos="right" severity="info" />
|
<Button @click="editNetwork" icon="pi pi-pen-to-square" label="Edit" iconPos="right" severity="info" />
|
||||||
<Button @click="newNetwork" icon="pi pi-plus" label="Create" iconPos="right" />
|
<Button @click="newNetwork" icon="pi pi-plus" label="Create" iconPos="right" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
<!-- For running network, show the status -->
|
<!-- For running network, show the status -->
|
||||||
<div v-if="needShowNetworkStatus">
|
<div v-if="needShowNetworkStatus">
|
||||||
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="needShowNetworkStatus">
|
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="needShowNetworkStatus">
|
||||||
</Status>
|
</Status>
|
||||||
<center>
|
<Divider />
|
||||||
<Button @click="updateNetworkState(true)" label="Disable Network" severity="warn" />
|
<div class="text-center">
|
||||||
</center>
|
<Button @click="updateNetworkState(true)" label="Disable Network" severity="warn" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- For disabled network, show the config -->
|
<!-- For disabled network, show the config -->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './style.css'
|
|
||||||
import 'easytier-frontend-lib/style.css'
|
import 'easytier-frontend-lib/style.css'
|
||||||
|
import './style.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import EasytierFrontendLib from 'easytier-frontend-lib'
|
import EasytierFrontendLib from 'easytier-frontend-lib'
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config'
|
||||||
@@ -15,6 +15,7 @@ import DeviceManagement from './components/DeviceManagement.vue'
|
|||||||
import Dashboard from './components/Dashboard.vue'
|
import Dashboard from './components/Dashboard.vue'
|
||||||
import DialogService from 'primevue/dialogservice';
|
import DialogService from 'primevue/dialogservice';
|
||||||
import ToastService from 'primevue/toastservice';
|
import ToastService from 'primevue/toastservice';
|
||||||
|
import ConfigGenerator from './components/ConfigGenerator.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -66,6 +67,10 @@ const routes = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/config_generator',
|
||||||
|
component: ConfigGenerator,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ use easytier::{
|
|||||||
common::{
|
common::{
|
||||||
config::{ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, TomlConfigLoader},
|
config::{ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, TomlConfigLoader},
|
||||||
constants::EASYTIER_VERSION,
|
constants::EASYTIER_VERSION,
|
||||||
|
error::Error,
|
||||||
},
|
},
|
||||||
tunnel::udp::UdpTunnelListener,
|
tunnel::{tcp::TcpTunnelListener, udp::UdpTunnelListener, TunnelListener},
|
||||||
utils::{init_logger, setup_panic_handler},
|
utils::{init_logger, setup_panic_handler},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,6 +72,18 @@ struct Cli {
|
|||||||
api_server_port: u16,
|
api_server_port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_listener_by_url(
|
||||||
|
l: &url::Url,
|
||||||
|
) -> Result<Box<dyn TunnelListener>, Error> {
|
||||||
|
Ok(match l.scheme() {
|
||||||
|
"tcp" => Box::new(TcpTunnelListener::new(l.clone())),
|
||||||
|
"udp" => Box::new(UdpTunnelListener::new(l.clone())),
|
||||||
|
_ => {
|
||||||
|
return Err(Error::InvalidUrl(l.to_string()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||||
@@ -92,14 +105,10 @@ async fn main() {
|
|||||||
// let db = db::Db::new(":memory:").await.unwrap();
|
// let db = db::Db::new(":memory:").await.unwrap();
|
||||||
let db = db::Db::new(cli.db).await.unwrap();
|
let db = db::Db::new(cli.db).await.unwrap();
|
||||||
|
|
||||||
let listener = UdpTunnelListener::new(
|
let listener = get_listener_by_url(
|
||||||
format!(
|
&format!("{}://0.0.0.0:{}", cli.config_server_protocol, cli.config_server_port).parse().unwrap(),
|
||||||
"{}://0.0.0.0:{}",
|
)
|
||||||
cli.config_server_protocol, cli.config_server_port
|
.unwrap();
|
||||||
)
|
|
||||||
.parse()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
let mut mgr = client_manager::ClientManager::new(db.clone());
|
let mut mgr = client_manager::ClientManager::new(db.clone());
|
||||||
mgr.serve(listener).await.unwrap();
|
mgr.serve(listener).await.unwrap();
|
||||||
let mgr = Arc::new(mgr);
|
let mgr = Arc::new(mgr);
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ mod users;
|
|||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
use axum::routing::post;
|
||||||
use axum::{extract::State, routing::get, Json, Router};
|
use axum::{extract::State, routing::get, Json, Router};
|
||||||
use axum_login::tower_sessions::{ExpiredDeletion, SessionManagerLayer};
|
use axum_login::tower_sessions::{ExpiredDeletion, SessionManagerLayer};
|
||||||
use axum_login::{login_required, AuthManagerLayerBuilder, AuthzBackend};
|
use axum_login::{login_required, AuthManagerLayerBuilder, AuthzBackend};
|
||||||
use axum_messages::MessagesManagerLayer;
|
use axum_messages::MessagesManagerLayer;
|
||||||
|
use easytier::common::config::ConfigLoader;
|
||||||
use easytier::common::scoped_task::ScopedTask;
|
use easytier::common::scoped_task::ScopedTask;
|
||||||
|
use easytier::launcher::NetworkConfig;
|
||||||
use easytier::proto::rpc_types;
|
use easytier::proto::rpc_types;
|
||||||
use network::NetworkApi;
|
use network::NetworkApi;
|
||||||
use sea_orm::DbErr;
|
use sea_orm::DbErr;
|
||||||
@@ -48,6 +51,17 @@ struct GetSummaryJsonResp {
|
|||||||
device_count: u32,
|
device_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct GenerateConfigRequest {
|
||||||
|
config: NetworkConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct GenerateConfigResponse {
|
||||||
|
error: Option<String>,
|
||||||
|
toml_config: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
message: String,
|
message: String,
|
||||||
@@ -131,6 +145,24 @@ impl RestfulServer {
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_generate_config(
|
||||||
|
Json(req): Json<GenerateConfigRequest>,
|
||||||
|
) -> Result<Json<GenerateConfigResponse>, HttpHandleError> {
|
||||||
|
let config = req.config.gen_config();
|
||||||
|
match config {
|
||||||
|
Ok(c) => Ok(GenerateConfigResponse {
|
||||||
|
error: None,
|
||||||
|
toml_config: Some(c.dump()),
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
|
Err(e) => Ok(GenerateConfigResponse {
|
||||||
|
error: Some(format!("{:?}", e)),
|
||||||
|
toml_config: None,
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start(&mut self) -> Result<(), anyhow::Error> {
|
pub async fn start(&mut self) -> Result<(), anyhow::Error> {
|
||||||
let listener = TcpListener::bind(self.bind_addr).await?;
|
let listener = TcpListener::bind(self.bind_addr).await?;
|
||||||
|
|
||||||
@@ -178,6 +210,10 @@ impl RestfulServer {
|
|||||||
.route_layer(login_required!(Backend))
|
.route_layer(login_required!(Backend))
|
||||||
.merge(auth::router())
|
.merge(auth::router())
|
||||||
.with_state(self.client_mgr.clone())
|
.with_state(self.client_mgr.clone())
|
||||||
|
.route(
|
||||||
|
"/api/v1/generate-config",
|
||||||
|
post(Self::handle_generate_config),
|
||||||
|
)
|
||||||
.layer(MessagesManagerLayer)
|
.layer(MessagesManagerLayer)
|
||||||
.layer(auth_layer)
|
.layer(auth_layer)
|
||||||
.layer(tower_http::cors::CorsLayer::very_permissive())
|
.layer(tower_http::cors::CorsLayer::very_permissive())
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "easytier"
|
|||||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||||
homepage = "https://github.com/EasyTier/EasyTier"
|
homepage = "https://github.com/EasyTier/EasyTier"
|
||||||
repository = "https://github.com/EasyTier/EasyTier"
|
repository = "https://github.com/EasyTier/EasyTier"
|
||||||
version = "2.2.1"
|
version = "2.2.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["kkrainbow"]
|
authors = ["kkrainbow"]
|
||||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||||
@@ -62,7 +62,6 @@ timedmap = "=1.0.1"
|
|||||||
zerocopy = { version = "0.7.32", features = ["derive", "simd"] }
|
zerocopy = { version = "0.7.32", features = ["derive", "simd"] }
|
||||||
bytes = "1.5.0"
|
bytes = "1.5.0"
|
||||||
pin-project-lite = "0.2.13"
|
pin-project-lite = "0.2.13"
|
||||||
atomicbox = "0.4.0"
|
|
||||||
tachyonix = "0.3.0"
|
tachyonix = "0.3.0"
|
||||||
|
|
||||||
quinn = { version = "0.11.0", optional = true, features = ["ring"] }
|
quinn = { version = "0.11.0", optional = true, features = ["ring"] }
|
||||||
@@ -99,7 +98,6 @@ uuid = { version = "1.5.0", features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
# for ring tunnel
|
# for ring tunnel
|
||||||
crossbeam-queue = "0.3"
|
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
|
|
||||||
# for rpc
|
# for rpc
|
||||||
@@ -126,7 +124,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
pnet = { version = "0.35.0", features = ["serde"] }
|
pnet = { version = "0.35.0", features = ["serde"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
clap = { version = "4.4.8", features = [
|
clap = { version = "4.5.30", features = [
|
||||||
"string",
|
"string",
|
||||||
"unicode",
|
"unicode",
|
||||||
"derive",
|
"derive",
|
||||||
@@ -138,7 +136,7 @@ async-recursion = "1.0.5"
|
|||||||
network-interface = "2.0"
|
network-interface = "2.0"
|
||||||
|
|
||||||
# for ospf route
|
# for ospf route
|
||||||
petgraph = "0.6.5"
|
petgraph = "0.7.1"
|
||||||
|
|
||||||
# for wireguard
|
# for wireguard
|
||||||
boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true }
|
boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true }
|
||||||
@@ -154,13 +152,9 @@ humansize = "2.1.3"
|
|||||||
|
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
|
||||||
derivative = "2.2.0"
|
|
||||||
|
|
||||||
mimalloc-rust = { version = "0.2.1", optional = true }
|
mimalloc-rust = { version = "0.2.1", optional = true }
|
||||||
|
|
||||||
# for mips
|
# mips
|
||||||
indexmap = { version = "~1.9.3", optional = false, features = ["std"] }
|
|
||||||
|
|
||||||
atomic-shim = "0.2.0"
|
atomic-shim = "0.2.0"
|
||||||
|
|
||||||
smoltcp = { version = "0.12.0", optional = true, default-features = false, features = [
|
smoltcp = { version = "0.12.0", optional = true, default-features = false, features = [
|
||||||
@@ -188,12 +182,18 @@ async-compression = { version = "0.4.17", default-features = false, features = [
|
|||||||
|
|
||||||
kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" }
|
kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" }
|
||||||
|
|
||||||
prost-reflect = { version = "0.14.5", features = [
|
prost-reflect = { version = "0.14.5", default-features = false, features = [
|
||||||
"serde",
|
|
||||||
"derive",
|
"derive",
|
||||||
"text-format"
|
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
# for http connector
|
||||||
|
http_req = { git = "https://github.com/EasyTier/http_req.git", default-features = false, features = ["rust-tls"] }
|
||||||
|
|
||||||
|
# for dns connector
|
||||||
|
hickory-resolver = "0.24.4"
|
||||||
|
|
||||||
|
bounded_join_set = "0.3.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies]
|
||||||
machine-uid = "0.5.3"
|
machine-uid = "0.5.3"
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ rpc_build = { package = "easytier-rpc-build", version = "0.1.0", features = ["in
|
|||||||
prost-reflect-build = { version = "0.14.0" }
|
prost-reflect-build = { version = "0.14.0" }
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
reqwest = { version = "0.11", features = ["blocking"] }
|
reqwest = { version = "0.12.12", features = ["blocking"] }
|
||||||
zip = "0.6.6"
|
zip = "0.6.6"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -128,16 +128,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
WindowsBuild::check_for_win();
|
WindowsBuild::check_for_win();
|
||||||
|
|
||||||
|
let proto_files_reflect = ["src/proto/peer_rpc.proto", "src/proto/common.proto"];
|
||||||
|
|
||||||
let proto_files = [
|
let proto_files = [
|
||||||
"src/proto/peer_rpc.proto",
|
|
||||||
"src/proto/common.proto",
|
|
||||||
"src/proto/error.proto",
|
"src/proto/error.proto",
|
||||||
"src/proto/tests.proto",
|
"src/proto/tests.proto",
|
||||||
"src/proto/cli.proto",
|
"src/proto/cli.proto",
|
||||||
"src/proto/web.proto",
|
"src/proto/web.proto",
|
||||||
];
|
];
|
||||||
|
|
||||||
for proto_file in &proto_files {
|
for proto_file in proto_files.iter().chain(proto_files_reflect.iter()) {
|
||||||
println!("cargo:rerun-if-changed={}", proto_file);
|
println!("cargo:rerun-if-changed={}", proto_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,12 +156,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.type_attribute("peer_rpc.PeerInfoForGlobalMap", "#[derive(Hash)]")
|
.type_attribute("peer_rpc.PeerInfoForGlobalMap", "#[derive(Hash)]")
|
||||||
.type_attribute("peer_rpc.ForeignNetworkRouteInfoKey", "#[derive(Hash, Eq)]")
|
.type_attribute("peer_rpc.ForeignNetworkRouteInfoKey", "#[derive(Hash, Eq)]")
|
||||||
.type_attribute("common.RpcDescriptor", "#[derive(Hash, Eq)]")
|
.type_attribute("common.RpcDescriptor", "#[derive(Hash, Eq)]")
|
||||||
|
.field_attribute(".web.NetworkConfig", "#[serde(default)]")
|
||||||
.service_generator(Box::new(rpc_build::ServiceGenerator::new()))
|
.service_generator(Box::new(rpc_build::ServiceGenerator::new()))
|
||||||
.btree_map(["."]);
|
.btree_map(["."]);
|
||||||
|
|
||||||
|
config.compile_protos(&proto_files, &["src/proto/"])?;
|
||||||
|
|
||||||
prost_reflect_build::Builder::new()
|
prost_reflect_build::Builder::new()
|
||||||
.file_descriptor_set_bytes("crate::proto::DESCRIPTOR_POOL_BYTES")
|
.file_descriptor_set_bytes("crate::proto::DESCRIPTOR_POOL_BYTES")
|
||||||
.compile_protos_with_config(config, &proto_files, &["src/proto/"])?;
|
.compile_protos_with_config(config, &proto_files_reflect, &["src/proto/"])?;
|
||||||
|
|
||||||
check_locale();
|
check_locale();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -96,12 +96,15 @@ core_clap:
|
|||||||
enable_exit_node:
|
enable_exit_node:
|
||||||
en: "allow this node to be an exit node"
|
en: "allow this node to be an exit node"
|
||||||
zh-CN: "允许此节点成为出口节点"
|
zh-CN: "允许此节点成为出口节点"
|
||||||
|
proxy_forward_by_system:
|
||||||
|
en: "forward packet to proxy networks via system kernel, disable internal nat for network proxy"
|
||||||
|
zh-CN: "通过系统内核转发子网代理数据包,禁用内置NAT"
|
||||||
no_tun:
|
no_tun:
|
||||||
en: "do not create TUN device, can use subnet proxy to access node"
|
en: "do not create TUN device, can use subnet proxy to access node"
|
||||||
zh-CN: "不创建TUN设备,可以使用子网代理访问节点"
|
zh-CN: "不创建TUN设备,可以使用子网代理访问节点"
|
||||||
use_smoltcp:
|
use_smoltcp:
|
||||||
en: "enable smoltcp stack for subnet proxy"
|
en: "enable smoltcp stack for subnet proxy and kcp proxy"
|
||||||
zh-CN: "为子网代理启用smoltcp堆栈"
|
zh-CN: "为子网代理和 KCP 代理启用smoltcp堆栈"
|
||||||
manual_routes:
|
manual_routes:
|
||||||
en: "assign routes cidr manually, will disable subnet proxy and wireguard routes propagated from peers. e.g.: 192.168.0.0/16"
|
en: "assign routes cidr manually, will disable subnet proxy and wireguard routes propagated from peers. e.g.: 192.168.0.0/16"
|
||||||
zh-CN: "手动分配路由CIDR,将禁用子网代理和从对等节点传播的wireguard路由。例如:192.168.0.0/16"
|
zh-CN: "手动分配路由CIDR,将禁用子网代理和从对等节点传播的wireguard路由。例如:192.168.0.0/16"
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ pub fn gen_default_flags() -> Flags {
|
|||||||
mtu: 1380,
|
mtu: 1380,
|
||||||
latency_first: false,
|
latency_first: false,
|
||||||
enable_exit_node: false,
|
enable_exit_node: false,
|
||||||
|
proxy_forward_by_system: false,
|
||||||
no_tun: false,
|
no_tun: false,
|
||||||
use_smoltcp: false,
|
use_smoltcp: false,
|
||||||
relay_network_whitelist: "*".to_string(),
|
relay_network_whitelist: "*".to_string(),
|
||||||
disable_p2p: false,
|
disable_p2p: false,
|
||||||
relay_all_peer_rpc: false,
|
relay_all_peer_rpc: false,
|
||||||
disable_udp_hole_punching: false,
|
disable_udp_hole_punching: false,
|
||||||
ipv6_listener: "udp://[::]:0".to_string(),
|
|
||||||
multi_thread: true,
|
multi_thread: true,
|
||||||
data_compress_algo: CompressionAlgoPb::None.into(),
|
data_compress_algo: CompressionAlgoPb::None.into(),
|
||||||
bind_device: true,
|
bind_device: true,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ pub struct GlobalCtx {
|
|||||||
running_listeners: Mutex<Vec<url::Url>>,
|
running_listeners: Mutex<Vec<url::Url>>,
|
||||||
|
|
||||||
enable_exit_node: bool,
|
enable_exit_node: bool,
|
||||||
|
proxy_forward_by_system: bool,
|
||||||
no_tun: bool,
|
no_tun: bool,
|
||||||
|
|
||||||
feature_flags: AtomicCell<PeerFeatureFlag>,
|
feature_flags: AtomicCell<PeerFeatureFlag>,
|
||||||
@@ -99,6 +100,7 @@ impl GlobalCtx {
|
|||||||
let stun_info_collection = Arc::new(StunInfoCollector::new_with_default_servers());
|
let stun_info_collection = Arc::new(StunInfoCollector::new_with_default_servers());
|
||||||
|
|
||||||
let enable_exit_node = config_fs.get_flags().enable_exit_node;
|
let enable_exit_node = config_fs.get_flags().enable_exit_node;
|
||||||
|
let proxy_forward_by_system = config_fs.get_flags().proxy_forward_by_system;
|
||||||
let no_tun = config_fs.get_flags().no_tun;
|
let no_tun = config_fs.get_flags().no_tun;
|
||||||
|
|
||||||
let mut feature_flags = PeerFeatureFlag::default();
|
let mut feature_flags = PeerFeatureFlag::default();
|
||||||
@@ -125,6 +127,7 @@ impl GlobalCtx {
|
|||||||
running_listeners: Mutex::new(Vec::new()),
|
running_listeners: Mutex::new(Vec::new()),
|
||||||
|
|
||||||
enable_exit_node,
|
enable_exit_node,
|
||||||
|
proxy_forward_by_system,
|
||||||
no_tun,
|
no_tun,
|
||||||
|
|
||||||
feature_flags: AtomicCell::new(feature_flags),
|
feature_flags: AtomicCell::new(feature_flags),
|
||||||
@@ -273,6 +276,10 @@ impl GlobalCtx {
|
|||||||
self.enable_exit_node
|
self.enable_exit_node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn proxy_forward_by_system(&self) -> bool {
|
||||||
|
self.proxy_forward_by_system
|
||||||
|
}
|
||||||
|
|
||||||
pub fn no_tun(&self) -> bool {
|
pub fn no_tun(&self) -> bool {
|
||||||
self.no_tun
|
self.no_tun
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{
|
|||||||
io::Write as _,
|
io::Write as _,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
use tokio::task::JoinSet;
|
use tokio::{task::JoinSet, time::timeout};
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
pub mod compressor;
|
pub mod compressor;
|
||||||
@@ -47,16 +47,13 @@ pub fn join_joinset_background<T: Debug + Send + Sync + 'static>(
|
|||||||
origin: String,
|
origin: String,
|
||||||
) {
|
) {
|
||||||
let js = Arc::downgrade(&js);
|
let js = Arc::downgrade(&js);
|
||||||
|
let o = origin.clone();
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
async move {
|
async move {
|
||||||
loop {
|
while js.strong_count() > 0 {
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
if js.weak_count() == 0 {
|
|
||||||
tracing::info!("joinset task exit");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
future::poll_fn(|cx| {
|
let fut = future::poll_fn(|cx| {
|
||||||
let Some(js) = js.upgrade() else {
|
let Some(js) = js.upgrade() else {
|
||||||
return std::task::Poll::Ready(());
|
return std::task::Poll::Ready(());
|
||||||
};
|
};
|
||||||
@@ -64,15 +61,24 @@ pub fn join_joinset_background<T: Debug + Send + Sync + 'static>(
|
|||||||
let mut js = js.lock().unwrap();
|
let mut js = js.lock().unwrap();
|
||||||
while !js.is_empty() {
|
while !js.is_empty() {
|
||||||
let ret = js.poll_join_next(cx);
|
let ret = js.poll_join_next(cx);
|
||||||
if ret.is_pending() {
|
match ret {
|
||||||
return std::task::Poll::Pending;
|
std::task::Poll::Ready(Some(_)) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::task::Poll::Ready(None) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::task::Poll::Pending => {
|
||||||
|
return std::task::Poll::Pending;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::task::Poll::Ready(())
|
std::task::Poll::Ready(())
|
||||||
})
|
});
|
||||||
.await;
|
|
||||||
|
let _ = timeout(std::time::Duration::from_secs(5), fut).await;
|
||||||
}
|
}
|
||||||
|
tracing::debug!(?o, "joinset task exit");
|
||||||
}
|
}
|
||||||
.instrument(tracing::info_span!(
|
.instrument(tracing::info_span!(
|
||||||
"join_joinset_background",
|
"join_joinset_background",
|
||||||
@@ -167,5 +173,6 @@ mod tests {
|
|||||||
drop(js);
|
drop(js);
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||||
assert_eq!(weak_js.weak_count(), 0);
|
assert_eq!(weak_js.weak_count(), 0);
|
||||||
|
assert_eq!(weak_js.strong_count(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -8,6 +8,8 @@ use crate::proto::common::{NatType, StunInfo};
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
use hickory_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts};
|
||||||
|
use hickory_resolver::TokioAsyncResolver;
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use tokio::net::{lookup_host, UdpSocket};
|
use tokio::net::{lookup_host, UdpSocket};
|
||||||
use tokio::sync::{broadcast, Mutex};
|
use tokio::sync::{broadcast, Mutex};
|
||||||
@@ -22,21 +24,68 @@ use crate::common::error::Error;
|
|||||||
|
|
||||||
use super::stun_codec_ext::*;
|
use super::stun_codec_ext::*;
|
||||||
|
|
||||||
|
pub fn get_default_resolver_config() -> ResolverConfig {
|
||||||
|
let mut default_resolve_config = ResolverConfig::new();
|
||||||
|
default_resolve_config.add_name_server(NameServerConfig::new(
|
||||||
|
"223.5.5.5:53".parse().unwrap(),
|
||||||
|
Protocol::Udp,
|
||||||
|
));
|
||||||
|
default_resolve_config.add_name_server(NameServerConfig::new(
|
||||||
|
"180.184.1.1:53".parse().unwrap(),
|
||||||
|
Protocol::Udp,
|
||||||
|
));
|
||||||
|
default_resolve_config
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn resolve_txt_record(
|
||||||
|
domain_name: &str,
|
||||||
|
resolver: &TokioAsyncResolver,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let response = resolver.txt_lookup(domain_name).await.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"txt_lookup failed, domain_name: {}",
|
||||||
|
domain_name.to_string()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let txt_record = response.iter().next().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"no txt record found, domain_name: {}",
|
||||||
|
domain_name.to_string()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let txt_data = String::from_utf8_lossy(&txt_record.txt_data()[0]);
|
||||||
|
tracing::info!(?txt_data, ?domain_name, "get txt record");
|
||||||
|
|
||||||
|
Ok(txt_data.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
struct HostResolverIter {
|
struct HostResolverIter {
|
||||||
hostnames: Vec<String>,
|
hostnames: Vec<String>,
|
||||||
ips: Vec<SocketAddr>,
|
ips: Vec<SocketAddr>,
|
||||||
max_ip_per_domain: u32,
|
max_ip_per_domain: u32,
|
||||||
|
use_ipv6: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostResolverIter {
|
impl HostResolverIter {
|
||||||
fn new(hostnames: Vec<String>, max_ip_per_domain: u32) -> Self {
|
fn new(hostnames: Vec<String>, max_ip_per_domain: u32, use_ipv6: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
hostnames,
|
hostnames,
|
||||||
ips: vec![],
|
ips: vec![],
|
||||||
max_ip_per_domain,
|
max_ip_per_domain,
|
||||||
|
use_ipv6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_txt_record(domain_name: &str) -> Result<Vec<String>, Error> {
|
||||||
|
let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or(
|
||||||
|
TokioAsyncResolver::tokio(get_default_resolver_config(), ResolverOpts::default()),
|
||||||
|
);
|
||||||
|
let txt_data = resolve_txt_record(domain_name, &resolver).await?;
|
||||||
|
Ok(txt_data.split(" ").map(|x| x.to_string()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
#[async_recursion::async_recursion]
|
#[async_recursion::async_recursion]
|
||||||
async fn next(&mut self) -> Option<SocketAddr> {
|
async fn next(&mut self) -> Option<SocketAddr> {
|
||||||
if self.ips.is_empty() {
|
if self.ips.is_empty() {
|
||||||
@@ -51,10 +100,35 @@ impl HostResolverIter {
|
|||||||
format!("{}:3478", host)
|
format!("{}:3478", host)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if host.starts_with("txt:") {
|
||||||
|
let domain_name = host.trim_start_matches("txt:");
|
||||||
|
match Self::get_txt_record(domain_name).await {
|
||||||
|
Ok(hosts) => {
|
||||||
|
tracing::info!(
|
||||||
|
?domain_name,
|
||||||
|
?hosts,
|
||||||
|
"get txt record success when resolve stun server"
|
||||||
|
);
|
||||||
|
// insert hosts to the head of hostnames
|
||||||
|
self.hostnames.splice(0..0, hosts.into_iter());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
?domain_name,
|
||||||
|
?e,
|
||||||
|
"get txt record failed when resolve stun server"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self.next().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let use_ipv6 = self.use_ipv6;
|
||||||
|
|
||||||
match lookup_host(&host).await {
|
match lookup_host(&host).await {
|
||||||
Ok(ips) => {
|
Ok(ips) => {
|
||||||
self.ips = ips
|
self.ips = ips
|
||||||
.filter(|x| x.is_ipv4())
|
.filter(|x| if use_ipv6 { x.is_ipv6() } else { x.is_ipv4() })
|
||||||
.choose_multiple(&mut rand::thread_rng(), self.max_ip_per_domain as usize);
|
.choose_multiple(&mut rand::thread_rng(), self.max_ip_per_domain as usize);
|
||||||
|
|
||||||
if self.ips.is_empty() {
|
if self.ips.is_empty() {
|
||||||
@@ -400,7 +474,7 @@ impl UdpNatTypeDetectResult {
|
|||||||
// find resp with distinct stun server
|
// find resp with distinct stun server
|
||||||
self.stun_resps
|
self.stun_resps
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.stun_server_addr)
|
.map(|x| x.recv_from_addr)
|
||||||
.collect::<BTreeSet<_>>()
|
.collect::<BTreeSet<_>>()
|
||||||
.len()
|
.len()
|
||||||
}
|
}
|
||||||
@@ -555,8 +629,11 @@ impl UdpNatTypeDetector {
|
|||||||
udp: Arc<UdpSocket>,
|
udp: Arc<UdpSocket>,
|
||||||
) -> Result<UdpNatTypeDetectResult, Error> {
|
) -> Result<UdpNatTypeDetectResult, Error> {
|
||||||
let mut stun_servers = vec![];
|
let mut stun_servers = vec![];
|
||||||
let mut host_resolver =
|
let mut host_resolver = HostResolverIter::new(
|
||||||
HostResolverIter::new(self.stun_server_hosts.clone(), self.max_ip_per_domain);
|
self.stun_server_hosts.clone(),
|
||||||
|
self.max_ip_per_domain,
|
||||||
|
false,
|
||||||
|
);
|
||||||
while let Some(addr) = host_resolver.next().await {
|
while let Some(addr) = host_resolver.next().await {
|
||||||
stun_servers.push(addr);
|
stun_servers.push(addr);
|
||||||
}
|
}
|
||||||
@@ -602,7 +679,9 @@ pub trait StunInfoCollectorTrait: Send + Sync {
|
|||||||
|
|
||||||
pub struct StunInfoCollector {
|
pub struct StunInfoCollector {
|
||||||
stun_servers: Arc<RwLock<Vec<String>>>,
|
stun_servers: Arc<RwLock<Vec<String>>>,
|
||||||
|
stun_servers_v6: Arc<RwLock<Vec<String>>>,
|
||||||
udp_nat_test_result: Arc<RwLock<Option<UdpNatTypeDetectResult>>>,
|
udp_nat_test_result: Arc<RwLock<Option<UdpNatTypeDetectResult>>>,
|
||||||
|
public_ipv6: Arc<AtomicCell<Option<Ipv6Addr>>>,
|
||||||
nat_test_result_time: Arc<AtomicCell<chrono::DateTime<Local>>>,
|
nat_test_result_time: Arc<AtomicCell<chrono::DateTime<Local>>>,
|
||||||
redetect_notify: Arc<tokio::sync::Notify>,
|
redetect_notify: Arc<tokio::sync::Notify>,
|
||||||
tasks: std::sync::Mutex<JoinSet<()>>,
|
tasks: std::sync::Mutex<JoinSet<()>>,
|
||||||
@@ -621,7 +700,12 @@ impl StunInfoCollectorTrait for StunInfoCollector {
|
|||||||
udp_nat_type: result.nat_type() as i32,
|
udp_nat_type: result.nat_type() as i32,
|
||||||
tcp_nat_type: 0,
|
tcp_nat_type: 0,
|
||||||
last_update_time: self.nat_test_result_time.load().timestamp(),
|
last_update_time: self.nat_test_result_time.load().timestamp(),
|
||||||
public_ip: result.public_ips().iter().map(|x| x.to_string()).collect(),
|
public_ip: result
|
||||||
|
.public_ips()
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.chain(self.public_ipv6.load().map(|x| x.to_string()))
|
||||||
|
.collect(),
|
||||||
min_port: result.min_port() as u32,
|
min_port: result.min_port() as u32,
|
||||||
max_port: result.max_port() as u32,
|
max_port: result.max_port() as u32,
|
||||||
}
|
}
|
||||||
@@ -640,7 +724,7 @@ impl StunInfoCollectorTrait for StunInfoCollector {
|
|||||||
|
|
||||||
if stun_servers.is_empty() {
|
if stun_servers.is_empty() {
|
||||||
let mut host_resolver =
|
let mut host_resolver =
|
||||||
HostResolverIter::new(self.stun_servers.read().unwrap().clone(), 2);
|
HostResolverIter::new(self.stun_servers.read().unwrap().clone(), 2, false);
|
||||||
while let Some(addr) = host_resolver.next().await {
|
while let Some(addr) = host_resolver.next().await {
|
||||||
stun_servers.push(addr);
|
stun_servers.push(addr);
|
||||||
if stun_servers.len() >= 2 {
|
if stun_servers.len() >= 2 {
|
||||||
@@ -680,7 +764,9 @@ impl StunInfoCollector {
|
|||||||
pub fn new(stun_servers: Vec<String>) -> Self {
|
pub fn new(stun_servers: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stun_servers: Arc::new(RwLock::new(stun_servers)),
|
stun_servers: Arc::new(RwLock::new(stun_servers)),
|
||||||
|
stun_servers_v6: Arc::new(RwLock::new(Self::get_default_servers_v6())),
|
||||||
udp_nat_test_result: Arc::new(RwLock::new(None)),
|
udp_nat_test_result: Arc::new(RwLock::new(None)),
|
||||||
|
public_ipv6: Arc::new(AtomicCell::new(None)),
|
||||||
nat_test_result_time: Arc::new(AtomicCell::new(Local::now())),
|
nat_test_result_time: Arc::new(AtomicCell::new(Local::now())),
|
||||||
redetect_notify: Arc::new(tokio::sync::Notify::new()),
|
redetect_notify: Arc::new(tokio::sync::Notify::new()),
|
||||||
tasks: std::sync::Mutex::new(JoinSet::new()),
|
tasks: std::sync::Mutex::new(JoinSet::new()),
|
||||||
@@ -696,28 +782,42 @@ impl StunInfoCollector {
|
|||||||
// NOTICE: we may need to choose stun stun server based on geo location
|
// NOTICE: we may need to choose stun stun server based on geo location
|
||||||
// stun server cross nation may return a external ip address with high latency and loss rate
|
// stun server cross nation may return a external ip address with high latency and loss rate
|
||||||
vec![
|
vec![
|
||||||
|
"txt:stun.easytier.cn",
|
||||||
"stun.miwifi.com",
|
"stun.miwifi.com",
|
||||||
"stun.chat.bilibili.com",
|
"stun.chat.bilibili.com",
|
||||||
"stun.hitv.com",
|
"stun.hitv.com",
|
||||||
"stun.cdnbye.com",
|
|
||||||
"stun.douyucdn.cn:18000",
|
|
||||||
"fwa.lifesizecloud.com",
|
|
||||||
"global.turn.twilio.com",
|
|
||||||
"turn.cloudflare.com",
|
|
||||||
"stun.isp.net.au",
|
|
||||||
"stun.nextcloud.com",
|
|
||||||
"stun.freeswitch.org",
|
|
||||||
"stun.voip.blackberry.com",
|
|
||||||
"stunserver.stunprotocol.org",
|
|
||||||
"stun.sipnet.com",
|
|
||||||
"stun.radiojar.com",
|
|
||||||
"stun.sonetel.com",
|
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_default_servers_v6() -> Vec<String> {
|
||||||
|
vec!["txt:stun-v6.easytier.cn"]
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_public_ipv6(servers: &Vec<String>) -> Option<Ipv6Addr> {
|
||||||
|
let mut ips = HostResolverIter::new(servers.to_vec(), 10, true);
|
||||||
|
while let Some(ip) = ips.next().await {
|
||||||
|
let udp = Arc::new(UdpSocket::bind(format!("[::]:0")).await.unwrap());
|
||||||
|
let ret = StunClientBuilder::new(udp.clone())
|
||||||
|
.new_stun_client(ip)
|
||||||
|
.bind_request(false, false)
|
||||||
|
.await;
|
||||||
|
tracing::debug!(?ret, "finish ipv6 udp nat type detect");
|
||||||
|
match ret.map(|x| x.mapped_socket_addr.map(|x| x.ip())) {
|
||||||
|
Ok(Some(IpAddr::V6(v6))) => {
|
||||||
|
return Some(v6);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn start_stun_routine(&self) {
|
fn start_stun_routine(&self) {
|
||||||
if self.started.load(std::sync::atomic::Ordering::Relaxed) {
|
if self.started.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
return;
|
return;
|
||||||
@@ -784,6 +884,30 @@ impl StunInfoCollector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// for ipv6
|
||||||
|
let stun_servers = self.stun_servers_v6.clone();
|
||||||
|
let stored_ipv6 = self.public_ipv6.clone();
|
||||||
|
let redetect_notify = self.redetect_notify.clone();
|
||||||
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
|
loop {
|
||||||
|
let servers = stun_servers.read().unwrap().clone();
|
||||||
|
Self::get_public_ipv6(&servers)
|
||||||
|
.await
|
||||||
|
.map(|x| stored_ipv6.store(Some(x)));
|
||||||
|
|
||||||
|
let sleep_sec = if stored_ipv6.load().is_none() {
|
||||||
|
60
|
||||||
|
} else {
|
||||||
|
360
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = redetect_notify.notified() => {}
|
||||||
|
_ = tokio::time::sleep(Duration::from_secs(sleep_sec)) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_stun_info(&self) {
|
pub fn update_stun_info(&self) {
|
||||||
@@ -862,6 +986,48 @@ mod tests {
|
|||||||
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||||
let ret = detector.detect_nat_type(0).await;
|
let ret = detector.detect_nat_type(0).await;
|
||||||
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
||||||
assert_eq!(ret.unwrap().nat_type(), NatType::PortRestricted);
|
assert_eq!(ret.unwrap().nat_type(), NatType::Restricted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_txt_public_stun_server() {
|
||||||
|
let stun_servers = vec!["txt:stun.easytier.cn".to_string()];
|
||||||
|
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||||
|
let ret = detector.detect_nat_type(0).await;
|
||||||
|
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
||||||
|
assert!(!ret.unwrap().stun_resps.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_v4_stun() {
|
||||||
|
let mut udp_server = UdpTunnelListener::new("udp://0.0.0.0:55355".parse().unwrap());
|
||||||
|
let mut tasks = JoinSet::new();
|
||||||
|
tasks.spawn(async move {
|
||||||
|
udp_server.listen().await.unwrap();
|
||||||
|
loop {
|
||||||
|
udp_server.accept().await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let stun_servers = vec!["127.0.0.1:55355".to_string()];
|
||||||
|
|
||||||
|
let detector = UdpNatTypeDetector::new(stun_servers, 1);
|
||||||
|
let ret = detector.detect_nat_type(0).await;
|
||||||
|
println!("{:#?}, {:?}", ret, ret.as_ref().unwrap().nat_type());
|
||||||
|
assert_eq!(ret.unwrap().nat_type(), NatType::Restricted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_v6_stun() {
|
||||||
|
let mut udp_server = UdpTunnelListener::new("udp://[::]:55355".parse().unwrap());
|
||||||
|
let mut tasks = JoinSet::new();
|
||||||
|
tasks.spawn(async move {
|
||||||
|
udp_server.listen().await.unwrap();
|
||||||
|
loop {
|
||||||
|
udp_server.accept().await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let stun_servers = vec!["::1:55355".to_string()];
|
||||||
|
let ret = StunInfoCollector::get_public_ipv6(&stun_servers).await;
|
||||||
|
println!("{:#?}", ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// try connect peers directly, with either its public ip or lan ip
|
// try connect peers directly, with either its public ip or lan ip
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
net::SocketAddr,
|
collections::HashSet,
|
||||||
|
net::{Ipv6Addr, SocketAddr},
|
||||||
|
str::FromStr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
@@ -22,6 +24,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
rpc_types::controller::BaseController,
|
rpc_types::controller::BaseController,
|
||||||
},
|
},
|
||||||
|
tunnel::IpVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::proto::cli::PeerConnInfo;
|
use crate::proto::cli::PeerConnInfo;
|
||||||
@@ -79,7 +82,6 @@ struct DstListenerUrlBlackListItem(PeerId, url::Url);
|
|||||||
struct DirectConnectorManagerData {
|
struct DirectConnectorManagerData {
|
||||||
global_ctx: ArcGlobalCtx,
|
global_ctx: ArcGlobalCtx,
|
||||||
peer_manager: Arc<PeerManager>,
|
peer_manager: Arc<PeerManager>,
|
||||||
dst_blacklist: timedmap::TimedMap<DstBlackListItem, ()>,
|
|
||||||
dst_listener_blacklist: timedmap::TimedMap<DstListenerUrlBlackListItem, ()>,
|
dst_listener_blacklist: timedmap::TimedMap<DstListenerUrlBlackListItem, ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +90,6 @@ impl DirectConnectorManagerData {
|
|||||||
Self {
|
Self {
|
||||||
global_ctx,
|
global_ctx,
|
||||||
peer_manager,
|
peer_manager,
|
||||||
dst_blacklist: timedmap::TimedMap::new(),
|
|
||||||
dst_listener_blacklist: timedmap::TimedMap::new(),
|
dst_listener_blacklist: timedmap::TimedMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,9 @@ impl DirectConnectorManager {
|
|||||||
let peers = data.peer_manager.list_peers().await;
|
let peers = data.peer_manager.list_peers().await;
|
||||||
let mut tasks = JoinSet::new();
|
let mut tasks = JoinSet::new();
|
||||||
for peer_id in peers {
|
for peer_id in peers {
|
||||||
if peer_id == my_peer_id {
|
if peer_id == my_peer_id
|
||||||
|
|| data.peer_manager.has_directly_connected_conn(peer_id)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tasks.spawn(Self::do_try_direct_connect(data.clone(), peer_id));
|
tasks.spawn(Self::do_try_direct_connect(data.clone(), peer_id));
|
||||||
@@ -173,24 +176,13 @@ impl DirectConnectorManager {
|
|||||||
dst_peer_id: PeerId,
|
dst_peer_id: PeerId,
|
||||||
addr: String,
|
addr: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
data.dst_blacklist.cleanup();
|
let connector = create_connector_by_url(&addr, &data.global_ctx, IpVersion::Both).await?;
|
||||||
if data
|
|
||||||
.dst_blacklist
|
|
||||||
.contains(&DstBlackListItem(dst_peer_id.clone(), addr.clone()))
|
|
||||||
{
|
|
||||||
tracing::debug!("try_connect_to_ip failed, addr in blacklist: {}", addr);
|
|
||||||
return Err(Error::UrlInBlacklist);
|
|
||||||
}
|
|
||||||
|
|
||||||
let connector = create_connector_by_url(&addr, &data.global_ctx).await?;
|
|
||||||
let (peer_id, conn_id) = timeout(
|
let (peer_id, conn_id) = timeout(
|
||||||
std::time::Duration::from_secs(5),
|
std::time::Duration::from_secs(3),
|
||||||
data.peer_manager.try_connect(connector),
|
data.peer_manager.try_direct_connect(connector),
|
||||||
)
|
)
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
// let (peer_id, conn_id) = data.peer_manager.try_connect(connector).await?;
|
|
||||||
|
|
||||||
if peer_id != dst_peer_id && !TESTING.load(Ordering::Relaxed) {
|
if peer_id != dst_peer_id && !TESTING.load(Ordering::Relaxed) {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"connect to ip succ: {}, but peer id mismatch, expect: {}, actual: {}",
|
"connect to ip succ: {}, but peer id mismatch, expect: {}, actual: {}",
|
||||||
@@ -204,6 +196,7 @@ impl DirectConnectorManager {
|
|||||||
.await?;
|
.await?;
|
||||||
return Err(Error::InvalidUrl(addr));
|
return Err(Error::InvalidUrl(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +207,7 @@ impl DirectConnectorManager {
|
|||||||
addr: String,
|
addr: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut rand_gen = rand::rngs::OsRng::default();
|
let mut rand_gen = rand::rngs::OsRng::default();
|
||||||
let backoff_ms = vec![1000, 2000, 4000];
|
let backoff_ms = vec![1000, 2000];
|
||||||
let mut backoff_idx = 0;
|
let mut backoff_idx = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -237,12 +230,6 @@ impl DirectConnectorManager {
|
|||||||
backoff_idx += 1;
|
backoff_idx += 1;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
data.dst_blacklist.insert(
|
|
||||||
DstBlackListItem(dst_peer_id.clone(), addr.clone()),
|
|
||||||
(),
|
|
||||||
std::time::Duration::from_secs(DIRECT_CONNECTOR_BLACKLIST_TIMEOUT_SEC),
|
|
||||||
);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,61 +260,43 @@ impl DirectConnectorManager {
|
|||||||
|
|
||||||
tracing::debug!(?available_listeners, "got available listeners");
|
tracing::debug!(?available_listeners, "got available listeners");
|
||||||
|
|
||||||
let mut listener = available_listeners.get(0).ok_or(anyhow::anyhow!(
|
if available_listeners.is_empty() {
|
||||||
"peer {} have no valid listener",
|
return Err(anyhow::anyhow!("peer {} have no valid listener", dst_peer_id).into());
|
||||||
dst_peer_id
|
}
|
||||||
))?;
|
|
||||||
|
|
||||||
// if have default listener, use it first
|
// if have default listener, use it first
|
||||||
listener = available_listeners
|
let listener = available_listeners
|
||||||
.iter()
|
.iter()
|
||||||
.find(|l| l.scheme() == data.global_ctx.get_flags().default_protocol)
|
.find(|l| l.scheme() == data.global_ctx.get_flags().default_protocol)
|
||||||
.unwrap_or(listener);
|
.unwrap_or(available_listeners.get(0).unwrap());
|
||||||
|
|
||||||
let mut tasks = JoinSet::new();
|
let mut tasks = bounded_join_set::JoinSet::new(2);
|
||||||
|
|
||||||
let listener_host = listener.socket_addrs(|| None).unwrap().pop();
|
let listener_host = listener.socket_addrs(|| None)?.pop();
|
||||||
match listener_host {
|
match listener_host {
|
||||||
Some(SocketAddr::V4(s_addr)) => {
|
Some(SocketAddr::V4(s_addr)) => {
|
||||||
if s_addr.ip().is_unspecified() {
|
if s_addr.ip().is_unspecified() {
|
||||||
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
ip_list
|
||||||
let mut addr = (*listener).clone();
|
.interface_ipv4s
|
||||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
.iter()
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
.chain(ip_list.public_ipv4.iter())
|
||||||
data.clone(),
|
.for_each(|ip| {
|
||||||
dst_peer_id.clone(),
|
let mut addr = (*listener).clone();
|
||||||
addr.to_string(),
|
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||||
));
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
} else {
|
data.clone(),
|
||||||
tracing::error!(
|
dst_peer_id.clone(),
|
||||||
?ip,
|
addr.to_string(),
|
||||||
?listener,
|
));
|
||||||
?dst_peer_id,
|
} else {
|
||||||
"failed to set host for interface ipv4"
|
tracing::error!(
|
||||||
);
|
?ip,
|
||||||
}
|
?listener,
|
||||||
});
|
?dst_peer_id,
|
||||||
|
"failed to set host for interface ipv4"
|
||||||
if let Some(public_ipv4) = ip_list.public_ipv4 {
|
);
|
||||||
let mut addr = (*listener).clone();
|
}
|
||||||
if addr
|
});
|
||||||
.set_host(Some(public_ipv4.to_string().as_str()))
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
|
||||||
data.clone(),
|
|
||||||
dst_peer_id.clone(),
|
|
||||||
addr.to_string(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
tracing::error!(
|
|
||||||
?public_ipv4,
|
|
||||||
?listener,
|
|
||||||
?dst_peer_id,
|
|
||||||
"failed to set host for public ipv4"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
data.clone(),
|
data.clone(),
|
||||||
@@ -338,47 +307,42 @@ impl DirectConnectorManager {
|
|||||||
}
|
}
|
||||||
Some(SocketAddr::V6(s_addr)) => {
|
Some(SocketAddr::V6(s_addr)) => {
|
||||||
if s_addr.ip().is_unspecified() {
|
if s_addr.ip().is_unspecified() {
|
||||||
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
// for ipv6, only try public ip
|
||||||
let mut addr = (*listener).clone();
|
ip_list
|
||||||
if addr
|
.interface_ipv6s
|
||||||
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
.iter()
|
||||||
.is_ok()
|
.chain(ip_list.public_ipv6.iter())
|
||||||
{
|
.filter_map(|x| Ipv6Addr::from_str(&x.to_string()).ok())
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
.filter(|x| {
|
||||||
data.clone(),
|
TESTING.load(Ordering::Relaxed)
|
||||||
dst_peer_id.clone(),
|
|| (!x.is_loopback()
|
||||||
addr.to_string(),
|
&& !x.is_unspecified()
|
||||||
));
|
&& !x.is_unique_local()
|
||||||
} else {
|
&& !x.is_unicast_link_local()
|
||||||
tracing::error!(
|
&& !x.is_multicast())
|
||||||
?ip,
|
})
|
||||||
?listener,
|
.collect::<HashSet<_>>()
|
||||||
?dst_peer_id,
|
.iter()
|
||||||
"failed to set host for interface ipv6"
|
.for_each(|ip| {
|
||||||
);
|
let mut addr = (*listener).clone();
|
||||||
}
|
if addr
|
||||||
});
|
.set_host(Some(format!("[{}]", ip.to_string()).as_str()))
|
||||||
|
.is_ok()
|
||||||
if let Some(public_ipv6) = ip_list.public_ipv6 {
|
{
|
||||||
let mut addr = (*listener).clone();
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
if addr
|
data.clone(),
|
||||||
.set_host(Some(format!("[{}]", public_ipv6.to_string()).as_str()))
|
dst_peer_id.clone(),
|
||||||
.is_ok()
|
addr.to_string(),
|
||||||
{
|
));
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
} else {
|
||||||
data.clone(),
|
tracing::error!(
|
||||||
dst_peer_id.clone(),
|
?ip,
|
||||||
addr.to_string(),
|
?listener,
|
||||||
));
|
?dst_peer_id,
|
||||||
} else {
|
"failed to set host for public ipv6"
|
||||||
tracing::error!(
|
);
|
||||||
?public_ipv6,
|
}
|
||||||
?listener,
|
});
|
||||||
?dst_peer_id,
|
|
||||||
"failed to set host for public ipv6"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
data.clone(),
|
data.clone(),
|
||||||
@@ -430,14 +394,6 @@ impl DirectConnectorManager {
|
|||||||
dst_peer_id: PeerId,
|
dst_peer_id: PeerId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let peer_manager = data.peer_manager.clone();
|
let peer_manager = data.peer_manager.clone();
|
||||||
// check if we have direct connection with dst_peer_id
|
|
||||||
if let Some(c) = peer_manager.list_peer_conns(dst_peer_id).await {
|
|
||||||
// currently if we have any type of direct connection (udp or tcp), we will not try to connect
|
|
||||||
if !c.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("try direct connect to peer: {}", dst_peer_id);
|
tracing::debug!("try direct connect to peer: {}", dst_peer_id);
|
||||||
|
|
||||||
let rpc_stub = peer_manager
|
let rpc_stub = peer_manager
|
||||||
@@ -466,8 +422,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connector::direct::{
|
connector::direct::{
|
||||||
DirectConnectorManager, DirectConnectorManagerData, DstBlackListItem,
|
DirectConnectorManager, DirectConnectorManagerData, DstListenerUrlBlackListItem,
|
||||||
DstListenerUrlBlackListItem,
|
|
||||||
},
|
},
|
||||||
instance::listeners::ListenerManager,
|
instance::listeners::ListenerManager,
|
||||||
peers::tests::{
|
peers::tests::{
|
||||||
@@ -526,9 +481,7 @@ mod tests {
|
|||||||
#[values("tcp", "udp", "wg")] proto: &str,
|
#[values("tcp", "udp", "wg")] proto: &str,
|
||||||
#[values("true", "false")] ipv6: bool,
|
#[values("true", "false")] ipv6: bool,
|
||||||
) {
|
) {
|
||||||
if ipv6 && proto != "udp" {
|
TESTING.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let p_a = create_mock_peer_manager().await;
|
let p_a = create_mock_peer_manager().await;
|
||||||
let p_b = create_mock_peer_manager().await;
|
let p_b = create_mock_peer_manager().await;
|
||||||
@@ -544,14 +497,18 @@ mod tests {
|
|||||||
dm_a.run_as_client();
|
dm_a.run_as_client();
|
||||||
dm_c.run_as_server();
|
dm_c.run_as_server();
|
||||||
|
|
||||||
|
let port = if proto == "wg" { 11040 } else { 11041 };
|
||||||
if !ipv6 {
|
if !ipv6 {
|
||||||
let port = if proto == "wg" { 11040 } else { 11041 };
|
|
||||||
p_c.get_global_ctx().config.set_listeners(vec![format!(
|
p_c.get_global_ctx().config.set_listeners(vec![format!(
|
||||||
"{}://0.0.0.0:{}",
|
"{}://0.0.0.0:{}",
|
||||||
proto, port
|
proto, port
|
||||||
)
|
)
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap()]);
|
.unwrap()]);
|
||||||
|
} else {
|
||||||
|
p_c.get_global_ctx()
|
||||||
|
.config
|
||||||
|
.set_listeners(vec![format!("{}://[::]:{}", proto, port).parse().unwrap()]);
|
||||||
}
|
}
|
||||||
let mut f = p_c.get_global_ctx().config.get_flags();
|
let mut f = p_c.get_global_ctx().config.get_flags();
|
||||||
f.enable_ipv6 = ipv6;
|
f.enable_ipv6 = ipv6;
|
||||||
@@ -592,9 +549,5 @@ mod tests {
|
|||||||
1,
|
1,
|
||||||
"tcp://127.0.0.1:10222".parse().unwrap()
|
"tcp://127.0.0.1:10222".parse().unwrap()
|
||||||
)));
|
)));
|
||||||
|
|
||||||
assert!(data
|
|
||||||
.dst_blacklist
|
|
||||||
.contains(&DstBlackListItem(1, ip_list.listeners[0].to_string())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
277
easytier/src/connector/dns_connector.rs
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::{
|
||||||
|
error::Error,
|
||||||
|
global_ctx::ArcGlobalCtx,
|
||||||
|
stun::{get_default_resolver_config, resolve_txt_record},
|
||||||
|
},
|
||||||
|
tunnel::{IpVersion, Tunnel, TunnelConnector, TunnelError, PROTO_PORT_OFFSET},
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use dashmap::DashSet;
|
||||||
|
use hickory_resolver::{
|
||||||
|
config::{ResolverConfig, ResolverOpts},
|
||||||
|
proto::rr::rdata::SRV,
|
||||||
|
TokioAsyncResolver,
|
||||||
|
};
|
||||||
|
use rand::{seq::SliceRandom, Rng as _};
|
||||||
|
|
||||||
|
use crate::proto::common::TunnelInfo;
|
||||||
|
|
||||||
|
use super::{create_connector_by_url, http_connector::TunnelWithInfo};
|
||||||
|
|
||||||
|
fn weighted_choice<T>(options: &[(T, u64)]) -> Option<&T> {
|
||||||
|
let total_weight = options.iter().map(|(_, weight)| *weight).sum();
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let rand_value = rng.gen_range(0..total_weight);
|
||||||
|
let mut accumulated_weight = 0;
|
||||||
|
|
||||||
|
for (item, weight) in options {
|
||||||
|
accumulated_weight += *weight;
|
||||||
|
if rand_value < accumulated_weight {
|
||||||
|
return Some(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DNSTunnelConnector {
|
||||||
|
addr: url::Url,
|
||||||
|
bind_addrs: Vec<SocketAddr>,
|
||||||
|
global_ctx: ArcGlobalCtx,
|
||||||
|
ip_version: IpVersion,
|
||||||
|
|
||||||
|
default_resolve_config: ResolverConfig,
|
||||||
|
default_resolve_opts: ResolverOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DNSTunnelConnector {
|
||||||
|
pub fn new(addr: url::Url, global_ctx: ArcGlobalCtx) -> Self {
|
||||||
|
Self {
|
||||||
|
addr,
|
||||||
|
bind_addrs: Vec::new(),
|
||||||
|
global_ctx,
|
||||||
|
ip_version: IpVersion::Both,
|
||||||
|
|
||||||
|
default_resolve_config: get_default_resolver_config(),
|
||||||
|
default_resolve_opts: ResolverOpts::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, err)]
|
||||||
|
pub async fn handle_txt_record(
|
||||||
|
&self,
|
||||||
|
domain_name: &str,
|
||||||
|
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||||
|
let resolver =
|
||||||
|
TokioAsyncResolver::tokio_from_system_conf().unwrap_or(TokioAsyncResolver::tokio(
|
||||||
|
self.default_resolve_config.clone(),
|
||||||
|
self.default_resolve_opts.clone(),
|
||||||
|
));
|
||||||
|
let txt_data = resolve_txt_record(domain_name, &resolver)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("resolve txt record failed, domain_name: {}", domain_name))?;
|
||||||
|
|
||||||
|
let candidate_urls = txt_data
|
||||||
|
.split(" ")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.filter_map(|s| url::Url::parse(s.as_str()).ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// shuffle candidate_urls and get the first one
|
||||||
|
let url = candidate_urls
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"no valid url found, txt_data: {}, expecting an url list splitted by space",
|
||||||
|
txt_data
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let connector =
|
||||||
|
create_connector_by_url(url.as_str(), &self.global_ctx, self.ip_version).await?;
|
||||||
|
Ok(connector)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_one_srv_record(record: &SRV, protocol: &str) -> Result<(url::Url, u64), Error> {
|
||||||
|
// port must be non-zero
|
||||||
|
if record.port() == 0 {
|
||||||
|
return Err(anyhow::anyhow!("port must be non-zero").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let connector_dst = record.target().to_utf8();
|
||||||
|
let dst_url = format!("{}://{}:{}", protocol, connector_dst, record.port());
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
dst_url.parse().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"parse dst_url failed, protocol: {}, connector_dst: {}, port: {}, dst_url: {}",
|
||||||
|
protocol,
|
||||||
|
connector_dst,
|
||||||
|
record.port(),
|
||||||
|
dst_url
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
record.priority() as _,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret, err)]
|
||||||
|
pub async fn handle_srv_record(
|
||||||
|
&self,
|
||||||
|
domain_name: &str,
|
||||||
|
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||||
|
tracing::info!("handle_srv_record: {}", domain_name);
|
||||||
|
|
||||||
|
let resolver =
|
||||||
|
TokioAsyncResolver::tokio_from_system_conf().unwrap_or(TokioAsyncResolver::tokio(
|
||||||
|
self.default_resolve_config.clone(),
|
||||||
|
self.default_resolve_opts.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let srv_domains = PROTO_PORT_OFFSET
|
||||||
|
.iter()
|
||||||
|
.map(|(p, _)| (format!("_easytier._{}.{}", p, domain_name), *p)) // _easytier._udp.{domain_name}
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
tracing::info!("build srv_domains: {:?}", srv_domains);
|
||||||
|
let responses = Arc::new(DashSet::new());
|
||||||
|
let srv_lookup_tasks = srv_domains
|
||||||
|
.iter()
|
||||||
|
.map(|(srv_domain, protocol)| {
|
||||||
|
let resolver = resolver.clone();
|
||||||
|
let responses = responses.clone();
|
||||||
|
async move {
|
||||||
|
let response = resolver.srv_lookup(srv_domain).await.with_context(|| {
|
||||||
|
format!("srv_lookup failed, srv_domain: {}", srv_domain.to_string())
|
||||||
|
})?;
|
||||||
|
tracing::info!(?response, ?srv_domain, "srv_lookup response");
|
||||||
|
for record in response.iter() {
|
||||||
|
let parsed_record = Self::handle_one_srv_record(record, &protocol);
|
||||||
|
tracing::info!(?parsed_record, ?srv_domain, "parsed_record");
|
||||||
|
if parsed_record.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"got invalid srv record {:?}",
|
||||||
|
parsed_record.as_ref().unwrap_err()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
responses.insert(parsed_record.unwrap());
|
||||||
|
}
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let _ = futures::future::join_all(srv_lookup_tasks).await;
|
||||||
|
|
||||||
|
let srv_records = responses.iter().map(|r| r.clone()).collect::<Vec<_>>();
|
||||||
|
if srv_records.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("no srv record found").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = weighted_choice(srv_records.as_slice()).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to choose a srv record, domain_name: {}, srv_records: {:?}",
|
||||||
|
domain_name.to_string(),
|
||||||
|
srv_records
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let connector =
|
||||||
|
create_connector_by_url(url.as_str(), &self.global_ctx, self.ip_version).await?;
|
||||||
|
Ok(connector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::TunnelConnector for DNSTunnelConnector {
|
||||||
|
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError> {
|
||||||
|
let mut conn = if self.addr.scheme() == "txt" {
|
||||||
|
self.handle_txt_record(self.addr.host_str().as_ref().unwrap())
|
||||||
|
.await
|
||||||
|
.with_context(|| "get txt record url failed")?
|
||||||
|
} else if self.addr.scheme() == "srv" {
|
||||||
|
self.handle_srv_record(self.addr.host_str().as_ref().unwrap())
|
||||||
|
.await
|
||||||
|
.with_context(|| "get srv record url failed")?
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"unsupported dns scheme: {}, expecting txt or srv",
|
||||||
|
self.addr.scheme()
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
};
|
||||||
|
let t = conn.connect().await?;
|
||||||
|
let info = t.info().unwrap_or_default();
|
||||||
|
Ok(Box::new(TunnelWithInfo::new(
|
||||||
|
t,
|
||||||
|
TunnelInfo {
|
||||||
|
local_addr: info.local_addr.clone(),
|
||||||
|
remote_addr: Some(self.addr.clone().into()),
|
||||||
|
tunnel_type: format!(
|
||||||
|
"{}-{}",
|
||||||
|
self.addr.scheme(),
|
||||||
|
info.remote_addr.unwrap_or_default()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_url(&self) -> url::Url {
|
||||||
|
self.addr.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bind_addrs(&mut self, addrs: Vec<SocketAddr>) {
|
||||||
|
self.bind_addrs = addrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ip_version(&mut self, ip_version: IpVersion) {
|
||||||
|
self.ip_version = ip_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::common::global_ctx::tests::get_mock_global_ctx;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_txt() {
|
||||||
|
let url = "txt://txt.easytier.cn";
|
||||||
|
let global_ctx = get_mock_global_ctx();
|
||||||
|
let mut connector = DNSTunnelConnector::new(url.parse().unwrap(), global_ctx);
|
||||||
|
connector.set_ip_version(IpVersion::V4);
|
||||||
|
for _ in 0..5 {
|
||||||
|
match connector.connect().await {
|
||||||
|
Ok(ret) => {
|
||||||
|
println!("{:?}", ret.info());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_srv() {
|
||||||
|
let url = "srv://easytier.cn";
|
||||||
|
let global_ctx = get_mock_global_ctx();
|
||||||
|
let mut connector = DNSTunnelConnector::new(url.parse().unwrap(), global_ctx);
|
||||||
|
connector.set_ip_version(IpVersion::V4);
|
||||||
|
for _ in 0..5 {
|
||||||
|
match connector.connect().await {
|
||||||
|
Ok(ret) => {
|
||||||
|
println!("{:?}", ret.info());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
320
easytier/src/connector/http_connector.rs
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
pin::Pin,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use http_req::request::{RedirectPolicy, Request};
|
||||||
|
use rand::seq::SliceRandom as _;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::{error::Error, global_ctx::ArcGlobalCtx},
|
||||||
|
tunnel::{IpVersion, Tunnel, TunnelConnector, TunnelError, ZCPacketSink, ZCPacketStream},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::proto::common::TunnelInfo;
|
||||||
|
|
||||||
|
use super::create_connector_by_url;
|
||||||
|
|
||||||
|
pub struct TunnelWithInfo {
|
||||||
|
inner: Box<dyn Tunnel>,
|
||||||
|
info: TunnelInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TunnelWithInfo {
|
||||||
|
pub fn new(inner: Box<dyn Tunnel>, info: TunnelInfo) -> Self {
|
||||||
|
Self { inner, info }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tunnel for TunnelWithInfo {
|
||||||
|
fn split(&self) -> (Pin<Box<dyn ZCPacketStream>>, Pin<Box<dyn ZCPacketSink>>) {
|
||||||
|
self.inner.split()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info(&self) -> Option<TunnelInfo> {
|
||||||
|
Some(self.info.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
enum HttpRedirectType {
|
||||||
|
Unknown,
|
||||||
|
// redirected url is in the path of new url
|
||||||
|
RedirectToQuery,
|
||||||
|
// redirected url is the entire new url
|
||||||
|
RedirectToUrl,
|
||||||
|
// redirected url is in the body of response
|
||||||
|
BodyUrls,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HttpTunnelConnector {
|
||||||
|
addr: url::Url,
|
||||||
|
bind_addrs: Vec<SocketAddr>,
|
||||||
|
ip_version: IpVersion,
|
||||||
|
global_ctx: ArcGlobalCtx,
|
||||||
|
redirect_type: HttpRedirectType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpTunnelConnector {
|
||||||
|
pub fn new(addr: url::Url, global_ctx: ArcGlobalCtx) -> Self {
|
||||||
|
Self {
|
||||||
|
addr,
|
||||||
|
bind_addrs: Vec::new(),
|
||||||
|
ip_version: IpVersion::Both,
|
||||||
|
global_ctx,
|
||||||
|
redirect_type: HttpRedirectType::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret)]
|
||||||
|
async fn handle_302_redirect(
|
||||||
|
&mut self,
|
||||||
|
new_url: url::Url,
|
||||||
|
url_str: &str,
|
||||||
|
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||||
|
// the url should be in following format:
|
||||||
|
// 1: http(s)://easytier.cn/?url=tcp://10.147.22.22:11010 (scheme is http, domain is ignored, path is splitted into proto type and addr)
|
||||||
|
// 2: http(s)://tcp://10.137.22.22:11010 (connector url is appended to the scheme)
|
||||||
|
// 3: tcp://10.137.22.22:11010 (scheme is protocol type, the url is used to construct a connector directly)
|
||||||
|
tracing::info!("redirect to {}", new_url);
|
||||||
|
let url = url::Url::parse(new_url.as_str())
|
||||||
|
.with_context(|| format!("parsing redirect url failed. url: {}", new_url))?;
|
||||||
|
if url.scheme() == "http" || url.scheme() == "https" {
|
||||||
|
let mut query = new_url
|
||||||
|
.query_pairs()
|
||||||
|
.filter_map(|x| url::Url::parse(&x.1).ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
query.shuffle(&mut rand::thread_rng());
|
||||||
|
if !query.is_empty() {
|
||||||
|
tracing::info!("try to create connector by url: {}", query[0]);
|
||||||
|
self.redirect_type = HttpRedirectType::RedirectToQuery;
|
||||||
|
return create_connector_by_url(
|
||||||
|
&query[0].to_string(),
|
||||||
|
&self.global_ctx,
|
||||||
|
self.ip_version,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
} else if let Some(new_url) = url_str
|
||||||
|
.strip_prefix(format!("{}://", url.scheme()).as_str())
|
||||||
|
.and_then(|x| Url::parse(x).ok())
|
||||||
|
{
|
||||||
|
// stripe the scheme and create connector by url
|
||||||
|
self.redirect_type = HttpRedirectType::RedirectToUrl;
|
||||||
|
return create_connector_by_url(
|
||||||
|
new_url.as_str(),
|
||||||
|
&self.global_ctx,
|
||||||
|
self.ip_version,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
return Err(Error::InvalidUrl(format!(
|
||||||
|
"no valid connector url found in url: {}",
|
||||||
|
url
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
self.redirect_type = HttpRedirectType::RedirectToUrl;
|
||||||
|
return create_connector_by_url(new_url.as_str(), &self.global_ctx, self.ip_version)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn handle_200_success(
|
||||||
|
&mut self,
|
||||||
|
body: &String,
|
||||||
|
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||||
|
// resp body should be line of connector urls, like:
|
||||||
|
// tcp://10.1.1.1:11010
|
||||||
|
// udp://10.1.1.1:11010
|
||||||
|
let mut lines = body
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim())
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.collect::<Vec<&str>>();
|
||||||
|
|
||||||
|
tracing::info!("get {} lines of connector urls", lines.len());
|
||||||
|
|
||||||
|
// shuffle the lines and pick the usable one
|
||||||
|
lines.shuffle(&mut rand::thread_rng());
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let url = url::Url::parse(line);
|
||||||
|
if url.is_err() {
|
||||||
|
tracing::warn!("invalid url: {}, skip it", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.redirect_type = HttpRedirectType::BodyUrls;
|
||||||
|
return create_connector_by_url(line, &self.global_ctx, self.ip_version).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::InvalidUrl(format!(
|
||||||
|
"no valid connector url found, response body: {}",
|
||||||
|
body
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(ret)]
|
||||||
|
pub async fn get_redirected_connector(
|
||||||
|
&mut self,
|
||||||
|
original_url: &str,
|
||||||
|
) -> Result<Box<dyn TunnelConnector>, Error> {
|
||||||
|
self.redirect_type = HttpRedirectType::Unknown;
|
||||||
|
tracing::info!("get_redirected_url: {}", original_url);
|
||||||
|
// Container for body of a response.
|
||||||
|
let body = Arc::new(RwLock::new(Vec::new()));
|
||||||
|
|
||||||
|
let original_url_clone = original_url.to_string();
|
||||||
|
let body_clone = body.clone();
|
||||||
|
let res = tokio::task::spawn_blocking(move || {
|
||||||
|
let uri = http_req::uri::Uri::try_from(original_url_clone.as_ref())
|
||||||
|
.with_context(|| format!("parsing url failed. url: {}", original_url_clone))?;
|
||||||
|
|
||||||
|
tracing::info!("sending http request to {}", uri);
|
||||||
|
|
||||||
|
Request::new(&uri)
|
||||||
|
.redirect_policy(RedirectPolicy::Limit(0))
|
||||||
|
.timeout(std::time::Duration::from_secs(20))
|
||||||
|
.send(&mut *body_clone.write().unwrap())
|
||||||
|
.with_context(|| format!("sending http request failed. url: {}", uri))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::InvalidUrl(format!("task join error: {}", e)))??;
|
||||||
|
|
||||||
|
let body = String::from_utf8_lossy(&body.read().unwrap()).to_string();
|
||||||
|
|
||||||
|
if res.status_code().is_redirect() {
|
||||||
|
let redirect_url = res
|
||||||
|
.headers()
|
||||||
|
.get("Location")
|
||||||
|
.ok_or_else(|| Error::InvalidUrl("no redirect address found".to_string()))?;
|
||||||
|
let new_url = url::Url::parse(redirect_url.as_str())
|
||||||
|
.with_context(|| format!("parsing redirect url failed. url: {}", redirect_url))?;
|
||||||
|
return self.handle_302_redirect(new_url, &redirect_url).await;
|
||||||
|
} else if res.status_code().is_success() {
|
||||||
|
return self.handle_200_success(&body).await;
|
||||||
|
} else {
|
||||||
|
return Err(Error::InvalidUrl(format!(
|
||||||
|
"unexpected response, resp: {:?}, body: {}",
|
||||||
|
res, body,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::TunnelConnector for HttpTunnelConnector {
|
||||||
|
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError> {
|
||||||
|
let mut conn = self
|
||||||
|
.get_redirected_connector(self.addr.to_string().as_str())
|
||||||
|
.await
|
||||||
|
.with_context(|| "get redirected url failed")?;
|
||||||
|
conn.set_ip_version(self.ip_version);
|
||||||
|
let t = conn.connect().await?;
|
||||||
|
let info = t.info().unwrap_or_default();
|
||||||
|
Ok(Box::new(TunnelWithInfo::new(
|
||||||
|
t,
|
||||||
|
TunnelInfo {
|
||||||
|
local_addr: info.local_addr.clone(),
|
||||||
|
remote_addr: Some(self.addr.clone().into()),
|
||||||
|
tunnel_type: format!(
|
||||||
|
"{:?}-{}",
|
||||||
|
self.redirect_type,
|
||||||
|
info.remote_addr.unwrap_or_default()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_url(&self) -> url::Url {
|
||||||
|
self.addr.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bind_addrs(&mut self, addrs: Vec<SocketAddr>) {
|
||||||
|
self.bind_addrs = addrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ip_version(&mut self, ip_version: IpVersion) {
|
||||||
|
self.ip_version = ip_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use tokio::{io::AsyncWriteExt as _, net::TcpListener};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
common::global_ctx::tests::get_mock_global_ctx,
|
||||||
|
tunnel::{tcp::TcpTunnelListener, TunnelConnector, TunnelListener},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
async fn run_http_redirect_server(port: u16, test_type: HttpRedirectType) -> Result<(), Error> {
|
||||||
|
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
|
||||||
|
let (mut stream, _) = listener.accept().await?;
|
||||||
|
|
||||||
|
match test_type {
|
||||||
|
HttpRedirectType::RedirectToQuery => {
|
||||||
|
let resp = "HTTP/1.1 301 Moved Permanently\r\nLocation: http://test.com/?url=tcp://127.0.0.1:25888\r\n\r\n";
|
||||||
|
stream.write_all(resp.as_bytes()).await?;
|
||||||
|
}
|
||||||
|
HttpRedirectType::RedirectToUrl => {
|
||||||
|
let resp =
|
||||||
|
"HTTP/1.1 301 Moved Permanently\r\nLocation: tcp://127.0.0.1:25888\r\n\r\n";
|
||||||
|
stream.write_all(resp.as_bytes()).await?;
|
||||||
|
}
|
||||||
|
HttpRedirectType::BodyUrls => {
|
||||||
|
let resp = "HTTP/1.1 200 OK\r\n\r\ntcp://127.0.0.1:25888";
|
||||||
|
stream.write_all(resp.as_bytes()).await?;
|
||||||
|
}
|
||||||
|
HttpRedirectType::Unknown => {
|
||||||
|
panic!("unexpected test type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest::rstest]
|
||||||
|
#[serial_test::serial(http_redirect_test)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn http_redirect_test(
|
||||||
|
// 1. 301 redirect
|
||||||
|
// 2. 200 success with valid connector urls
|
||||||
|
#[values(
|
||||||
|
HttpRedirectType::RedirectToQuery,
|
||||||
|
HttpRedirectType::RedirectToUrl,
|
||||||
|
HttpRedirectType::BodyUrls
|
||||||
|
)]
|
||||||
|
test_type: HttpRedirectType,
|
||||||
|
) {
|
||||||
|
let http_task = tokio::spawn(run_http_redirect_server(35888, test_type));
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
|
let test_url: url::Url = "http://127.0.0.1:35888".parse().unwrap();
|
||||||
|
let global_ctx = get_mock_global_ctx();
|
||||||
|
let mut flags = global_ctx.config.get_flags();
|
||||||
|
flags.bind_device = false;
|
||||||
|
global_ctx.config.set_flags(flags);
|
||||||
|
let mut connector = HttpTunnelConnector::new(test_url.clone(), global_ctx.clone());
|
||||||
|
|
||||||
|
let mut listener = TcpTunnelListener::new("tcp://0.0.0.0:25888".parse().unwrap());
|
||||||
|
listener.listen().await.unwrap();
|
||||||
|
|
||||||
|
let task = tokio::spawn(async move {
|
||||||
|
let _conn = listener.accept().await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let t = connector.connect().await.unwrap();
|
||||||
|
assert_eq!(connector.redirect_type, test_type);
|
||||||
|
let info = t.info().unwrap();
|
||||||
|
let remote_addr = info.remote_addr.unwrap();
|
||||||
|
assert_eq!(remote_addr, test_url.into());
|
||||||
|
|
||||||
|
tokio::join!(task).0.unwrap();
|
||||||
|
tokio::join!(http_task).0.unwrap().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,7 +106,7 @@ impl ManualConnectorManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_connector_by_url(&self, url: &str) -> Result<(), Error> {
|
pub async fn add_connector_by_url(&self, url: &str) -> Result<(), Error> {
|
||||||
self.add_connector(create_connector_by_url(url, &self.global_ctx).await?);
|
self.add_connector(create_connector_by_url(url, &self.global_ctx, IpVersion::Both).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,6 @@ impl ManualConnectorManager {
|
|||||||
ip_version: IpVersion,
|
ip_version: IpVersion,
|
||||||
) -> Result<ReconnResult, Error> {
|
) -> Result<ReconnResult, Error> {
|
||||||
let ip_collector = data.global_ctx.get_ip_collector();
|
let ip_collector = data.global_ctx.get_ip_collector();
|
||||||
let net_ns = data.net_ns.clone();
|
|
||||||
|
|
||||||
connector.lock().await.set_ip_version(ip_version);
|
connector.lock().await.set_ip_version(ip_version);
|
||||||
|
|
||||||
@@ -309,18 +308,11 @@ impl ManualConnectorManager {
|
|||||||
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
data.global_ctx.issue_event(GlobalCtxEvent::Connecting(
|
||||||
connector.lock().await.remote_url().clone(),
|
connector.lock().await.remote_url().clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let _g = net_ns.guard();
|
|
||||||
tracing::info!("reconnect try connect... conn: {:?}", connector);
|
tracing::info!("reconnect try connect... conn: {:?}", connector);
|
||||||
let tunnel = connector.lock().await.connect().await?;
|
let (peer_id, conn_id) = data
|
||||||
tracing::info!("reconnect get tunnel succ: {:?}", tunnel);
|
.peer_manager
|
||||||
assert_eq!(
|
.try_direct_connect(connector.lock().await.as_mut())
|
||||||
dead_url,
|
.await?;
|
||||||
tunnel.info().unwrap().remote_addr.unwrap().to_string(),
|
|
||||||
"info: {:?}",
|
|
||||||
tunnel.info()
|
|
||||||
);
|
|
||||||
let (peer_id, conn_id) = data.peer_manager.add_client_tunnel(tunnel).await?;
|
|
||||||
tracing::info!("reconnect succ: {} {} {}", peer_id, conn_id, dead_url);
|
tracing::info!("reconnect succ: {} {} {}", peer_id, conn_id, dead_url);
|
||||||
Ok(ReconnResult {
|
Ok(ReconnResult {
|
||||||
dead_url,
|
dead_url,
|
||||||
@@ -339,7 +331,7 @@ impl ManualConnectorManager {
|
|||||||
let mut ip_versions = vec![];
|
let mut ip_versions = vec![];
|
||||||
let u = url::Url::parse(&dead_url)
|
let u = url::Url::parse(&dead_url)
|
||||||
.with_context(|| format!("failed to parse connector url {:?}", dead_url))?;
|
.with_context(|| format!("failed to parse connector url {:?}", dead_url))?;
|
||||||
if u.scheme() == "ring" {
|
if u.scheme() == "ring" || u.scheme() == "txt" || u.scheme() == "srv" {
|
||||||
ip_versions.push(IpVersion::Both);
|
ip_versions.push(IpVersion::Both);
|
||||||
} else {
|
} else {
|
||||||
let addrs = u.socket_addrs(|| Some(1000))?;
|
let addrs = u.socket_addrs(|| Some(1000))?;
|
||||||
@@ -365,8 +357,12 @@ impl ManualConnectorManager {
|
|||||||
"cannot get ip from url"
|
"cannot get ip from url"
|
||||||
)));
|
)));
|
||||||
for ip_version in ip_versions {
|
for ip_version in ip_versions {
|
||||||
|
let use_long_timeout = dead_url.starts_with("http")
|
||||||
|
|| dead_url.starts_with("srv")
|
||||||
|
|| dead_url.starts_with("txt");
|
||||||
let ret = timeout(
|
let ret = timeout(
|
||||||
std::time::Duration::from_secs(1),
|
// allow http connector to wait longer
|
||||||
|
std::time::Duration::from_secs(if use_long_timeout { 20 } else { 2 }),
|
||||||
Self::conn_reconnect_with_ip_version(
|
Self::conn_reconnect_with_ip_version(
|
||||||
data.clone(),
|
data.clone(),
|
||||||
dead_url.clone(),
|
dead_url.clone(),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use http_connector::HttpTunnelConnector;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
use crate::tunnel::quic::QUICTunnelConnector;
|
use crate::tunnel::quic::QUICTunnelConnector;
|
||||||
#[cfg(feature = "wireguard")]
|
#[cfg(feature = "wireguard")]
|
||||||
@@ -11,7 +13,7 @@ use crate::{
|
|||||||
common::{error::Error, global_ctx::ArcGlobalCtx, network::IPCollector},
|
common::{error::Error, global_ctx::ArcGlobalCtx, network::IPCollector},
|
||||||
tunnel::{
|
tunnel::{
|
||||||
check_scheme_and_get_socket_addr, ring::RingTunnelConnector, tcp::TcpTunnelConnector,
|
check_scheme_and_get_socket_addr, ring::RingTunnelConnector, tcp::TcpTunnelConnector,
|
||||||
udp::UdpTunnelConnector, TunnelConnector,
|
udp::UdpTunnelConnector, IpVersion, TunnelConnector,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,6 +21,9 @@ pub mod direct;
|
|||||||
pub mod manual;
|
pub mod manual;
|
||||||
pub mod udp_hole_punch;
|
pub mod udp_hole_punch;
|
||||||
|
|
||||||
|
pub mod dns_connector;
|
||||||
|
pub mod http_connector;
|
||||||
|
|
||||||
async fn set_bind_addr_for_peer_connector(
|
async fn set_bind_addr_for_peer_connector(
|
||||||
connector: &mut (impl TunnelConnector + ?Sized),
|
connector: &mut (impl TunnelConnector + ?Sized),
|
||||||
is_ipv4: bool,
|
is_ipv4: bool,
|
||||||
@@ -50,11 +55,12 @@ async fn set_bind_addr_for_peer_connector(
|
|||||||
pub async fn create_connector_by_url(
|
pub async fn create_connector_by_url(
|
||||||
url: &str,
|
url: &str,
|
||||||
global_ctx: &ArcGlobalCtx,
|
global_ctx: &ArcGlobalCtx,
|
||||||
|
ip_version: IpVersion,
|
||||||
) -> Result<Box<dyn TunnelConnector + 'static>, Error> {
|
) -> Result<Box<dyn TunnelConnector + 'static>, Error> {
|
||||||
let url = url::Url::parse(url).map_err(|_| Error::InvalidUrl(url.to_owned()))?;
|
let url = url::Url::parse(url).map_err(|_| Error::InvalidUrl(url.to_owned()))?;
|
||||||
match url.scheme() {
|
let mut connector: Box<dyn TunnelConnector + 'static> = match url.scheme() {
|
||||||
"tcp" => {
|
"tcp" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "tcp")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "tcp", ip_version)?;
|
||||||
let mut connector = TcpTunnelConnector::new(url);
|
let mut connector = TcpTunnelConnector::new(url);
|
||||||
if global_ctx.config.get_flags().bind_device {
|
if global_ctx.config.get_flags().bind_device {
|
||||||
set_bind_addr_for_peer_connector(
|
set_bind_addr_for_peer_connector(
|
||||||
@@ -64,10 +70,10 @@ pub async fn create_connector_by_url(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
return Ok(Box::new(connector));
|
Box::new(connector)
|
||||||
}
|
}
|
||||||
"udp" => {
|
"udp" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "udp")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "udp", ip_version)?;
|
||||||
let mut connector = UdpTunnelConnector::new(url);
|
let mut connector = UdpTunnelConnector::new(url);
|
||||||
if global_ctx.config.get_flags().bind_device {
|
if global_ctx.config.get_flags().bind_device {
|
||||||
set_bind_addr_for_peer_connector(
|
set_bind_addr_for_peer_connector(
|
||||||
@@ -77,16 +83,20 @@ pub async fn create_connector_by_url(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
return Ok(Box::new(connector));
|
Box::new(connector)
|
||||||
|
}
|
||||||
|
"http" | "https" => {
|
||||||
|
let connector = HttpTunnelConnector::new(url, global_ctx.clone());
|
||||||
|
Box::new(connector)
|
||||||
}
|
}
|
||||||
"ring" => {
|
"ring" => {
|
||||||
check_scheme_and_get_socket_addr::<uuid::Uuid>(&url, "ring")?;
|
check_scheme_and_get_socket_addr::<uuid::Uuid>(&url, "ring", IpVersion::Both)?;
|
||||||
let connector = RingTunnelConnector::new(url);
|
let connector = RingTunnelConnector::new(url);
|
||||||
return Ok(Box::new(connector));
|
Box::new(connector)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
"quic" => {
|
"quic" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "quic")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "quic", ip_version)?;
|
||||||
let mut connector = QUICTunnelConnector::new(url);
|
let mut connector = QUICTunnelConnector::new(url);
|
||||||
if global_ctx.config.get_flags().bind_device {
|
if global_ctx.config.get_flags().bind_device {
|
||||||
set_bind_addr_for_peer_connector(
|
set_bind_addr_for_peer_connector(
|
||||||
@@ -96,11 +106,11 @@ pub async fn create_connector_by_url(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
return Ok(Box::new(connector));
|
Box::new(connector)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wireguard")]
|
#[cfg(feature = "wireguard")]
|
||||||
"wg" => {
|
"wg" => {
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "wg")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&url, "wg", ip_version)?;
|
||||||
let nid = global_ctx.get_network_identity();
|
let nid = global_ctx.get_network_identity();
|
||||||
let wg_config = WgConfig::new_from_network_identity(
|
let wg_config = WgConfig::new_from_network_identity(
|
||||||
&nid.network_name,
|
&nid.network_name,
|
||||||
@@ -115,12 +125,12 @@ pub async fn create_connector_by_url(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
return Ok(Box::new(connector));
|
Box::new(connector)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "websocket")]
|
#[cfg(feature = "websocket")]
|
||||||
"ws" | "wss" => {
|
"ws" | "wss" => {
|
||||||
use crate::tunnel::{FromUrl, IpVersion};
|
use crate::tunnel::FromUrl;
|
||||||
let dst_addr = SocketAddr::from_url(url.clone(), IpVersion::Both)?;
|
let dst_addr = SocketAddr::from_url(url.clone(), ip_version)?;
|
||||||
let mut connector = crate::tunnel::websocket::WSTunnelConnector::new(url);
|
let mut connector = crate::tunnel::websocket::WSTunnelConnector::new(url);
|
||||||
if global_ctx.config.get_flags().bind_device {
|
if global_ctx.config.get_flags().bind_device {
|
||||||
set_bind_addr_for_peer_connector(
|
set_bind_addr_for_peer_connector(
|
||||||
@@ -130,10 +140,17 @@ pub async fn create_connector_by_url(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
return Ok(Box::new(connector));
|
Box::new(connector)
|
||||||
|
}
|
||||||
|
"txt" | "srv" => {
|
||||||
|
let connector = dns_connector::DNSTunnelConnector::new(url, global_ctx.clone());
|
||||||
|
Box::new(connector)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidUrl(url.into()));
|
return Err(Error::InvalidUrl(url.into()));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
connector.set_ip_version(ip_version);
|
||||||
|
|
||||||
|
Ok(connector)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ impl UdpHolePunchListener {
|
|||||||
tracing::warn!(?conn, "udp hole punching listener got peer connection");
|
tracing::warn!(?conn, "udp hole punching listener got peer connection");
|
||||||
let peer_mgr = peer_mgr.clone();
|
let peer_mgr = peer_mgr.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = peer_mgr.add_tunnel_as_server(conn).await {
|
if let Err(e) = peer_mgr.add_tunnel_as_server(conn, false).await {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
?e,
|
?e,
|
||||||
"failed to add tunnel as server in hole punch listener"
|
"failed to add tunnel as server in hole punch listener"
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ impl PunchSymToConeHoleClient {
|
|||||||
let public_ips: Vec<Ipv4Addr> = stun_info
|
let public_ips: Vec<Ipv4Addr> = stun_info
|
||||||
.public_ip
|
.public_ip
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.parse().unwrap())
|
.filter_map(|x| x.parse().ok())
|
||||||
.collect();
|
.collect();
|
||||||
if public_ips.is_empty() {
|
if public_ips.is_empty() {
|
||||||
return Err(anyhow::anyhow!("failed to get public ips"));
|
return Err(anyhow::anyhow!("failed to get public ips"));
|
||||||
@@ -580,13 +580,15 @@ pub mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
println!("start punching {:?}", p_a.list_routes().await);
|
||||||
|
|
||||||
wait_for_condition(
|
wait_for_condition(
|
||||||
|| async {
|
|| async {
|
||||||
wait_route_appear_with_cost(p_a.clone(), p_c.my_peer_id(), Some(1))
|
wait_route_appear_with_cost(p_a.clone(), p_c.my_peer_id(), Some(1))
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
},
|
},
|
||||||
Duration::from_secs(5),
|
Duration::from_secs(10),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
println!("{:?}", p_a.list_routes().await);
|
println!("{:?}", p_a.list_routes().await);
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
ffi::OsString, fmt::Write, net::SocketAddr, path::PathBuf, sync::Mutex, time::Duration, vec,
|
ffi::OsString,
|
||||||
|
fmt::Write,
|
||||||
|
net::{IpAddr, SocketAddr},
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Mutex,
|
||||||
|
time::Duration,
|
||||||
|
vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Ok};
|
use anyhow::Context;
|
||||||
use clap::{command, Args, Parser, Subcommand};
|
use clap::{command, Args, Parser, Subcommand};
|
||||||
use humansize::format_size;
|
use humansize::format_size;
|
||||||
use service_manager::*;
|
use service_manager::*;
|
||||||
@@ -20,7 +26,8 @@ use easytier::{
|
|||||||
DumpRouteRequest, GetVpnPortalInfoRequest, ListConnectorRequest,
|
DumpRouteRequest, GetVpnPortalInfoRequest, ListConnectorRequest,
|
||||||
ListForeignNetworkRequest, ListGlobalForeignNetworkRequest, ListPeerRequest,
|
ListForeignNetworkRequest, ListGlobalForeignNetworkRequest, ListPeerRequest,
|
||||||
ListPeerResponse, ListRouteRequest, ListRouteResponse, NodeInfo, PeerManageRpc,
|
ListPeerResponse, ListRouteRequest, ListRouteResponse, NodeInfo, PeerManageRpc,
|
||||||
PeerManageRpcClientFactory, ShowNodeInfoRequest, VpnPortalRpc,
|
PeerManageRpcClientFactory, ShowNodeInfoRequest, TcpProxyEntryState,
|
||||||
|
TcpProxyEntryTransportType, TcpProxyRpc, TcpProxyRpcClientFactory, VpnPortalRpc,
|
||||||
VpnPortalRpcClientFactory,
|
VpnPortalRpcClientFactory,
|
||||||
},
|
},
|
||||||
common::NatType,
|
common::NatType,
|
||||||
@@ -50,14 +57,24 @@ struct Cli {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum SubCommand {
|
enum SubCommand {
|
||||||
|
#[command(about = "show peers info")]
|
||||||
Peer(PeerArgs),
|
Peer(PeerArgs),
|
||||||
|
#[command(about = "manage connectors")]
|
||||||
Connector(ConnectorArgs),
|
Connector(ConnectorArgs),
|
||||||
|
#[command(about = "do stun test")]
|
||||||
Stun,
|
Stun,
|
||||||
|
#[command(about = "show route info")]
|
||||||
Route(RouteArgs),
|
Route(RouteArgs),
|
||||||
|
#[command(about = "show global peers info")]
|
||||||
PeerCenter,
|
PeerCenter,
|
||||||
|
#[command(about = "show vpn portal (wireguard) info")]
|
||||||
VpnPortal,
|
VpnPortal,
|
||||||
|
#[command(about = "inspect self easytier-core status")]
|
||||||
Node(NodeArgs),
|
Node(NodeArgs),
|
||||||
|
#[command(about = "manage easytier-core as a system service")]
|
||||||
Service(ServiceArgs),
|
Service(ServiceArgs),
|
||||||
|
#[command(about = "show tcp/kcp proxy status")]
|
||||||
|
Proxy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
@@ -114,7 +131,9 @@ enum ConnectorSubCommand {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum NodeSubCommand {
|
enum NodeSubCommand {
|
||||||
|
#[command(about = "show node info")]
|
||||||
Info,
|
Info,
|
||||||
|
#[command(about = "show node config")]
|
||||||
Config,
|
Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +154,15 @@ struct ServiceArgs {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum ServiceSubCommand {
|
enum ServiceSubCommand {
|
||||||
|
#[command(about = "register easytier-core as a system service")]
|
||||||
Install(InstallArgs),
|
Install(InstallArgs),
|
||||||
|
#[command(about = "unregister easytier-core system service")]
|
||||||
Uninstall,
|
Uninstall,
|
||||||
|
#[command(about = "check easytier-core system service status")]
|
||||||
Status,
|
Status,
|
||||||
|
#[command(about = "start easytier-core system service")]
|
||||||
Start,
|
Start,
|
||||||
|
#[command(about = "stop easytier-core system service")]
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,13 +177,17 @@ struct InstallArgs {
|
|||||||
#[arg(long, default_value = "false")]
|
#[arg(long, default_value = "false")]
|
||||||
disable_autostart: bool,
|
disable_autostart: bool,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long, help = "path to easytier-core binary")]
|
||||||
core_path: Option<PathBuf>,
|
core_path: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
service_work_dir: Option<PathBuf>,
|
service_work_dir: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
#[arg(
|
||||||
|
trailing_var_arg = true,
|
||||||
|
allow_hyphen_values = true,
|
||||||
|
help = "args to pass to easytier-core"
|
||||||
|
)]
|
||||||
core_args: Option<Vec<OsString>>,
|
core_args: Option<Vec<OsString>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +249,19 @@ impl CommandHandler {
|
|||||||
.with_context(|| "failed to get vpn portal client")?)
|
.with_context(|| "failed to get vpn portal client")?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_tcp_proxy_client(
|
||||||
|
&self,
|
||||||
|
transport_type: &str,
|
||||||
|
) -> Result<Box<dyn TcpProxyRpc<Controller = BaseController>>, Error> {
|
||||||
|
Ok(self
|
||||||
|
.client
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.scoped_client::<TcpProxyRpcClientFactory<BaseController>>(transport_type.to_string())
|
||||||
|
.await
|
||||||
|
.with_context(|| "failed to get vpn portal client")?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_peers(&self) -> Result<ListPeerResponse, Error> {
|
async fn list_peers(&self) -> Result<ListPeerResponse, Error> {
|
||||||
let client = self.get_peer_manager_client().await?;
|
let client = self.get_peer_manager_client().await?;
|
||||||
let request = ListPeerRequest::default();
|
let request = ListPeerRequest::default();
|
||||||
@@ -276,7 +317,11 @@ impl CommandHandler {
|
|||||||
ipv4: route.ipv4_addr.map(|ip| ip.to_string()).unwrap_or_default(),
|
ipv4: route.ipv4_addr.map(|ip| ip.to_string()).unwrap_or_default(),
|
||||||
hostname: route.hostname.clone(),
|
hostname: route.hostname.clone(),
|
||||||
cost: cost_to_str(route.cost),
|
cost: cost_to_str(route.cost),
|
||||||
lat_ms: float_to_str(p.get_latency_ms().unwrap_or(0.0), 3),
|
lat_ms: if route.cost == 1 {
|
||||||
|
float_to_str(p.get_latency_ms().unwrap_or(0.0), 3)
|
||||||
|
} else {
|
||||||
|
route.path_latency_latency_first().to_string()
|
||||||
|
},
|
||||||
loss_rate: float_to_str(p.get_loss_rate().unwrap_or(0.0), 3),
|
loss_rate: float_to_str(p.get_loss_rate().unwrap_or(0.0), 3),
|
||||||
rx_bytes: format_size(p.get_rx_bytes().unwrap_or(0), humansize::DECIMAL),
|
rx_bytes: format_size(p.get_rx_bytes().unwrap_or(0), humansize::DECIMAL),
|
||||||
tx_bytes: format_size(p.get_tx_bytes().unwrap_or(0), humansize::DECIMAL),
|
tx_bytes: format_size(p.get_tx_bytes().unwrap_or(0), humansize::DECIMAL),
|
||||||
@@ -647,12 +692,22 @@ impl Service {
|
|||||||
environment: None,
|
environment: None,
|
||||||
};
|
};
|
||||||
if self.status()? != ServiceStatus::NotInstalled {
|
if self.status()? != ServiceStatus::NotInstalled {
|
||||||
return Err(anyhow::anyhow!("Service is already installed"));
|
return Err(anyhow::anyhow!(
|
||||||
|
"Service is already installed! Service Name: {}",
|
||||||
|
self.lable
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.service_manager
|
self.service_manager
|
||||||
.install(ctx)
|
.install(ctx.clone())
|
||||||
.map_err(|e| anyhow::anyhow!("failed to install service: {}", e))
|
.map_err(|e| anyhow::anyhow!("failed to install service: {:?}", e))?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Service installed successfully! Service Name: {}",
|
||||||
|
self.lable
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uninstall(&self) -> Result<(), Error> {
|
pub fn uninstall(&self) -> Result<(), Error> {
|
||||||
@@ -769,7 +824,8 @@ impl Service {
|
|||||||
writeln!(unit_content, "Type=simple")?;
|
writeln!(unit_content, "Type=simple")?;
|
||||||
writeln!(unit_content, "WorkingDirectory={work_dir}")?;
|
writeln!(unit_content, "WorkingDirectory={work_dir}")?;
|
||||||
writeln!(unit_content, "ExecStart={target_app} {args}")?;
|
writeln!(unit_content, "ExecStart={target_app} {args}")?;
|
||||||
writeln!(unit_content, "Restart=Always")?;
|
writeln!(unit_content, "Restart=always")?;
|
||||||
|
writeln!(unit_content, "RestartSec=1")?;
|
||||||
writeln!(unit_content, "LimitNOFILE=infinity")?;
|
writeln!(unit_content, "LimitNOFILE=infinity")?;
|
||||||
writeln!(unit_content)?;
|
writeln!(unit_content)?;
|
||||||
writeln!(unit_content, "[Install]")?;
|
writeln!(unit_content, "[Install]")?;
|
||||||
@@ -990,6 +1046,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
match sub_cmd.sub_command {
|
match sub_cmd.sub_command {
|
||||||
Some(NodeSubCommand::Info) | None => {
|
Some(NodeSubCommand::Info) | None => {
|
||||||
let stun_info = node_info.stun_info.clone().unwrap_or_default();
|
let stun_info = node_info.stun_info.clone().unwrap_or_default();
|
||||||
|
let ip_list = node_info.ip_list.clone().unwrap_or_default();
|
||||||
|
|
||||||
let mut builder = tabled::builder::Builder::default();
|
let mut builder = tabled::builder::Builder::default();
|
||||||
builder.push_record(vec!["Virtual IP", node_info.ipv4_addr.as_str()]);
|
builder.push_record(vec!["Virtual IP", node_info.ipv4_addr.as_str()]);
|
||||||
@@ -999,11 +1056,32 @@ async fn main() -> Result<(), Error> {
|
|||||||
node_info.proxy_cidrs.join(", ").as_str(),
|
node_info.proxy_cidrs.join(", ").as_str(),
|
||||||
]);
|
]);
|
||||||
builder.push_record(vec!["Peer ID", node_info.peer_id.to_string().as_str()]);
|
builder.push_record(vec!["Peer ID", node_info.peer_id.to_string().as_str()]);
|
||||||
builder.push_record(vec!["Public IP", stun_info.public_ip.join(", ").as_str()]);
|
stun_info.public_ip.iter().for_each(|ip| {
|
||||||
|
let Ok(ip) = ip.parse::<IpAddr>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if ip.is_ipv4() {
|
||||||
|
builder.push_record(vec!["Public IPv4", ip.to_string().as_str()]);
|
||||||
|
} else {
|
||||||
|
builder.push_record(vec!["Public IPv6", ip.to_string().as_str()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
builder.push_record(vec![
|
builder.push_record(vec![
|
||||||
"UDP Stun Type",
|
"UDP Stun Type",
|
||||||
format!("{:?}", stun_info.udp_nat_type()).as_str(),
|
format!("{:?}", stun_info.udp_nat_type()).as_str(),
|
||||||
]);
|
]);
|
||||||
|
ip_list.interface_ipv4s.iter().for_each(|ip| {
|
||||||
|
builder.push_record(vec![
|
||||||
|
"Interface IPv4",
|
||||||
|
format!("{}", ip.to_string()).as_str(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
ip_list.interface_ipv6s.iter().for_each(|ip| {
|
||||||
|
builder.push_record(vec![
|
||||||
|
"Interface IPv6",
|
||||||
|
format!("{}", ip.to_string()).as_str(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
for (idx, l) in node_info.listeners.iter().enumerate() {
|
for (idx, l) in node_info.listeners.iter().enumerate() {
|
||||||
if l.starts_with("ring") {
|
if l.starts_with("ring") {
|
||||||
continue;
|
continue;
|
||||||
@@ -1088,6 +1166,57 @@ async fn main() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SubCommand::Proxy => {
|
||||||
|
let mut entries = vec![];
|
||||||
|
let client = handler.get_tcp_proxy_client("tcp").await?;
|
||||||
|
let ret = client
|
||||||
|
.list_tcp_proxy_entry(BaseController::default(), Default::default())
|
||||||
|
.await;
|
||||||
|
entries.extend(ret.unwrap_or_default().entries);
|
||||||
|
|
||||||
|
let client = handler.get_tcp_proxy_client("kcp_src").await?;
|
||||||
|
let ret = client
|
||||||
|
.list_tcp_proxy_entry(BaseController::default(), Default::default())
|
||||||
|
.await;
|
||||||
|
entries.extend(ret.unwrap_or_default().entries);
|
||||||
|
|
||||||
|
let client = handler.get_tcp_proxy_client("kcp_dst").await?;
|
||||||
|
let ret = client
|
||||||
|
.list_tcp_proxy_entry(BaseController::default(), Default::default())
|
||||||
|
.await;
|
||||||
|
entries.extend(ret.unwrap_or_default().entries);
|
||||||
|
|
||||||
|
#[derive(tabled::Tabled)]
|
||||||
|
struct TableItem {
|
||||||
|
src: String,
|
||||||
|
dst: String,
|
||||||
|
start_time: String,
|
||||||
|
state: String,
|
||||||
|
transport_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let table_rows = entries
|
||||||
|
.iter()
|
||||||
|
.map(|e| TableItem {
|
||||||
|
src: SocketAddr::from(e.src.unwrap_or_default()).to_string(),
|
||||||
|
dst: SocketAddr::from(e.dst.unwrap_or_default()).to_string(),
|
||||||
|
start_time: chrono::DateTime::<chrono::Utc>::from_timestamp_millis(
|
||||||
|
(e.start_time * 1000) as i64,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.with_timezone(&chrono::Local)
|
||||||
|
.format("%Y-%m-%d %H:%M:%S")
|
||||||
|
.to_string(),
|
||||||
|
state: format!("{:?}", TcpProxyEntryState::try_from(e.state).unwrap()),
|
||||||
|
transport_type: format!(
|
||||||
|
"{:?}",
|
||||||
|
TcpProxyEntryTransportType::try_from(e.transport_type).unwrap()
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
println!("{}", tabled::Table::new(table_rows).with(Style::modern()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ extern crate rust_i18n;
|
|||||||
use std::{
|
use std::{
|
||||||
net::{Ipv4Addr, SocketAddr},
|
net::{Ipv4Addr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -19,12 +20,17 @@ use easytier::{
|
|||||||
TomlConfigLoader, VpnPortalConfig,
|
TomlConfigLoader, VpnPortalConfig,
|
||||||
},
|
},
|
||||||
constants::EASYTIER_VERSION,
|
constants::EASYTIER_VERSION,
|
||||||
global_ctx::{EventBusSubscriber, GlobalCtxEvent},
|
global_ctx::{EventBusSubscriber, GlobalCtx, GlobalCtxEvent},
|
||||||
scoped_task::ScopedTask,
|
scoped_task::ScopedTask,
|
||||||
|
stun::MockStunInfoCollector,
|
||||||
},
|
},
|
||||||
|
connector::{create_connector_by_url, dns_connector::DNSTunnelConnector},
|
||||||
launcher,
|
launcher,
|
||||||
proto::{self, common::CompressionAlgoPb},
|
proto::{
|
||||||
tunnel::udp::UdpTunnelConnector,
|
self,
|
||||||
|
common::{CompressionAlgoPb, NatType},
|
||||||
|
},
|
||||||
|
tunnel::{IpVersion, PROTO_PORT_OFFSET},
|
||||||
utils::{init_logger, setup_panic_handler},
|
utils::{init_logger, setup_panic_handler},
|
||||||
web_client,
|
web_client,
|
||||||
};
|
};
|
||||||
@@ -236,6 +242,13 @@ struct Cli {
|
|||||||
)]
|
)]
|
||||||
enable_exit_node: bool,
|
enable_exit_node: bool,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = t!("core_clap.proxy_forward_by_system").to_string(),
|
||||||
|
default_value = "false"
|
||||||
|
)]
|
||||||
|
proxy_forward_by_system: bool,
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = t!("core_clap.no_tun").to_string(),
|
help = t!("core_clap.no_tun").to_string(),
|
||||||
@@ -295,12 +308,6 @@ struct Cli {
|
|||||||
)]
|
)]
|
||||||
socks5: Option<u16>,
|
socks5: Option<u16>,
|
||||||
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
help = t!("core_clap.ipv6_listener").to_string()
|
|
||||||
)]
|
|
||||||
ipv6_listener: Option<String>,
|
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = t!("core_clap.compression").to_string(),
|
help = t!("core_clap.compression").to_string(),
|
||||||
@@ -333,8 +340,6 @@ rust_i18n::i18n!("locales", fallback = "en");
|
|||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
fn parse_listeners(no_listener: bool, listeners: Vec<String>) -> anyhow::Result<Vec<String>> {
|
fn parse_listeners(no_listener: bool, listeners: Vec<String>) -> anyhow::Result<Vec<String>> {
|
||||||
let proto_port_offset = vec![("tcp", 0), ("udp", 0), ("wg", 1), ("ws", 1), ("wss", 2)];
|
|
||||||
|
|
||||||
if no_listener || listeners.is_empty() {
|
if no_listener || listeners.is_empty() {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
@@ -343,8 +348,8 @@ impl Cli {
|
|||||||
let mut listeners: Vec<String> = Vec::new();
|
let mut listeners: Vec<String> = Vec::new();
|
||||||
if origin_listners.len() == 1 {
|
if origin_listners.len() == 1 {
|
||||||
if let Ok(port) = origin_listners[0].parse::<u16>() {
|
if let Ok(port) = origin_listners[0].parse::<u16>() {
|
||||||
for (proto, offset) in proto_port_offset {
|
for (proto, offset) in PROTO_PORT_OFFSET {
|
||||||
listeners.push(format!("{}://0.0.0.0:{}", proto, port + offset));
|
listeners.push(format!("{}://0.0.0.0:{}", proto, port + *offset));
|
||||||
}
|
}
|
||||||
return Ok(listeners);
|
return Ok(listeners);
|
||||||
}
|
}
|
||||||
@@ -359,7 +364,7 @@ impl Cli {
|
|||||||
panic!("failed to parse listener: {}", l);
|
panic!("failed to parse listener: {}", l);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let Some((proto, offset)) = proto_port_offset
|
let Some((proto, offset)) = PROTO_PORT_OFFSET
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(proto, _)| *proto == proto_port[0])
|
.find(|(proto, _)| *proto == proto_port[0])
|
||||||
else {
|
else {
|
||||||
@@ -556,6 +561,7 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
|||||||
f.mtu = mtu as u32;
|
f.mtu = mtu as u32;
|
||||||
}
|
}
|
||||||
f.enable_exit_node = cli.enable_exit_node;
|
f.enable_exit_node = cli.enable_exit_node;
|
||||||
|
f.proxy_forward_by_system = cli.proxy_forward_by_system;
|
||||||
f.no_tun = cli.no_tun || cfg!(not(feature = "tun"));
|
f.no_tun = cli.no_tun || cfg!(not(feature = "tun"));
|
||||||
f.use_smoltcp = cli.use_smoltcp;
|
f.use_smoltcp = cli.use_smoltcp;
|
||||||
if let Some(wl) = cli.relay_network_whitelist.as_ref() {
|
if let Some(wl) = cli.relay_network_whitelist.as_ref() {
|
||||||
@@ -564,11 +570,6 @@ impl TryFrom<&Cli> for TomlConfigLoader {
|
|||||||
f.disable_p2p = cli.disable_p2p;
|
f.disable_p2p = cli.disable_p2p;
|
||||||
f.disable_udp_hole_punching = cli.disable_udp_hole_punching;
|
f.disable_udp_hole_punching = cli.disable_udp_hole_punching;
|
||||||
f.relay_all_peer_rpc = cli.relay_all_peer_rpc;
|
f.relay_all_peer_rpc = cli.relay_all_peer_rpc;
|
||||||
if let Some(ipv6_listener) = cli.ipv6_listener.as_ref() {
|
|
||||||
f.ipv6_listener = ipv6_listener
|
|
||||||
.parse()
|
|
||||||
.with_context(|| format!("failed to parse ipv6 listener: {}", ipv6_listener))?
|
|
||||||
}
|
|
||||||
f.multi_thread = cli.multi_thread;
|
f.multi_thread = cli.multi_thread;
|
||||||
f.data_compress_algo = match cli.compression.as_str() {
|
f.data_compress_algo = match cli.compression.as_str() {
|
||||||
"none" => CompressionAlgoPb::None,
|
"none" => CompressionAlgoPb::None,
|
||||||
@@ -860,8 +861,20 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
|||||||
panic!("empty token");
|
panic!("empty token");
|
||||||
}
|
}
|
||||||
|
|
||||||
let _wc = web_client::WebClient::new(UdpTunnelConnector::new(c_url), token.to_string());
|
let config = TomlConfigLoader::default();
|
||||||
|
let global_ctx = Arc::new(GlobalCtx::new(config));
|
||||||
|
global_ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||||
|
udp_nat_type: NatType::Unknown,
|
||||||
|
}));
|
||||||
|
let mut flags = global_ctx.get_flags();
|
||||||
|
flags.bind_device = false;
|
||||||
|
global_ctx.set_flags(flags);
|
||||||
|
let _wc = web_client::WebClient::new(
|
||||||
|
create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?,
|
||||||
|
token.to_string(),
|
||||||
|
);
|
||||||
tokio::signal::ctrl_c().await.unwrap();
|
tokio::signal::ctrl_c().await.unwrap();
|
||||||
|
DNSTunnelConnector::new("".parse().unwrap(), global_ctx);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use pnet::packet::{
|
use pnet::packet::{
|
||||||
icmp::{self, echo_reply::MutableEchoReplyPacket, IcmpCode, IcmpTypes, MutableIcmpPacket},
|
icmp::{self, echo_reply::MutableEchoReplyPacket, IcmpCode, IcmpTypes, MutableIcmpPacket},
|
||||||
ip::IpNextHeaderProtocols,
|
ip::IpNextHeaderProtocols,
|
||||||
@@ -212,7 +213,7 @@ impl IcmpProxy {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("create icmp socket failed: {:?}", e);
|
tracing::warn!("create icmp socket failed: {:?}", e);
|
||||||
if !self.global_ctx.no_tun() {
|
if !self.global_ctx.no_tun() {
|
||||||
return Err(e);
|
return Err(anyhow::anyhow!("create icmp socket failed: {:?}", e).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,10 +282,15 @@ impl IcmpProxy {
|
|||||||
dst_ip: Ipv4Addr,
|
dst_ip: Ipv4Addr,
|
||||||
icmp_packet: &icmp::echo_request::EchoRequestPacket,
|
icmp_packet: &icmp::echo_request::EchoRequestPacket,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.socket.lock().unwrap().as_ref().unwrap().send_to(
|
self.socket
|
||||||
icmp_packet.packet(),
|
.lock()
|
||||||
&SocketAddrV4::new(dst_ip.into(), 0).into(),
|
.unwrap()
|
||||||
)?;
|
.as_ref()
|
||||||
|
.with_context(|| "icmp socket not created")?
|
||||||
|
.send_to(
|
||||||
|
icmp_packet.packet(),
|
||||||
|
&SocketAddrV4::new(dst_ip.into(), 0).into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
sync::Arc,
|
sync::{Arc, Weak},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use dashmap::DashMap;
|
||||||
use kcp_sys::{
|
use kcp_sys::{
|
||||||
endpoint::{KcpEndpoint, KcpPacketReceiver},
|
endpoint::{ConnId, KcpEndpoint, KcpPacketReceiver},
|
||||||
ffi_safe::KcpConfig,
|
ffi_safe::KcpConfig,
|
||||||
packet_def::KcpPacket,
|
packet_def::KcpPacket,
|
||||||
stream::KcpStream,
|
stream::KcpStream,
|
||||||
@@ -31,7 +32,14 @@ use crate::{
|
|||||||
global_ctx::{ArcGlobalCtx, GlobalCtx},
|
global_ctx::{ArcGlobalCtx, GlobalCtx},
|
||||||
},
|
},
|
||||||
peers::{peer_manager::PeerManager, NicPacketFilter, PeerPacketFilter},
|
peers::{peer_manager::PeerManager, NicPacketFilter, PeerPacketFilter},
|
||||||
proto::peer_rpc::KcpConnData,
|
proto::{
|
||||||
|
cli::{
|
||||||
|
ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState,
|
||||||
|
TcpProxyEntryTransportType, TcpProxyRpc,
|
||||||
|
},
|
||||||
|
peer_rpc::KcpConnData,
|
||||||
|
rpc_types::{self, controller::BaseController},
|
||||||
|
},
|
||||||
tunnel::packet_def::{PacketType, PeerManagerHeader, ZCPacket},
|
tunnel::packet_def::{PacketType, PeerManagerHeader, ZCPacket},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,8 +114,9 @@ pub struct NatDstKcpConnector {
|
|||||||
impl NatDstConnector for NatDstKcpConnector {
|
impl NatDstConnector for NatDstKcpConnector {
|
||||||
type DstStream = KcpStream;
|
type DstStream = KcpStream;
|
||||||
|
|
||||||
async fn connect(&self, nat_dst: SocketAddr) -> Result<Self::DstStream> {
|
async fn connect(&self, src: SocketAddr, nat_dst: SocketAddr) -> Result<Self::DstStream> {
|
||||||
let conn_data = KcpConnData {
|
let conn_data = KcpConnData {
|
||||||
|
src: Some(src.into()),
|
||||||
dst: Some(nat_dst.into()),
|
dst: Some(nat_dst.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,9 +162,12 @@ impl NatDstConnector for NatDstKcpConnector {
|
|||||||
hdr: &PeerManagerHeader,
|
hdr: &PeerManagerHeader,
|
||||||
_ipv4: &Ipv4Packet,
|
_ipv4: &Ipv4Packet,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// TODO: how to support net to net kcp proxy?
|
|
||||||
return hdr.from_peer_id == hdr.to_peer_id;
|
return hdr.from_peer_id == hdr.to_peer_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transport_type(&self) -> TcpProxyEntryTransportType {
|
||||||
|
TcpProxyEntryTransportType::Kcp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -191,17 +203,10 @@ impl NicPacketFilter for TcpProxyForKcpSrc {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(my_ipv4) = self.0.get_global_ctx().get_ipv4() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = zc_packet.payload();
|
let data = zc_packet.payload();
|
||||||
let ip_packet = Ipv4Packet::new(data).unwrap();
|
let ip_packet = Ipv4Packet::new(data).unwrap();
|
||||||
if ip_packet.get_version() != 4
|
if ip_packet.get_version() != 4
|
||||||
// TODO: how to support net to net kcp proxy?
|
|
||||||
|| ip_packet.get_source() != my_ipv4.address()
|
|
||||||
|| ip_packet.get_next_level_protocol() != IpNextHeaderProtocols::Tcp
|
|| ip_packet.get_next_level_protocol() != IpNextHeaderProtocols::Tcp
|
||||||
|| !self.check_dst_allow_kcp_input(&ip_packet.get_destination()).await
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -210,15 +215,33 @@ impl NicPacketFilter for TcpProxyForKcpSrc {
|
|||||||
let tcp_packet = TcpPacket::new(ip_packet.payload()).unwrap();
|
let tcp_packet = TcpPacket::new(ip_packet.payload()).unwrap();
|
||||||
let is_syn = tcp_packet.get_flags() & TcpFlags::SYN != 0
|
let is_syn = tcp_packet.get_flags() & TcpFlags::SYN != 0
|
||||||
&& tcp_packet.get_flags() & TcpFlags::ACK == 0;
|
&& tcp_packet.get_flags() & TcpFlags::ACK == 0;
|
||||||
if !is_syn
|
if is_syn {
|
||||||
&& !self.0.is_tcp_proxy_connection(SocketAddr::new(
|
// only check dst feature flag when SYN packet
|
||||||
IpAddr::V4(my_ipv4.address()),
|
if !self
|
||||||
|
.check_dst_allow_kcp_input(&ip_packet.get_destination())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if not syn packet, only allow established connection
|
||||||
|
if !self.0.is_tcp_proxy_connection(SocketAddr::new(
|
||||||
|
IpAddr::V4(ip_packet.get_source()),
|
||||||
tcp_packet.get_source(),
|
tcp_packet.get_source(),
|
||||||
))
|
)) {
|
||||||
{
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(my_ipv4) = self.0.get_global_ctx().get_ipv4() {
|
||||||
|
// this is a net-to-net packet, only allow it when smoltcp is enabled
|
||||||
|
// because the syn-ack packet will not be through and handled by the tun device when
|
||||||
|
// the source ip is in the local network
|
||||||
|
if ip_packet.get_source() != my_ipv4.address() && !self.0.is_smoltcp_enabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
zc_packet.mut_peer_manager_header().unwrap().to_peer_id = self.0.get_my_peer_id().into();
|
zc_packet.mut_peer_manager_header().unwrap().to_peer_id = self.0.get_my_peer_id().into();
|
||||||
|
|
||||||
true
|
true
|
||||||
@@ -272,11 +295,16 @@ impl KcpProxySrc {
|
|||||||
.await;
|
.await;
|
||||||
self.tcp_proxy.0.start(false).await.unwrap();
|
self.tcp_proxy.0.start(false).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_tcp_proxy(&self) -> Arc<TcpProxy<NatDstKcpConnector>> {
|
||||||
|
self.tcp_proxy.0.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KcpProxyDst {
|
pub struct KcpProxyDst {
|
||||||
kcp_endpoint: Arc<KcpEndpoint>,
|
kcp_endpoint: Arc<KcpEndpoint>,
|
||||||
peer_manager: Arc<PeerManager>,
|
peer_manager: Arc<PeerManager>,
|
||||||
|
proxy_entries: Arc<DashMap<ConnId, TcpProxyEntry>>,
|
||||||
tasks: JoinSet<()>,
|
tasks: JoinSet<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,6 +324,7 @@ impl KcpProxyDst {
|
|||||||
Self {
|
Self {
|
||||||
kcp_endpoint: Arc::new(kcp_endpoint),
|
kcp_endpoint: Arc::new(kcp_endpoint),
|
||||||
peer_manager,
|
peer_manager,
|
||||||
|
proxy_entries: Arc::new(DashMap::new()),
|
||||||
tasks,
|
tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,6 +333,7 @@ impl KcpProxyDst {
|
|||||||
async fn handle_one_in_stream(
|
async fn handle_one_in_stream(
|
||||||
mut kcp_stream: KcpStream,
|
mut kcp_stream: KcpStream,
|
||||||
global_ctx: ArcGlobalCtx,
|
global_ctx: ArcGlobalCtx,
|
||||||
|
proxy_entries: Arc<DashMap<ConnId, TcpProxyEntry>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut conn_data = kcp_stream.conn_data().clone();
|
let mut conn_data = kcp_stream.conn_data().clone();
|
||||||
let parsed_conn_data = KcpConnData::decode(&mut conn_data)
|
let parsed_conn_data = KcpConnData::decode(&mut conn_data)
|
||||||
@@ -316,7 +346,24 @@ impl KcpProxyDst {
|
|||||||
))?
|
))?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
if Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address())) {
|
let conn_id = kcp_stream.conn_id();
|
||||||
|
proxy_entries.insert(
|
||||||
|
conn_id,
|
||||||
|
TcpProxyEntry {
|
||||||
|
src: parsed_conn_data.src,
|
||||||
|
dst: parsed_conn_data.dst,
|
||||||
|
start_time: chrono::Local::now().timestamp() as u64,
|
||||||
|
state: TcpProxyEntryState::ConnectingDst.into(),
|
||||||
|
transport_type: TcpProxyEntryTransportType::Kcp.into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
crate::defer! {
|
||||||
|
proxy_entries.remove(&conn_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address()))
|
||||||
|
&& global_ctx.no_tun()
|
||||||
|
{
|
||||||
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
|
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +371,13 @@ impl KcpProxyDst {
|
|||||||
|
|
||||||
let _g = global_ctx.net_ns.guard();
|
let _g = global_ctx.net_ns.guard();
|
||||||
let connector = NatDstTcpConnector {};
|
let connector = NatDstTcpConnector {};
|
||||||
let mut ret = connector.connect(dst_socket).await?;
|
let mut ret = connector
|
||||||
|
.connect("0.0.0.0:0".parse().unwrap(), dst_socket)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(mut e) = proxy_entries.get_mut(&kcp_stream.conn_id()) {
|
||||||
|
e.state = TcpProxyEntryState::Connected.into();
|
||||||
|
}
|
||||||
|
|
||||||
copy_bidirectional(&mut ret, &mut kcp_stream).await?;
|
copy_bidirectional(&mut ret, &mut kcp_stream).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -333,6 +386,7 @@ impl KcpProxyDst {
|
|||||||
async fn run_accept_task(&mut self) {
|
async fn run_accept_task(&mut self) {
|
||||||
let kcp_endpoint = self.kcp_endpoint.clone();
|
let kcp_endpoint = self.kcp_endpoint.clone();
|
||||||
let global_ctx = self.peer_manager.get_global_ctx().clone();
|
let global_ctx = self.peer_manager.get_global_ctx().clone();
|
||||||
|
let proxy_entries = self.proxy_entries.clone();
|
||||||
self.tasks.spawn(async move {
|
self.tasks.spawn(async move {
|
||||||
while let Ok(conn) = kcp_endpoint.accept().await {
|
while let Ok(conn) = kcp_endpoint.accept().await {
|
||||||
let stream = KcpStream::new(&kcp_endpoint, conn)
|
let stream = KcpStream::new(&kcp_endpoint, conn)
|
||||||
@@ -340,8 +394,9 @@ impl KcpProxyDst {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let global_ctx = global_ctx.clone();
|
let global_ctx = global_ctx.clone();
|
||||||
|
let proxy_entries = proxy_entries.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = Self::handle_one_in_stream(stream, global_ctx).await;
|
let _ = Self::handle_one_in_stream(stream, global_ctx, proxy_entries).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -357,3 +412,30 @@ impl KcpProxyDst {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct KcpProxyDstRpcService(Weak<DashMap<ConnId, TcpProxyEntry>>);
|
||||||
|
|
||||||
|
impl KcpProxyDstRpcService {
|
||||||
|
pub fn new(kcp_proxy_dst: &KcpProxyDst) -> Self {
|
||||||
|
Self(Arc::downgrade(&kcp_proxy_dst.proxy_entries))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl TcpProxyRpc for KcpProxyDstRpcService {
|
||||||
|
type Controller = BaseController;
|
||||||
|
async fn list_tcp_proxy_entry(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
_request: ListTcpProxyEntryRequest, // Accept request of type HelloRequest
|
||||||
|
) -> std::result::Result<ListTcpProxyEntryResponse, rpc_types::error::Error> {
|
||||||
|
let mut reply = ListTcpProxyEntryResponse::default();
|
||||||
|
if let Some(tcp_proxy) = self.0.upgrade() {
|
||||||
|
for item in tcp_proxy.iter() {
|
||||||
|
reply.entries.push(item.value().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,14 +8,17 @@ use pnet::packet::ipv4::{Ipv4Packet, MutableIpv4Packet};
|
|||||||
use pnet::packet::tcp::{ipv4_checksum, MutableTcpPacket, TcpPacket};
|
use pnet::packet::tcp::{ipv4_checksum, MutableTcpPacket, TcpPacket};
|
||||||
use pnet::packet::MutablePacket;
|
use pnet::packet::MutablePacket;
|
||||||
use pnet::packet::Packet;
|
use pnet::packet::Packet;
|
||||||
|
use socket2::{SockRef, TcpKeepalive};
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU16};
|
use std::sync::atomic::{AtomicBool, AtomicU16};
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tokio::io::{copy_bidirectional, AsyncRead, AsyncWrite};
|
use tokio::io::{copy_bidirectional, AsyncRead, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||||
|
use tokio::select;
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
use tokio::time::timeout;
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
use crate::common::error::Result;
|
use crate::common::error::Result;
|
||||||
@@ -24,6 +27,12 @@ use crate::common::join_joinset_background;
|
|||||||
|
|
||||||
use crate::peers::peer_manager::PeerManager;
|
use crate::peers::peer_manager::PeerManager;
|
||||||
use crate::peers::{NicPacketFilter, PeerPacketFilter};
|
use crate::peers::{NicPacketFilter, PeerPacketFilter};
|
||||||
|
use crate::proto::cli::{
|
||||||
|
ListTcpProxyEntryRequest, ListTcpProxyEntryResponse, TcpProxyEntry, TcpProxyEntryState,
|
||||||
|
TcpProxyEntryTransportType, TcpProxyRpc,
|
||||||
|
};
|
||||||
|
use crate::proto::rpc_types;
|
||||||
|
use crate::proto::rpc_types::controller::BaseController;
|
||||||
use crate::tunnel::packet_def::{PacketType, PeerManagerHeader, ZCPacket};
|
use crate::tunnel::packet_def::{PacketType, PeerManagerHeader, ZCPacket};
|
||||||
|
|
||||||
use super::CidrSet;
|
use super::CidrSet;
|
||||||
@@ -35,7 +44,7 @@ use super::tokio_smoltcp::{self, channel_device, Net, NetConfig};
|
|||||||
pub(crate) trait NatDstConnector: Send + Sync + Clone + 'static {
|
pub(crate) trait NatDstConnector: Send + Sync + Clone + 'static {
|
||||||
type DstStream: AsyncRead + AsyncWrite + Unpin + Send;
|
type DstStream: AsyncRead + AsyncWrite + Unpin + Send;
|
||||||
|
|
||||||
async fn connect(&self, dst: SocketAddr) -> Result<Self::DstStream>;
|
async fn connect(&self, src: SocketAddr, dst: SocketAddr) -> Result<Self::DstStream>;
|
||||||
fn check_packet_from_peer_fast(&self, cidr_set: &CidrSet, global_ctx: &GlobalCtx) -> bool;
|
fn check_packet_from_peer_fast(&self, cidr_set: &CidrSet, global_ctx: &GlobalCtx) -> bool;
|
||||||
fn check_packet_from_peer(
|
fn check_packet_from_peer(
|
||||||
&self,
|
&self,
|
||||||
@@ -44,6 +53,7 @@ pub(crate) trait NatDstConnector: Send + Sync + Clone + 'static {
|
|||||||
hdr: &PeerManagerHeader,
|
hdr: &PeerManagerHeader,
|
||||||
ipv4: &Ipv4Packet,
|
ipv4: &Ipv4Packet,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
fn transport_type(&self) -> TcpProxyEntryTransportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -52,18 +62,31 @@ pub struct NatDstTcpConnector;
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl NatDstConnector for NatDstTcpConnector {
|
impl NatDstConnector for NatDstTcpConnector {
|
||||||
type DstStream = TcpStream;
|
type DstStream = TcpStream;
|
||||||
|
async fn connect(&self, _src: SocketAddr, nat_dst: SocketAddr) -> Result<Self::DstStream> {
|
||||||
async fn connect(&self, nat_dst: SocketAddr) -> Result<Self::DstStream> {
|
|
||||||
let socket = TcpSocket::new_v4().unwrap();
|
let socket = TcpSocket::new_v4().unwrap();
|
||||||
if let Err(e) = socket.set_nodelay(true) {
|
if let Err(e) = socket.set_nodelay(true) {
|
||||||
tracing::warn!("set_nodelay failed, ignore it: {:?}", e);
|
tracing::warn!("set_nodelay failed, ignore it: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(
|
const TCP_KEEPALIVE_TIME: Duration = Duration::from_secs(5);
|
||||||
tokio::time::timeout(Duration::from_secs(10), socket.connect(nat_dst))
|
const TCP_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(2);
|
||||||
.await?
|
const TCP_KEEPALIVE_RETRIES: u32 = 2;
|
||||||
.with_context(|| format!("connect to nat dst failed: {:?}", nat_dst))?,
|
|
||||||
)
|
let stream = timeout(Duration::from_secs(10), socket.connect(nat_dst))
|
||||||
|
.await?
|
||||||
|
.with_context(|| format!("connect to nat dst failed: {:?}", nat_dst))?;
|
||||||
|
|
||||||
|
let ka = TcpKeepalive::new()
|
||||||
|
.with_time(TCP_KEEPALIVE_TIME)
|
||||||
|
.with_interval(TCP_KEEPALIVE_INTERVAL);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let ka = ka.with_retries(TCP_KEEPALIVE_RETRIES);
|
||||||
|
|
||||||
|
let sf = SockRef::from(&stream);
|
||||||
|
sf.set_tcp_keepalive(&ka)?;
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_packet_from_peer_fast(&self, cidr_set: &CidrSet, global_ctx: &GlobalCtx) -> bool {
|
fn check_packet_from_peer_fast(&self, cidr_set: &CidrSet, global_ctx: &GlobalCtx) -> bool {
|
||||||
@@ -90,19 +113,13 @@ impl NatDstConnector for NatDstTcpConnector {
|
|||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transport_type(&self) -> TcpProxyEntryTransportType {
|
||||||
|
TcpProxyEntryTransportType::Tcp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
type NatDstEntryState = TcpProxyEntryState;
|
||||||
enum NatDstEntryState {
|
|
||||||
// receive syn packet but not start connecting to dst
|
|
||||||
SynReceived,
|
|
||||||
// connecting to dst
|
|
||||||
ConnectingDst,
|
|
||||||
// connected to dst
|
|
||||||
Connected,
|
|
||||||
// connection closed
|
|
||||||
Closed,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NatDstEntry {
|
pub struct NatDstEntry {
|
||||||
@@ -110,6 +127,7 @@ pub struct NatDstEntry {
|
|||||||
src: SocketAddr,
|
src: SocketAddr,
|
||||||
dst: SocketAddr,
|
dst: SocketAddr,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
|
start_time_local: chrono::DateTime<chrono::Local>,
|
||||||
tasks: Mutex<JoinSet<()>>,
|
tasks: Mutex<JoinSet<()>>,
|
||||||
state: AtomicCell<NatDstEntryState>,
|
state: AtomicCell<NatDstEntryState>,
|
||||||
}
|
}
|
||||||
@@ -121,10 +139,21 @@ impl NatDstEntry {
|
|||||||
src,
|
src,
|
||||||
dst,
|
dst,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
start_time_local: chrono::Local::now(),
|
||||||
tasks: Mutex::new(JoinSet::new()),
|
tasks: Mutex::new(JoinSet::new()),
|
||||||
state: AtomicCell::new(NatDstEntryState::SynReceived),
|
state: AtomicCell::new(NatDstEntryState::SynReceived),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_pb(&self, transport_type: TcpProxyEntryTransportType) -> TcpProxyEntry {
|
||||||
|
TcpProxyEntry {
|
||||||
|
src: Some(self.src.clone().into()),
|
||||||
|
dst: Some(self.dst.clone().into()),
|
||||||
|
start_time: self.start_time_local.timestamp() as u64,
|
||||||
|
state: self.state.load().into(),
|
||||||
|
transport_type: transport_type.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProxyTcpStream {
|
enum ProxyTcpStream {
|
||||||
@@ -145,6 +174,20 @@ impl ProxyTcpStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn shutdown(&mut self) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::KernelTcpStream(stream) => {
|
||||||
|
stream.shutdown().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "smoltcp")]
|
||||||
|
Self::SmolTcpStream(stream) => {
|
||||||
|
stream.shutdown().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn copy_bidirectional<D: AsyncRead + AsyncWrite + Unpin>(
|
pub async fn copy_bidirectional<D: AsyncRead + AsyncWrite + Unpin>(
|
||||||
&mut self,
|
&mut self,
|
||||||
dst: &mut D,
|
dst: &mut D,
|
||||||
@@ -187,11 +230,29 @@ impl SmolTcpListener {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
tasks.spawn(async move {
|
tasks.spawn(async move {
|
||||||
|
let mut not_listening_count = 0;
|
||||||
loop {
|
loop {
|
||||||
tx.send(tcp.accept().await.map_err(|e| {
|
select! {
|
||||||
anyhow::anyhow!("smol tcp listener accept failed: {:?}", e).into()
|
_ = tokio::time::sleep(Duration::from_secs(2)) => {
|
||||||
}))
|
if tcp.is_listening() {
|
||||||
.unwrap();
|
not_listening_count = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_listening_count += 1;
|
||||||
|
if not_listening_count >= 2 {
|
||||||
|
tracing::error!("smol tcp listener not listening");
|
||||||
|
tcp.relisten();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accept_ret = tcp.accept() => {
|
||||||
|
tx.send(accept_ret.map_err(|e| {
|
||||||
|
anyhow::anyhow!("smol tcp listener accept failed: {:?}", e).into()
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
not_listening_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -644,7 +705,7 @@ impl<C: NatDstConnector> TcpProxy<C> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let _guard = global_ctx.net_ns.guard();
|
let _guard = global_ctx.net_ns.guard();
|
||||||
let Ok(dst_tcp_stream) = connector.connect(nat_dst).await else {
|
let Ok(dst_tcp_stream) = connector.connect(nat_entry.src, nat_dst).await else {
|
||||||
tracing::error!("connect to dst failed: {:?}", nat_entry);
|
tracing::error!("connect to dst failed: {:?}", nat_entry);
|
||||||
nat_entry.state.store(NatDstEntryState::Closed);
|
nat_entry.state.store(NatDstEntryState::Closed);
|
||||||
Self::remove_entry_from_all_conn_map(conn_map, addr_conn_map, nat_entry);
|
Self::remove_entry_from_all_conn_map(conn_map, addr_conn_map, nat_entry);
|
||||||
@@ -678,9 +739,19 @@ impl<C: NatDstConnector> TcpProxy<C> {
|
|||||||
nat_entry.tasks.lock().await.spawn(async move {
|
nat_entry.tasks.lock().await.spawn(async move {
|
||||||
let ret = src_tcp_stream.copy_bidirectional(&mut dst_tcp_stream).await;
|
let ret = src_tcp_stream.copy_bidirectional(&mut dst_tcp_stream).await;
|
||||||
tracing::info!(nat_entry = ?nat_entry_clone, ret = ?ret, "nat tcp connection closed");
|
tracing::info!(nat_entry = ?nat_entry_clone, ret = ?ret, "nat tcp connection closed");
|
||||||
nat_entry_clone.state.store(NatDstEntryState::Closed);
|
|
||||||
drop(src_tcp_stream);
|
|
||||||
|
|
||||||
|
nat_entry_clone.state.store(NatDstEntryState::ClosingSrc);
|
||||||
|
let ret = timeout(Duration::from_secs(10), src_tcp_stream.shutdown()).await;
|
||||||
|
tracing::info!(nat_entry = ?nat_entry_clone, ret = ?ret, "src tcp stream shutdown");
|
||||||
|
|
||||||
|
nat_entry_clone.state.store(NatDstEntryState::ClosingDst);
|
||||||
|
let ret = timeout(Duration::from_secs(10), dst_tcp_stream.shutdown()).await;
|
||||||
|
tracing::info!(nat_entry = ?nat_entry_clone, ret = ?ret, "dst tcp stream shutdown");
|
||||||
|
|
||||||
|
drop(src_tcp_stream);
|
||||||
|
drop(dst_tcp_stream);
|
||||||
|
|
||||||
|
nat_entry_clone.state.store(NatDstEntryState::Closed);
|
||||||
// sleep later so the fin packet can be processed
|
// sleep later so the fin packet can be processed
|
||||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||||
|
|
||||||
@@ -802,4 +873,45 @@ impl<C: NatDstConnector> TcpProxy<C> {
|
|||||||
pub fn is_tcp_proxy_connection(&self, src: SocketAddr) -> bool {
|
pub fn is_tcp_proxy_connection(&self, src: SocketAddr) -> bool {
|
||||||
self.syn_map.contains_key(&src) || self.addr_conn_map.contains_key(&src)
|
self.syn_map.contains_key(&src) || self.addr_conn_map.contains_key(&src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_proxy_entries(&self) -> Vec<TcpProxyEntry> {
|
||||||
|
let mut entries: Vec<TcpProxyEntry> = Vec::new();
|
||||||
|
let transport_type = self.connector.transport_type();
|
||||||
|
for entry in self.syn_map.iter() {
|
||||||
|
entries.push(entry.value().as_ref().into_pb(transport_type));
|
||||||
|
}
|
||||||
|
for entry in self.conn_map.iter() {
|
||||||
|
entries.push(entry.value().as_ref().into_pb(transport_type));
|
||||||
|
}
|
||||||
|
entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TcpProxyRpcService<C: NatDstConnector> {
|
||||||
|
tcp_proxy: Weak<TcpProxy<C>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<C: NatDstConnector> TcpProxyRpc for TcpProxyRpcService<C> {
|
||||||
|
type Controller = BaseController;
|
||||||
|
async fn list_tcp_proxy_entry(
|
||||||
|
&self,
|
||||||
|
_: BaseController,
|
||||||
|
_request: ListTcpProxyEntryRequest, // Accept request of type HelloRequest
|
||||||
|
) -> std::result::Result<ListTcpProxyEntryResponse, rpc_types::error::Error> {
|
||||||
|
let mut reply = ListTcpProxyEntryResponse::default();
|
||||||
|
if let Some(tcp_proxy) = self.tcp_proxy.upgrade() {
|
||||||
|
reply.entries = tcp_proxy.list_proxy_entries();
|
||||||
|
}
|
||||||
|
Ok(reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: NatDstConnector> TcpProxyRpcService<C> {
|
||||||
|
pub fn new(tcp_proxy: Arc<TcpProxy<C>>) -> Self {
|
||||||
|
Self {
|
||||||
|
tcp_proxy: Arc::downgrade(&tcp_proxy),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,19 @@ async fn run(
|
|||||||
&mut device,
|
&mut device,
|
||||||
&mut socket_allocator.sockets().lock(),
|
&mut socket_allocator.sockets().lock(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// wake up all closed sockets (smoltcp seems have a bug that it doesn't wake up closed sockets)
|
||||||
|
for (_, socket) in socket_allocator.sockets().lock().iter_mut() {
|
||||||
|
match socket {
|
||||||
|
Socket::Tcp(tcp) => {
|
||||||
|
if tcp.state() == smoltcp::socket::tcp::State::Closed {
|
||||||
|
tcp.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -67,6 +67,19 @@ impl TcpListener {
|
|||||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||||
Ok(self.local_addr)
|
Ok(self.local_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn relisten(&mut self) {
|
||||||
|
let mut socket = self.reactor.get_socket::<tcp::Socket>(*self.handle);
|
||||||
|
let local_endpoint = socket.local_endpoint().unwrap();
|
||||||
|
socket.abort();
|
||||||
|
socket.listen(local_endpoint).unwrap();
|
||||||
|
self.reactor.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_listening(&self) -> bool {
|
||||||
|
let socket = self.reactor.get_socket::<tcp::Socket>(*self.handle);
|
||||||
|
socket.is_listening()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Incoming(TcpListener);
|
pub struct Incoming(TcpListener);
|
||||||
@@ -210,6 +223,9 @@ impl AsyncWrite for TcpStream {
|
|||||||
}
|
}
|
||||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||||
let mut socket = self.reactor.get_socket::<tcp::Socket>(*self.handle);
|
let mut socket = self.reactor.get_socket::<tcp::Socket>(*self.handle);
|
||||||
|
if !socket.may_send() {
|
||||||
|
return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()));
|
||||||
|
}
|
||||||
if socket.send_queue() == 0 {
|
if socket.send_queue() == 0 {
|
||||||
return Poll::Ready(Ok(()));
|
return Poll::Ready(Ok(()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use parking_lot::Mutex;
|
|||||||
use smoltcp::{
|
use smoltcp::{
|
||||||
iface::{SocketHandle as InnerSocketHandle, SocketSet},
|
iface::{SocketHandle as InnerSocketHandle, SocketSet},
|
||||||
socket::tcp,
|
socket::tcp,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
@@ -53,6 +54,8 @@ impl SocketAlloctor {
|
|||||||
let tx_buffer = tcp::SocketBuffer::new(vec![0; self.buffer_size.tcp_tx_size]);
|
let tx_buffer = tcp::SocketBuffer::new(vec![0; self.buffer_size.tcp_tx_size]);
|
||||||
let mut tcp = tcp::Socket::new(rx_buffer, tx_buffer);
|
let mut tcp = tcp::Socket::new(rx_buffer, tx_buffer);
|
||||||
tcp.set_nagle_enabled(false);
|
tcp.set_nagle_enabled(false);
|
||||||
|
tcp.set_keep_alive(Some(Duration::from_secs(10)));
|
||||||
|
tcp.set_timeout(Some(Duration::from_secs(60)));
|
||||||
|
|
||||||
tcp
|
tcp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ use crate::connector::direct::DirectConnectorManager;
|
|||||||
use crate::connector::manual::{ConnectorManagerRpcService, ManualConnectorManager};
|
use crate::connector::manual::{ConnectorManagerRpcService, ManualConnectorManager};
|
||||||
use crate::connector::udp_hole_punch::UdpHolePunchConnector;
|
use crate::connector::udp_hole_punch::UdpHolePunchConnector;
|
||||||
use crate::gateway::icmp_proxy::IcmpProxy;
|
use crate::gateway::icmp_proxy::IcmpProxy;
|
||||||
use crate::gateway::kcp_proxy::{KcpProxyDst, KcpProxySrc};
|
use crate::gateway::kcp_proxy::{KcpProxyDst, KcpProxyDstRpcService, KcpProxySrc};
|
||||||
use crate::gateway::tcp_proxy::{NatDstTcpConnector, TcpProxy};
|
use crate::gateway::tcp_proxy::{NatDstTcpConnector, TcpProxy, TcpProxyRpcService};
|
||||||
use crate::gateway::udp_proxy::UdpProxy;
|
use crate::gateway::udp_proxy::UdpProxy;
|
||||||
use crate::peer_center::instance::PeerCenterInstance;
|
use crate::peer_center::instance::PeerCenterInstance;
|
||||||
use crate::peers::peer_conn::PeerConnId;
|
use crate::peers::peer_conn::PeerConnId;
|
||||||
@@ -65,7 +65,9 @@ impl IpProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn start(&self) -> Result<(), Error> {
|
async fn start(&self) -> Result<(), Error> {
|
||||||
if (self.global_ctx.get_proxy_cidrs().is_empty() || self.started.load(Ordering::Relaxed))
|
if (self.global_ctx.get_proxy_cidrs().is_empty()
|
||||||
|
|| self.global_ctx.proxy_forward_by_system()
|
||||||
|
|| self.started.load(Ordering::Relaxed))
|
||||||
&& !self.global_ctx.enable_exit_node()
|
&& !self.global_ctx.enable_exit_node()
|
||||||
&& !self.global_ctx.no_tun()
|
&& !self.global_ctx.no_tun()
|
||||||
{
|
{
|
||||||
@@ -380,8 +382,6 @@ impl Instance {
|
|||||||
self.check_dhcp_ip_conflict();
|
self.check_dhcp_ip_conflict();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run_rpc_server().await?;
|
|
||||||
|
|
||||||
if self.global_ctx.get_flags().enable_kcp_proxy {
|
if self.global_ctx.get_flags().enable_kcp_proxy {
|
||||||
let src_proxy = KcpProxySrc::new(self.get_peer_manager()).await;
|
let src_proxy = KcpProxySrc::new(self.get_peer_manager()).await;
|
||||||
src_proxy.start().await;
|
src_proxy.start().await;
|
||||||
@@ -419,6 +419,8 @@ impl Instance {
|
|||||||
#[cfg(feature = "socks5")]
|
#[cfg(feature = "socks5")]
|
||||||
self.socks5_server.run().await?;
|
self.socks5_server.run().await?;
|
||||||
|
|
||||||
|
self.run_rpc_server().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +543,26 @@ impl Instance {
|
|||||||
s.registry()
|
s.registry()
|
||||||
.register(VpnPortalRpcServer::new(vpn_portal_rpc), "");
|
.register(VpnPortalRpcServer::new(vpn_portal_rpc), "");
|
||||||
|
|
||||||
|
if let Some(ip_proxy) = self.ip_proxy.as_ref() {
|
||||||
|
s.registry().register(
|
||||||
|
TcpProxyRpcServer::new(TcpProxyRpcService::new(ip_proxy.tcp_proxy.clone())),
|
||||||
|
"tcp",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(kcp_proxy) = self.kcp_proxy_src.as_ref() {
|
||||||
|
s.registry().register(
|
||||||
|
TcpProxyRpcServer::new(TcpProxyRpcService::new(kcp_proxy.get_tcp_proxy())),
|
||||||
|
"kcp_src",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(kcp_proxy) = self.kcp_proxy_dst.as_ref() {
|
||||||
|
s.registry().register(
|
||||||
|
TcpProxyRpcServer::new(KcpProxyDstRpcService::new(kcp_proxy)),
|
||||||
|
"kcp_dst",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let _g = self.global_ctx.net_ns.guard();
|
let _g = self.global_ctx.net_ns.guard();
|
||||||
Ok(s.serve().await.with_context(|| "rpc server start failed")?)
|
Ok(s.serve().await.with_context(|| "rpc server start failed")?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::{fmt::Debug, sync::Arc};
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
@@ -49,6 +50,10 @@ pub fn get_listener_by_url(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_url_host_ipv6(l: &url::Url) -> bool {
|
||||||
|
l.host_str().map_or(false, |h| h.contains(':'))
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait TunnelHandlerForListener {
|
pub trait TunnelHandlerForListener {
|
||||||
async fn handle_tunnel(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error>;
|
async fn handle_tunnel(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error>;
|
||||||
@@ -58,7 +63,7 @@ pub trait TunnelHandlerForListener {
|
|||||||
impl TunnelHandlerForListener for PeerManager {
|
impl TunnelHandlerForListener for PeerManager {
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
async fn handle_tunnel(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error> {
|
async fn handle_tunnel(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error> {
|
||||||
self.add_tunnel_as_server(tunnel).await
|
self.add_tunnel_as_server(tunnel, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,22 +118,26 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let ctx = self.global_ctx.clone();
|
let ctx = self.global_ctx.clone();
|
||||||
self.add_listener(move || get_listener_by_url(&l, ctx.clone()).unwrap(), true)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.global_ctx.config.get_flags().enable_ipv6 {
|
let listener = l.clone();
|
||||||
let ipv6_listener = self.global_ctx.config.get_flags().ipv6_listener.clone();
|
self.add_listener(
|
||||||
let _ = self
|
move || get_listener_by_url(&listener, ctx.clone()).unwrap(),
|
||||||
.add_listener(
|
true,
|
||||||
move || {
|
)
|
||||||
Box::new(UdpTunnelListener::new(
|
.await?;
|
||||||
ipv6_listener.clone().parse().unwrap(),
|
|
||||||
))
|
if self.global_ctx.config.get_flags().enable_ipv6 && !is_url_host_ipv6(&l) {
|
||||||
},
|
let mut ipv6_listener = l.clone();
|
||||||
|
ipv6_listener
|
||||||
|
.set_host(Some("[::]".to_string().as_str()))
|
||||||
|
.with_context(|| format!("failed to set ipv6 host for listener: {}", l))?;
|
||||||
|
let ctx = self.global_ctx.clone();
|
||||||
|
self.add_listener(
|
||||||
|
move || get_listener_by_url(&ipv6_listener, ctx.clone()).unwrap(),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -161,11 +170,11 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAdded(l.local_url()));
|
global_ctx.issue_event(GlobalCtxEvent::ListenerAdded(l.local_url()));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
tracing::error!(?e, ?l, "listener listen error");
|
||||||
global_ctx.issue_event(GlobalCtxEvent::ListenerAddFailed(
|
global_ctx.issue_event(GlobalCtxEvent::ListenerAddFailed(
|
||||||
l.local_url(),
|
l.local_url(),
|
||||||
format!("error: {:?}, retry listen later...", e),
|
format!("error: {:?}, retry listen later...", e),
|
||||||
));
|
));
|
||||||
tracing::error!(?e, ?l, "listener listen error");
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -217,6 +226,15 @@ impl<H: TunnelHandlerForListener + Send + Sync + 'static + Debug> ListenerManage
|
|||||||
|
|
||||||
pub async fn run(&mut self) -> Result<(), Error> {
|
pub async fn run(&mut self) -> Result<(), Error> {
|
||||||
for listener in &self.listeners {
|
for listener in &self.listeners {
|
||||||
|
if listener.must_succ {
|
||||||
|
// try listen once
|
||||||
|
let mut l = (listener.creator_fn)();
|
||||||
|
let _g = self.net_ns.guard();
|
||||||
|
l.listen()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to listen on {}", l.local_url()))?;
|
||||||
|
}
|
||||||
|
|
||||||
self.tasks.spawn(Self::run_listener(
|
self.tasks.spawn(Self::run_listener(
|
||||||
listener.creator_fn.clone(),
|
listener.creator_fn.clone(),
|
||||||
self.peer_manager.clone(),
|
self.peer_manager.clone(),
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ impl EasyTierLauncher {
|
|||||||
fetch_node_info,
|
fetch_node_info,
|
||||||
));
|
));
|
||||||
if let Err(e) = ret {
|
if let Err(e) = ret {
|
||||||
error_msg.write().unwrap().replace(e.to_string());
|
error_msg.write().unwrap().replace(format!("{:?}", e));
|
||||||
}
|
}
|
||||||
instance_alive.store(false, std::sync::atomic::Ordering::Relaxed);
|
instance_alive.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
notifier.notify_one();
|
notifier.notify_one();
|
||||||
@@ -517,6 +517,39 @@ impl NetworkConfig {
|
|||||||
})?,
|
})?,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.enable_manual_routes.unwrap_or_default() {
|
||||||
|
let mut routes = Vec::<cidr::Ipv4Cidr>::with_capacity(self.routes.len());
|
||||||
|
for route in self.routes.iter() {
|
||||||
|
routes.push(
|
||||||
|
route.parse()
|
||||||
|
.with_context(|| format!("failed to parse route: {}", route))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cfg.set_routes(Some(routes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.exit_nodes.len() > 0 {
|
||||||
|
let mut exit_nodes = Vec::<std::net::Ipv4Addr>::with_capacity(self.exit_nodes.len());
|
||||||
|
for node in self.exit_nodes.iter() {
|
||||||
|
exit_nodes.push(
|
||||||
|
node.parse()
|
||||||
|
.with_context(|| format!("failed to parse exit node: {}", node))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cfg.set_exit_nodes(exit_nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.enable_socks5.unwrap_or_default() {
|
||||||
|
if let Some(socks5_port) = self.socks5_port {
|
||||||
|
cfg.set_socks5_portal(Some(
|
||||||
|
format!("socks5://0.0.0.0:{}", socks5_port)
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut flags = gen_default_flags();
|
let mut flags = gen_default_flags();
|
||||||
if let Some(latency_first) = self.latency_first {
|
if let Some(latency_first) = self.latency_first {
|
||||||
flags.latency_first = latency_first;
|
flags.latency_first = latency_first;
|
||||||
@@ -550,6 +583,35 @@ impl NetworkConfig {
|
|||||||
flags.no_tun = no_tun;
|
flags.no_tun = no_tun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(enable_exit_node) = self.enable_exit_node {
|
||||||
|
flags.enable_exit_node = enable_exit_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(relay_all_peer_rpc) = self.relay_all_peer_rpc {
|
||||||
|
flags.relay_all_peer_rpc = relay_all_peer_rpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(multi_thread) = self.multi_thread {
|
||||||
|
flags.multi_thread = multi_thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(proxy_forward_by_system) = self.proxy_forward_by_system {
|
||||||
|
flags.proxy_forward_by_system = proxy_forward_by_system;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(disable_encryption) = self.disable_encryption {
|
||||||
|
flags.enable_encryption = !disable_encryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.enable_relay_network_whitelist.unwrap_or_default() {
|
||||||
|
if self.relay_network_whitelist.len() > 0 {
|
||||||
|
flags.relay_network_whitelist = self.relay_network_whitelist.join(" ")
|
||||||
|
} else {
|
||||||
|
flags.relay_network_whitelist = "".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
cfg.set_flags(flags);
|
cfg.set_flags(flags);
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
mod arch;
|
mod arch;
|
||||||
mod connector;
|
|
||||||
mod gateway;
|
mod gateway;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod peer_center;
|
mod peer_center;
|
||||||
mod vpn_portal;
|
mod vpn_portal;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
pub mod connector;
|
||||||
pub mod launcher;
|
pub mod launcher;
|
||||||
pub mod peers;
|
pub mod peers;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use crate::{
|
|||||||
config::{ConfigLoader, TomlConfigLoader},
|
config::{ConfigLoader, TomlConfigLoader},
|
||||||
error::Error,
|
error::Error,
|
||||||
global_ctx::{ArcGlobalCtx, GlobalCtx, GlobalCtxEvent, NetworkIdentity},
|
global_ctx::{ArcGlobalCtx, GlobalCtx, GlobalCtxEvent, NetworkIdentity},
|
||||||
|
join_joinset_background,
|
||||||
stun::MockStunInfoCollector,
|
stun::MockStunInfoCollector,
|
||||||
PeerId,
|
PeerId,
|
||||||
},
|
},
|
||||||
@@ -181,6 +182,15 @@ impl ForeignNetworkEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for RpcTransport {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
tracing::debug!(
|
||||||
|
"drop rpc transport for foreign network manager, my_peer_id: {:?}",
|
||||||
|
self.my_peer_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (rpc_transport_sender, peer_rpc_tspt_recv) = mpsc::unbounded_channel();
|
let (rpc_transport_sender, peer_rpc_tspt_recv) = mpsc::unbounded_channel();
|
||||||
let tspt = RpcTransport {
|
let tspt = RpcTransport {
|
||||||
my_peer_id,
|
my_peer_id,
|
||||||
@@ -216,7 +226,6 @@ impl ForeignNetworkEntry {
|
|||||||
.list_global_foreign_peer(&self.network_identity)
|
.list_global_foreign_peer(&self.network_identity)
|
||||||
.await;
|
.await;
|
||||||
let local = peer_map.list_peers_with_conn().await;
|
let local = peer_map.list_peers_with_conn().await;
|
||||||
tracing::debug!(?global, ?local, ?self.my_peer_id, "list peers in foreign network manager");
|
|
||||||
global.extend(local.iter().cloned());
|
global.extend(local.iter().cloned());
|
||||||
global
|
global
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -426,7 +435,7 @@ pub struct ForeignNetworkManager {
|
|||||||
|
|
||||||
data: Arc<ForeignNetworkManagerData>,
|
data: Arc<ForeignNetworkManagerData>,
|
||||||
|
|
||||||
tasks: Mutex<JoinSet<()>>,
|
tasks: Arc<std::sync::Mutex<JoinSet<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ForeignNetworkManager {
|
impl ForeignNetworkManager {
|
||||||
@@ -444,6 +453,9 @@ impl ForeignNetworkManager {
|
|||||||
lock: std::sync::Mutex::new(()),
|
lock: std::sync::Mutex::new(()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let tasks = Arc::new(std::sync::Mutex::new(JoinSet::new()));
|
||||||
|
join_joinset_background(tasks.clone(), "ForeignNetworkManager".to_string());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
my_peer_id,
|
my_peer_id,
|
||||||
global_ctx,
|
global_ctx,
|
||||||
@@ -451,7 +463,7 @@ impl ForeignNetworkManager {
|
|||||||
|
|
||||||
data,
|
data,
|
||||||
|
|
||||||
tasks: Mutex::new(JoinSet::new()),
|
tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +515,7 @@ impl ForeignNetworkManager {
|
|||||||
let data = self.data.clone();
|
let data = self.data.clone();
|
||||||
let network_name = entry.network.network_name.clone();
|
let network_name = entry.network.network_name.clone();
|
||||||
let mut s = entry.global_ctx.subscribe();
|
let mut s = entry.global_ctx.subscribe();
|
||||||
self.tasks.lock().await.spawn(async move {
|
self.tasks.lock().unwrap().spawn(async move {
|
||||||
while let Ok(e) = s.recv().await {
|
while let Ok(e) = s.recv().await {
|
||||||
match &e {
|
match &e {
|
||||||
GlobalCtxEvent::PeerRemoved(peer_id) => {
|
GlobalCtxEvent::PeerRemoved(peer_id) => {
|
||||||
@@ -683,7 +695,8 @@ mod tests {
|
|||||||
|
|
||||||
let (a_ring, b_ring) = crate::tunnel::ring::create_ring_tunnel_pair();
|
let (a_ring, b_ring) = crate::tunnel::ring::create_ring_tunnel_pair();
|
||||||
let b_mgr_copy = pm_center.clone();
|
let b_mgr_copy = pm_center.clone();
|
||||||
let s_ret = tokio::spawn(async move { b_mgr_copy.add_tunnel_as_server(b_ring).await });
|
let s_ret =
|
||||||
|
tokio::spawn(async move { b_mgr_copy.add_tunnel_as_server(b_ring, true).await });
|
||||||
|
|
||||||
pma_net1.add_client_tunnel(a_ring).await.unwrap();
|
pma_net1.add_client_tunnel(a_ring).await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use super::{
|
|||||||
peer_conn::{PeerConn, PeerConnId},
|
peer_conn::{PeerConn, PeerConnId},
|
||||||
PacketRecvChan,
|
PacketRecvChan,
|
||||||
};
|
};
|
||||||
use crate::proto::cli::PeerConnInfo;
|
use crate::{common::scoped_task::ScopedTask, proto::cli::PeerConnInfo};
|
||||||
use crate::{
|
use crate::{
|
||||||
common::{
|
common::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@@ -36,7 +36,8 @@ pub struct Peer {
|
|||||||
|
|
||||||
shutdown_notifier: Arc<tokio::sync::Notify>,
|
shutdown_notifier: Arc<tokio::sync::Notify>,
|
||||||
|
|
||||||
default_conn_id: AtomicCell<PeerConnId>,
|
default_conn_id: Arc<AtomicCell<PeerConnId>>,
|
||||||
|
default_conn_id_clear_task: ScopedTask<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Peer {
|
impl Peer {
|
||||||
@@ -88,6 +89,19 @@ impl Peer {
|
|||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let default_conn_id = Arc::new(AtomicCell::new(PeerConnId::default()));
|
||||||
|
|
||||||
|
let conns_copy = conns.clone();
|
||||||
|
let default_conn_id_copy = default_conn_id.clone();
|
||||||
|
let default_conn_id_clear_task = ScopedTask::from(tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||||
|
if conns_copy.len() > 1 {
|
||||||
|
default_conn_id_copy.store(PeerConnId::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
Peer {
|
Peer {
|
||||||
peer_node_id,
|
peer_node_id,
|
||||||
conns: conns.clone(),
|
conns: conns.clone(),
|
||||||
@@ -98,7 +112,8 @@ impl Peer {
|
|||||||
close_event_listener,
|
close_event_listener,
|
||||||
|
|
||||||
shutdown_notifier,
|
shutdown_notifier,
|
||||||
default_conn_id: AtomicCell::new(PeerConnId::default()),
|
default_conn_id,
|
||||||
|
default_conn_id_clear_task,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,14 +132,19 @@ impl Peer {
|
|||||||
return Some(conn.clone());
|
return Some(conn.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn = self.conns.iter().next();
|
// find a conn with the smallest latency
|
||||||
if conn.is_none() {
|
let mut min_latency = std::u64::MAX;
|
||||||
return None;
|
for conn in self.conns.iter() {
|
||||||
|
let latency = conn.value().get_stats().latency_us;
|
||||||
|
if latency < min_latency {
|
||||||
|
min_latency = latency;
|
||||||
|
self.default_conn_id.store(conn.get_conn_id());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn = conn.unwrap().clone();
|
self.conns
|
||||||
self.default_conn_id.store(conn.get_conn_id());
|
.get(&self.default_conn_id.load())
|
||||||
Some(conn)
|
.map(|conn| conn.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_msg(&self, msg: ZCPacket) -> Result<(), Error> {
|
pub async fn send_msg(&self, msg: ZCPacket) -> Result<(), Error> {
|
||||||
@@ -158,6 +178,10 @@ impl Peer {
|
|||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_default_conn_id(&self) -> PeerConnId {
|
||||||
|
self.default_conn_id.load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pritn on drop
|
// pritn on drop
|
||||||
|
|||||||
@@ -84,9 +84,7 @@ impl PingIntervalController {
|
|||||||
self.throughput.rx_packets() > self.last_throughput.rx_packets()
|
self.throughput.rx_packets() > self.last_throughput.rx_packets()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
|
||||||
fn should_send_ping(&mut self) -> bool {
|
fn should_send_ping(&mut self) -> bool {
|
||||||
tracing::trace!(?self, "check should_send_ping");
|
|
||||||
if self.loss_counter.load(Ordering::Relaxed) > 0 {
|
if self.loss_counter.load(Ordering::Relaxed) > 0 {
|
||||||
self.backoff_idx = 0;
|
self.backoff_idx = 0;
|
||||||
} else if self.tx_increase() && !self.rx_increase() {
|
} else if self.tx_increase() && !self.rx_increase() {
|
||||||
@@ -253,6 +251,13 @@ impl PeerConnPinger {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"pingpong controller send pingpong task, seq: {}, node_id: {}, controller: {:?}",
|
||||||
|
req_seq,
|
||||||
|
my_node_id,
|
||||||
|
controller
|
||||||
|
);
|
||||||
|
|
||||||
let mut sink = sink.clone();
|
let mut sink = sink.clone();
|
||||||
let receiver = ctrl_resp_sender.subscribe();
|
let receiver = ctrl_resp_sender.subscribe();
|
||||||
let ping_res_sender = ping_res_sender.clone();
|
let ping_res_sender = ping_res_sender.clone();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use std::{
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::{DashMap, DashSet};
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{
|
sync::{
|
||||||
@@ -23,7 +23,7 @@ use crate::{
|
|||||||
compressor::{Compressor as _, DefaultCompressor},
|
compressor::{Compressor as _, DefaultCompressor},
|
||||||
constants::EASYTIER_VERSION,
|
constants::EASYTIER_VERSION,
|
||||||
error::Error,
|
error::Error,
|
||||||
global_ctx::{ArcGlobalCtx, NetworkIdentity},
|
global_ctx::{ArcGlobalCtx, GlobalCtxEvent, NetworkIdentity},
|
||||||
stun::StunInfoCollectorTrait,
|
stun::StunInfoCollectorTrait,
|
||||||
PeerId,
|
PeerId,
|
||||||
},
|
},
|
||||||
@@ -141,6 +141,9 @@ pub struct PeerManager {
|
|||||||
data_compress_algo: CompressorAlgo,
|
data_compress_algo: CompressorAlgo,
|
||||||
|
|
||||||
exit_nodes: Vec<Ipv4Addr>,
|
exit_nodes: Vec<Ipv4Addr>,
|
||||||
|
|
||||||
|
// conns that are directly connected (which are not hole punched)
|
||||||
|
directly_connected_conn_map: Arc<DashMap<PeerId, DashSet<uuid::Uuid>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for PeerManager {
|
impl Debug for PeerManager {
|
||||||
@@ -267,6 +270,8 @@ impl PeerManager {
|
|||||||
data_compress_algo,
|
data_compress_algo,
|
||||||
|
|
||||||
exit_nodes,
|
exit_nodes,
|
||||||
|
|
||||||
|
directly_connected_conn_map: Arc::new(DashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,8 +330,48 @@ impl PeerManager {
|
|||||||
Ok((peer_id, conn_id))
|
Ok((peer_id, conn_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_directly_connected_conn(&self, peer_id: PeerId, conn_id: uuid::Uuid) {
|
||||||
|
let _ = self
|
||||||
|
.directly_connected_conn_map
|
||||||
|
.entry(peer_id)
|
||||||
|
.or_insert_with(DashSet::new)
|
||||||
|
.insert(conn_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_directly_connected_conn(&self, peer_id: PeerId) -> bool {
|
||||||
|
self.directly_connected_conn_map
|
||||||
|
.get(&peer_id)
|
||||||
|
.map_or(false, |x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_peer_conn_close_event_handler(&self) {
|
||||||
|
let dmap = self.directly_connected_conn_map.clone();
|
||||||
|
let mut event_recv = self.global_ctx.subscribe();
|
||||||
|
self.tasks.lock().await.spawn(async move {
|
||||||
|
while let Ok(event) = event_recv.recv().await {
|
||||||
|
match event {
|
||||||
|
GlobalCtxEvent::PeerConnRemoved(info) => {
|
||||||
|
if let Some(set) = dmap.get_mut(&info.peer_id) {
|
||||||
|
let conn_id = info.conn_id.parse().unwrap();
|
||||||
|
let old = set.remove(&conn_id);
|
||||||
|
tracing::info!(
|
||||||
|
?old,
|
||||||
|
?info,
|
||||||
|
"try remove conn id from directly connected map"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn try_connect<C>(&self, mut connector: C) -> Result<(PeerId, PeerConnId), Error>
|
pub async fn try_direct_connect<C>(
|
||||||
|
&self,
|
||||||
|
mut connector: C,
|
||||||
|
) -> Result<(PeerId, PeerConnId), Error>
|
||||||
where
|
where
|
||||||
C: TunnelConnector + Debug,
|
C: TunnelConnector + Debug,
|
||||||
{
|
{
|
||||||
@@ -334,18 +379,28 @@ impl PeerManager {
|
|||||||
let t = ns
|
let t = ns
|
||||||
.run_async(|| async move { connector.connect().await })
|
.run_async(|| async move { connector.connect().await })
|
||||||
.await?;
|
.await?;
|
||||||
self.add_client_tunnel(t).await
|
let (peer_id, conn_id) = self.add_client_tunnel(t).await?;
|
||||||
|
self.add_directly_connected_conn(peer_id, conn_id);
|
||||||
|
Ok((peer_id, conn_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn add_tunnel_as_server(&self, tunnel: Box<dyn Tunnel>) -> Result<(), Error> {
|
pub async fn add_tunnel_as_server(
|
||||||
|
&self,
|
||||||
|
tunnel: Box<dyn Tunnel>,
|
||||||
|
is_directly_connected: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
tracing::info!("add tunnel as server start");
|
tracing::info!("add tunnel as server start");
|
||||||
let mut peer = PeerConn::new(self.my_peer_id, self.global_ctx.clone(), tunnel);
|
let mut peer = PeerConn::new(self.my_peer_id, self.global_ctx.clone(), tunnel);
|
||||||
peer.do_handshake_as_server().await?;
|
peer.do_handshake_as_server().await?;
|
||||||
if peer.get_network_identity().network_name
|
if peer.get_network_identity().network_name
|
||||||
== self.global_ctx.get_network_identity().network_name
|
== self.global_ctx.get_network_identity().network_name
|
||||||
{
|
{
|
||||||
|
let (peer_id, conn_id) = (peer.get_peer_id(), peer.get_conn_id());
|
||||||
self.add_new_peer_conn(peer).await?;
|
self.add_new_peer_conn(peer).await?;
|
||||||
|
if is_directly_connected {
|
||||||
|
self.add_directly_connected_conn(peer_id, conn_id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.foreign_network_manager.add_peer_conn(peer).await?;
|
self.foreign_network_manager.add_peer_conn(peer).await?;
|
||||||
}
|
}
|
||||||
@@ -417,6 +472,7 @@ impl PeerManager {
|
|||||||
let foreign_client = self.foreign_network_client.clone();
|
let foreign_client = self.foreign_network_client.clone();
|
||||||
let foreign_mgr = self.foreign_network_manager.clone();
|
let foreign_mgr = self.foreign_network_manager.clone();
|
||||||
let encryptor = self.encryptor.clone();
|
let encryptor = self.encryptor.clone();
|
||||||
|
let compress_algo = self.data_compress_algo;
|
||||||
self.tasks.lock().await.spawn(async move {
|
self.tasks.lock().await.spawn(async move {
|
||||||
tracing::trace!("start_peer_recv");
|
tracing::trace!("start_peer_recv");
|
||||||
while let Ok(ret) = recv_packet_from_chan(&mut recv).await {
|
while let Ok(ret) = recv_packet_from_chan(&mut recv).await {
|
||||||
@@ -447,6 +503,16 @@ impl PeerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hdr.forward_counter += 1;
|
hdr.forward_counter += 1;
|
||||||
|
|
||||||
|
if from_peer_id == my_peer_id
|
||||||
|
&& (hdr.packet_type == PacketType::Data as u8
|
||||||
|
|| hdr.packet_type == PacketType::KcpSrc as u8
|
||||||
|
|| hdr.packet_type == PacketType::KcpDst as u8)
|
||||||
|
{
|
||||||
|
let _ = Self::try_compress_and_encrypt(compress_algo, &encryptor, &mut ret)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
tracing::trace!(?to_peer_id, ?my_peer_id, "need forward");
|
tracing::trace!(?to_peer_id, ?my_peer_id, "need forward");
|
||||||
let ret =
|
let ret =
|
||||||
Self::send_msg_internal(&peers, &foreign_client, ret, to_peer_id).await;
|
Self::send_msg_internal(&peers, &foreign_client, ret, to_peer_id).await;
|
||||||
@@ -757,6 +823,20 @@ impl PeerManager {
|
|||||||
(dst_peers, is_exit_node)
|
(dst_peers, is_exit_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn try_compress_and_encrypt(
|
||||||
|
compress_algo: CompressorAlgo,
|
||||||
|
encryptor: &Box<dyn Encryptor>,
|
||||||
|
msg: &mut ZCPacket,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let compressor = DefaultCompressor {};
|
||||||
|
compressor
|
||||||
|
.compress(msg, compress_algo)
|
||||||
|
.await
|
||||||
|
.with_context(|| "compress failed")?;
|
||||||
|
encryptor.encrypt(msg).with_context(|| "encrypt failed")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_msg_ipv4(&self, mut msg: ZCPacket, ipv4_addr: Ipv4Addr) -> Result<(), Error> {
|
pub async fn send_msg_ipv4(&self, mut msg: ZCPacket, ipv4_addr: Ipv4Addr) -> Result<(), Error> {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"do send_msg in peer manager, msg: {:?}, ipv4_addr: {}",
|
"do send_msg in peer manager, msg: {:?}, ipv4_addr: {}",
|
||||||
@@ -788,14 +868,7 @@ impl PeerManager {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let compressor = DefaultCompressor {};
|
Self::try_compress_and_encrypt(self.data_compress_algo, &self.encryptor, &mut msg).await?;
|
||||||
compressor
|
|
||||||
.compress(&mut msg, self.data_compress_algo)
|
|
||||||
.await
|
|
||||||
.with_context(|| "compress failed")?;
|
|
||||||
self.encryptor
|
|
||||||
.encrypt(&mut msg)
|
|
||||||
.with_context(|| "encrypt failed")?;
|
|
||||||
|
|
||||||
let is_latency_first = self.global_ctx.get_flags().latency_first;
|
let is_latency_first = self.global_ctx.get_flags().latency_first;
|
||||||
msg.mut_peer_manager_header()
|
msg.mut_peer_manager_header()
|
||||||
@@ -839,9 +912,11 @@ impl PeerManager {
|
|||||||
|
|
||||||
async fn run_clean_peer_without_conn_routine(&self) {
|
async fn run_clean_peer_without_conn_routine(&self) {
|
||||||
let peer_map = self.peers.clone();
|
let peer_map = self.peers.clone();
|
||||||
|
let dmap = self.directly_connected_conn_map.clone();
|
||||||
self.tasks.lock().await.spawn(async move {
|
self.tasks.lock().await.spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
peer_map.clean_peer_without_conn().await;
|
peer_map.clean_peer_without_conn().await;
|
||||||
|
dmap.retain(|p, v| peer_map.has_peer(*p) && !v.is_empty());
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -858,6 +933,8 @@ impl PeerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&self) -> Result<(), Error> {
|
pub async fn run(&self) -> Result<(), Error> {
|
||||||
|
self.start_peer_conn_close_event_handler().await;
|
||||||
|
|
||||||
match &self.route_algo_inst {
|
match &self.route_algo_inst {
|
||||||
RouteAlgoInst::Ospf(route) => self.add_route(route.clone()).await,
|
RouteAlgoInst::Ospf(route) => self.add_route(route.clone()).await,
|
||||||
RouteAlgoInst::None => {}
|
RouteAlgoInst::None => {}
|
||||||
@@ -906,7 +983,7 @@ impl PeerManager {
|
|||||||
self.foreign_network_client.clone()
|
self.foreign_network_client.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_my_info(&self) -> cli::NodeInfo {
|
pub async fn get_my_info(&self) -> cli::NodeInfo {
|
||||||
cli::NodeInfo {
|
cli::NodeInfo {
|
||||||
peer_id: self.my_peer_id,
|
peer_id: self.my_peer_id,
|
||||||
ipv4_addr: self
|
ipv4_addr: self
|
||||||
@@ -932,6 +1009,7 @@ impl PeerManager {
|
|||||||
config: self.global_ctx.config.dump(),
|
config: self.global_ctx.config.dump(),
|
||||||
version: EASYTIER_VERSION.to_string(),
|
version: EASYTIER_VERSION.to_string(),
|
||||||
feature_flag: Some(self.global_ctx.get_feature_flags()),
|
feature_flag: Some(self.global_ctx.get_feature_flags()),
|
||||||
|
ip_list: Some(self.global_ctx.get_ip_collector().collect_ip_addrs().await),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -940,6 +1018,13 @@ impl PeerManager {
|
|||||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_directly_connections(&self, peer_id: PeerId) -> DashSet<uuid::Uuid> {
|
||||||
|
self.directly_connected_conn_map
|
||||||
|
.get(&peer_id)
|
||||||
|
.map(|x| x.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -1008,7 +1093,7 @@ mod tests {
|
|||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
client.set_bind_addrs(vec![]);
|
client.set_bind_addrs(vec![]);
|
||||||
client_mgr.try_connect(client).await.unwrap();
|
client_mgr.try_direct_connect(client).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
server_mgr
|
server_mgr
|
||||||
@@ -1045,6 +1130,7 @@ mod tests {
|
|||||||
let connector1 = create_connector_by_url(
|
let connector1 = create_connector_by_url(
|
||||||
format!("{}://127.0.0.1:31013", proto1).as_str(),
|
format!("{}://127.0.0.1:31013", proto1).as_str(),
|
||||||
&peer_mgr_a.get_global_ctx(),
|
&peer_mgr_a.get_global_ctx(),
|
||||||
|
crate::tunnel::IpVersion::Both,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1063,6 +1149,7 @@ mod tests {
|
|||||||
let connector2 = create_connector_by_url(
|
let connector2 = create_connector_by_url(
|
||||||
format!("{}://127.0.0.1:31014", proto2).as_str(),
|
format!("{}://127.0.0.1:31014", proto2).as_str(),
|
||||||
&peer_mgr_b.get_global_ctx(),
|
&peer_mgr_b.get_global_ctx(),
|
||||||
|
crate::tunnel::IpVersion::Both,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -212,6 +212,11 @@ impl PeerMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_peer_default_conn_id(&self, peer_id: PeerId) -> Option<PeerConnId> {
|
||||||
|
self.get_peer_by_id(peer_id)
|
||||||
|
.map(|p| p.get_default_conn_id())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn close_peer_conn(
|
pub async fn close_peer_conn(
|
||||||
&self,
|
&self,
|
||||||
peer_id: PeerId,
|
peer_id: PeerId,
|
||||||
|
|||||||
@@ -1450,9 +1450,6 @@ impl PeerRouteServiceImpl {
|
|||||||
let my_peer_id = self.my_peer_id;
|
let my_peer_id = self.my_peer_id;
|
||||||
|
|
||||||
let (peer_infos, conn_bitmap, foreign_network) = self.build_sync_request(&session);
|
let (peer_infos, conn_bitmap, foreign_network) = self.build_sync_request(&session);
|
||||||
tracing::trace!(?foreign_network, "building sync_route request. my_id {:?}, pper_id: {:?}, peer_infos: {:?}, conn_bitmap: {:?}, synced_route_info: {:?} session: {:?}",
|
|
||||||
my_peer_id, dst_peer_id, peer_infos, conn_bitmap, self.synced_route_info, session);
|
|
||||||
|
|
||||||
if peer_infos.is_none()
|
if peer_infos.is_none()
|
||||||
&& conn_bitmap.is_none()
|
&& conn_bitmap.is_none()
|
||||||
&& foreign_network.is_none()
|
&& foreign_network.is_none()
|
||||||
@@ -1462,6 +1459,9 @@ impl PeerRouteServiceImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::debug!(?foreign_network, "sync_route request need send to peer. my_id {:?}, pper_id: {:?}, peer_infos: {:?}, conn_bitmap: {:?}, synced_route_info: {:?} session: {:?}",
|
||||||
|
my_peer_id, dst_peer_id, peer_infos, conn_bitmap, self.synced_route_info, session);
|
||||||
|
|
||||||
session
|
session
|
||||||
.need_sync_initiator_info
|
.need_sync_initiator_info
|
||||||
.store(false, Ordering::Relaxed);
|
.store(false, Ordering::Relaxed);
|
||||||
@@ -1728,7 +1728,6 @@ impl RouteSessionManager {
|
|||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
async fn maintain_sessions(&self, service_impl: Arc<PeerRouteServiceImpl>) -> bool {
|
async fn maintain_sessions(&self, service_impl: Arc<PeerRouteServiceImpl>) -> bool {
|
||||||
let mut cur_dst_peer_id_to_initiate = None;
|
let mut cur_dst_peer_id_to_initiate = None;
|
||||||
let mut next_sleep_ms = 0;
|
let mut next_sleep_ms = 0;
|
||||||
@@ -1764,8 +1763,6 @@ impl RouteSessionManager {
|
|||||||
.map(|x| *x)
|
.map(|x| *x)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
tracing::trace!(?service_impl.my_peer_id, ?peers, ?session_peers, ?initiator_candidates, "maintain_sessions begin");
|
|
||||||
|
|
||||||
if initiator_candidates.is_empty() {
|
if initiator_candidates.is_empty() {
|
||||||
next_sleep_ms = 1000;
|
next_sleep_ms = 1000;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -83,12 +83,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
?peers_to_connect,
|
|
||||||
?to_remove,
|
|
||||||
"got peers to connect and remove"
|
|
||||||
);
|
|
||||||
|
|
||||||
for key in to_remove {
|
for key in to_remove {
|
||||||
if let Some((_, task)) = peer_task_map.remove(&key) {
|
if let Some((_, task)) = peer_task_map.remove(&key) {
|
||||||
task.abort();
|
task.abort();
|
||||||
@@ -115,7 +109,6 @@ where
|
|||||||
.insert(item.clone(), launcher.launch_task(&data, item).await.into());
|
.insert(item.clone(), launcher.launch_task(&data, item).await.into());
|
||||||
}
|
}
|
||||||
} else if peer_task_map.is_empty() {
|
} else if peer_task_map.is_empty() {
|
||||||
tracing::debug!("all task done");
|
|
||||||
launcher.all_task_done(&data).await;
|
launcher.all_task_done(&data).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,23 @@ impl PeerManagerRpcService {
|
|||||||
.await
|
.await
|
||||||
.iter(),
|
.iter(),
|
||||||
);
|
);
|
||||||
|
let peer_map = self.peer_manager.get_peer_map();
|
||||||
let mut peer_infos = Vec::new();
|
let mut peer_infos = Vec::new();
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
let mut peer_info = PeerInfo::default();
|
let mut peer_info = PeerInfo::default();
|
||||||
peer_info.peer_id = peer;
|
peer_info.peer_id = peer;
|
||||||
|
peer_info.default_conn_id = peer_map
|
||||||
|
.get_peer_default_conn_id(peer)
|
||||||
|
.await
|
||||||
|
.map(Into::into);
|
||||||
|
peer_info.directly_connected_conns = self
|
||||||
|
.peer_manager
|
||||||
|
.get_directly_connections(peer)
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect();
|
||||||
|
|
||||||
if let Some(conns) = self.peer_manager.get_peer_map().list_peer_conns(peer).await {
|
if let Some(conns) = peer_map.list_peer_conns(peer).await {
|
||||||
peer_info.conns = conns;
|
peer_info.conns = conns;
|
||||||
} else if let Some(conns) = self
|
} else if let Some(conns) = self
|
||||||
.peer_manager
|
.peer_manager
|
||||||
@@ -121,7 +132,7 @@ impl PeerManageRpc for PeerManagerRpcService {
|
|||||||
_request: ShowNodeInfoRequest, // Accept request of type HelloRequest
|
_request: ShowNodeInfoRequest, // Accept request of type HelloRequest
|
||||||
) -> Result<ShowNodeInfoResponse, rpc_types::error::Error> {
|
) -> Result<ShowNodeInfoResponse, rpc_types::error::Error> {
|
||||||
Ok(ShowNodeInfoResponse {
|
Ok(ShowNodeInfoResponse {
|
||||||
node_info: Some(self.peer_manager.get_my_info()),
|
node_info: Some(self.peer_manager.get_my_info().await),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub async fn connect_peer_manager(client: Arc<PeerManager>, server: Arc<PeerMana
|
|||||||
});
|
});
|
||||||
let b_mgr_copy = server.clone();
|
let b_mgr_copy = server.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
b_mgr_copy.add_tunnel_as_server(b_ring).await.unwrap();
|
b_mgr_copy.add_tunnel_as_server(b_ring, true).await.unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
import "common.proto";
|
import "common.proto";
|
||||||
|
import "peer_rpc.proto";
|
||||||
|
|
||||||
package cli;
|
package cli;
|
||||||
|
|
||||||
@@ -34,6 +35,8 @@ message PeerConnInfo {
|
|||||||
message PeerInfo {
|
message PeerInfo {
|
||||||
uint32 peer_id = 1;
|
uint32 peer_id = 1;
|
||||||
repeated PeerConnInfo conns = 2;
|
repeated PeerConnInfo conns = 2;
|
||||||
|
common.UUID default_conn_id = 3;
|
||||||
|
repeated common.UUID directly_connected_conns = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListPeerRequest {}
|
message ListPeerRequest {}
|
||||||
@@ -79,6 +82,7 @@ message NodeInfo {
|
|||||||
string config = 8;
|
string config = 8;
|
||||||
string version = 9;
|
string version = 9;
|
||||||
common.PeerFeatureFlag feature_flag = 10;
|
common.PeerFeatureFlag feature_flag = 10;
|
||||||
|
peer_rpc.GetIpListResponse ip_list = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ShowNodeInfoRequest {}
|
message ShowNodeInfoRequest {}
|
||||||
@@ -177,3 +181,43 @@ service VpnPortalRpc {
|
|||||||
rpc GetVpnPortalInfo(GetVpnPortalInfoRequest)
|
rpc GetVpnPortalInfo(GetVpnPortalInfoRequest)
|
||||||
returns (GetVpnPortalInfoResponse);
|
returns (GetVpnPortalInfoResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TcpProxyEntryTransportType {
|
||||||
|
TCP = 0;
|
||||||
|
KCP = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TcpProxyEntryState {
|
||||||
|
Unknown = 0;
|
||||||
|
// receive syn packet but not start connecting to dst
|
||||||
|
SynReceived = 1;
|
||||||
|
// connecting to dst
|
||||||
|
ConnectingDst = 2;
|
||||||
|
// connected to dst
|
||||||
|
Connected = 3;
|
||||||
|
// connection closed
|
||||||
|
Closed = 4;
|
||||||
|
// closing src
|
||||||
|
ClosingSrc = 5;
|
||||||
|
// closing dst
|
||||||
|
ClosingDst = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TcpProxyEntry {
|
||||||
|
common.SocketAddr src = 1;
|
||||||
|
common.SocketAddr dst = 2;
|
||||||
|
uint64 start_time = 3;
|
||||||
|
TcpProxyEntryState state = 4;
|
||||||
|
TcpProxyEntryTransportType transport_type = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTcpProxyEntryRequest {}
|
||||||
|
|
||||||
|
message ListTcpProxyEntryResponse {
|
||||||
|
repeated TcpProxyEntry entries = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service TcpProxyRpc {
|
||||||
|
rpc ListTcpProxyEntry(ListTcpProxyEntryRequest)
|
||||||
|
returns (ListTcpProxyEntryResponse);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ impl PeerRoutePair {
|
|||||||
pub fn get_latency_ms(&self) -> Option<f64> {
|
pub fn get_latency_ms(&self) -> Option<f64> {
|
||||||
let mut ret = u64::MAX;
|
let mut ret = u64::MAX;
|
||||||
let p = self.peer.as_ref()?;
|
let p = self.peer.as_ref()?;
|
||||||
|
let default_conn_id = p.default_conn_id.map(|id| id.to_string());
|
||||||
for conn in p.conns.iter() {
|
for conn in p.conns.iter() {
|
||||||
let Some(stats) = &conn.stats else {
|
let Some(stats) = &conn.stats else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
if default_conn_id == Some(conn.conn_id.to_string()) {
|
||||||
|
return Some(f64::from(stats.latency_us as u32) / 1000.0);
|
||||||
|
}
|
||||||
ret = ret.min(stats.latency_us);
|
ret = ret.min(stats.latency_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ message FlagsInConfig {
|
|||||||
bool disable_p2p = 11;
|
bool disable_p2p = 11;
|
||||||
bool relay_all_peer_rpc = 12;
|
bool relay_all_peer_rpc = 12;
|
||||||
bool disable_udp_hole_punching = 13;
|
bool disable_udp_hole_punching = 13;
|
||||||
string ipv6_listener = 14;
|
// string ipv6_listener = 14; [deprecated = true]; use -l udp://[::]:12345 instead
|
||||||
bool multi_thread = 15;
|
bool multi_thread = 15;
|
||||||
CompressionAlgoPb data_compress_algo = 16;
|
CompressionAlgoPb data_compress_algo = 16;
|
||||||
bool bind_device = 17;
|
bool bind_device = 17;
|
||||||
@@ -29,6 +29,7 @@ message FlagsInConfig {
|
|||||||
bool disable_kcp_input = 19;
|
bool disable_kcp_input = 19;
|
||||||
// allow relay kcp packets (for public server, this can reduce the throughput)
|
// allow relay kcp packets (for public server, this can reduce the throughput)
|
||||||
bool disable_relay_kcp = 20;
|
bool disable_relay_kcp = 20;
|
||||||
|
bool proxy_forward_by_system = 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcDescriptor {
|
message RpcDescriptor {
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ impl From<cidr::Ipv4Inet> for Ipv4Inet {
|
|||||||
|
|
||||||
impl From<Ipv4Inet> for cidr::Ipv4Inet {
|
impl From<Ipv4Inet> for cidr::Ipv4Inet {
|
||||||
fn from(value: Ipv4Inet) -> Self {
|
fn from(value: Ipv4Inet) -> Self {
|
||||||
cidr::Ipv4Inet::new(value.address.unwrap().into(), value.network_length as u8).unwrap()
|
cidr::Ipv4Inet::new(
|
||||||
|
value.address.unwrap_or_default().into(),
|
||||||
|
value.network_length as u8,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +172,9 @@ impl From<std::net::SocketAddr> for SocketAddr {
|
|||||||
|
|
||||||
impl From<SocketAddr> for std::net::SocketAddr {
|
impl From<SocketAddr> for std::net::SocketAddr {
|
||||||
fn from(value: SocketAddr) -> Self {
|
fn from(value: SocketAddr) -> Self {
|
||||||
|
if value.ip.is_none() {
|
||||||
|
return "0.0.0.0:0".parse().unwrap();
|
||||||
|
}
|
||||||
match value.ip.unwrap() {
|
match value.ip.unwrap() {
|
||||||
socket_addr::Ip::Ipv4(ip) => std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
|
socket_addr::Ip::Ipv4(ip) => std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
|
||||||
std::net::Ipv4Addr::from(ip),
|
std::net::Ipv4Addr::from(ip),
|
||||||
|
|||||||
@@ -207,5 +207,6 @@ message HandshakeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message KcpConnData {
|
message KcpConnData {
|
||||||
|
common.SocketAddr src = 1;
|
||||||
common.SocketAddr dst = 4;
|
common.SocketAddr dst = 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,19 @@ message NetworkConfig {
|
|||||||
optional bool disable_p2p = 24;
|
optional bool disable_p2p = 24;
|
||||||
optional bool bind_device = 25;
|
optional bool bind_device = 25;
|
||||||
optional bool no_tun = 26;
|
optional bool no_tun = 26;
|
||||||
|
|
||||||
|
optional bool enable_exit_node = 27;
|
||||||
|
optional bool relay_all_peer_rpc = 28;
|
||||||
|
optional bool multi_thread = 29;
|
||||||
|
optional bool enable_relay_network_whitelist = 30;
|
||||||
|
repeated string relay_network_whitelist = 31;
|
||||||
|
optional bool enable_manual_routes = 32;
|
||||||
|
repeated string routes = 33;
|
||||||
|
repeated string exit_nodes = 34;
|
||||||
|
optional bool proxy_forward_by_system = 35;
|
||||||
|
optional bool disable_encryption = 36;
|
||||||
|
optional bool enable_socks5 = 37;
|
||||||
|
optional int32 socks5_port = 38;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MyNodeInfo {
|
message MyNodeInfo {
|
||||||
|
|||||||
@@ -499,17 +499,20 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str
|
|||||||
|
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
for _ in 1..=2 {
|
for _ in 1..=2 {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(8)).await;
|
|
||||||
// inst4 should be in inst1's route list
|
// inst4 should be in inst1's route list
|
||||||
let routes = insts[0].get_peer_manager().list_routes().await;
|
wait_for_condition(
|
||||||
assert!(
|
|| async {
|
||||||
routes
|
insts[0]
|
||||||
.iter()
|
.get_peer_manager()
|
||||||
.find(|r| r.peer_id == inst4.peer_id())
|
.list_routes()
|
||||||
.is_some(),
|
.await
|
||||||
"inst4 should be in inst1's route list, {:?}",
|
.iter()
|
||||||
routes
|
.find(|r| r.peer_id == inst4.peer_id())
|
||||||
);
|
.is_some()
|
||||||
|
},
|
||||||
|
Duration::from_secs(8),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
set_link_status("net_d", false);
|
set_link_status("net_d", false);
|
||||||
let _t = ScopedTask::from(tokio::spawn(async move {
|
let _t = ScopedTask::from(tokio::spawn(async move {
|
||||||
@@ -518,6 +521,35 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str
|
|||||||
ping_test("net_a", "10.144.144.4", Some(1)).await;
|
ping_test("net_a", "10.144.144.4", Some(1)).await;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
wait_for_condition(
|
||||||
|
|| async {
|
||||||
|
let ret = insts[2]
|
||||||
|
.get_peer_manager()
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peers_with_conn()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.find(|r| **r == inst4.peer_id())
|
||||||
|
.is_none();
|
||||||
|
if !ret {
|
||||||
|
println!(
|
||||||
|
"conn info: {:?}",
|
||||||
|
insts[2]
|
||||||
|
.get_peer_manager()
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(inst4.peer_id())
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
},
|
||||||
|
// 0 down, assume last packet is recv in -0.01
|
||||||
|
// [2, 7) send ping
|
||||||
|
// [4, 9) ping fail and close connection
|
||||||
|
Duration::from_secs(11),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
wait_for_condition(
|
wait_for_condition(
|
||||||
|| async {
|
|| async {
|
||||||
insts[0]
|
insts[0]
|
||||||
@@ -528,12 +560,10 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str
|
|||||||
.find(|r| r.peer_id == inst4.peer_id())
|
.find(|r| r.peer_id == inst4.peer_id())
|
||||||
.is_none()
|
.is_none()
|
||||||
},
|
},
|
||||||
// 0 down, assume last packet is recv in -0.01
|
Duration::from_secs(7),
|
||||||
// [2, 7) send ping
|
|
||||||
// [4, 9) ping fail and close connection
|
|
||||||
Duration::from_secs(11),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
set_link_status("net_d", true);
|
set_link_status("net_d", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -360,7 +360,13 @@ pub(crate) fn setup_sokcet2_ext(
|
|||||||
|
|
||||||
socket2_socket.set_nonblocking(true)?;
|
socket2_socket.set_nonblocking(true)?;
|
||||||
socket2_socket.set_reuse_address(true)?;
|
socket2_socket.set_reuse_address(true)?;
|
||||||
socket2_socket.bind(&socket2::SockAddr::from(*bind_addr))?;
|
if let Err(e) = socket2_socket.bind(&socket2::SockAddr::from(*bind_addr)) {
|
||||||
|
if bind_addr.is_ipv4() {
|
||||||
|
return Err(e.into());
|
||||||
|
} else {
|
||||||
|
tracing::warn!(?e, "bind failed, do not return error for ipv6");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
|
// #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
|
||||||
// socket2_socket.set_reuse_port(true)?;
|
// socket2_socket.set_reuse_port(true)?;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ pub fn get_insecure_tls_client_config() -> rustls::ClientConfig {
|
|||||||
.dangerous()
|
.dangerous()
|
||||||
.with_custom_certificate_verifier(SkipServerVerification::new(provider.clone()))
|
.with_custom_certificate_verifier(SkipServerVerification::new(provider.clone()))
|
||||||
.with_no_client_auth();
|
.with_no_client_auth();
|
||||||
config.enable_sni = false;
|
config.enable_sni = true;
|
||||||
config.enable_early_data = false;
|
config.enable_early_data = false;
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ pub mod stats;
|
|||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
pub mod udp;
|
pub mod udp;
|
||||||
|
|
||||||
|
pub const PROTO_PORT_OFFSET: &[(&str, u16)] =
|
||||||
|
&[("tcp", 0), ("udp", 0), ("wg", 1), ("ws", 1), ("wss", 2)];
|
||||||
|
|
||||||
#[cfg(feature = "wireguard")]
|
#[cfg(feature = "wireguard")]
|
||||||
pub mod wireguard;
|
pub mod wireguard;
|
||||||
|
|
||||||
@@ -123,7 +126,7 @@ pub trait TunnelListener: Send {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[auto_impl::auto_impl(Box)]
|
#[auto_impl::auto_impl(Box, &mut)]
|
||||||
pub trait TunnelConnector: Send {
|
pub trait TunnelConnector: Send {
|
||||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError>;
|
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, TunnelError>;
|
||||||
fn remote_url(&self) -> url::Url;
|
fn remote_url(&self) -> url::Url;
|
||||||
@@ -190,6 +193,7 @@ where
|
|||||||
pub(crate) fn check_scheme_and_get_socket_addr<T>(
|
pub(crate) fn check_scheme_and_get_socket_addr<T>(
|
||||||
url: &url::Url,
|
url: &url::Url,
|
||||||
scheme: &str,
|
scheme: &str,
|
||||||
|
ip_version: IpVersion,
|
||||||
) -> Result<T, TunnelError>
|
) -> Result<T, TunnelError>
|
||||||
where
|
where
|
||||||
T: FromUrl,
|
T: FromUrl,
|
||||||
@@ -198,12 +202,24 @@ where
|
|||||||
return Err(TunnelError::InvalidProtocol(url.scheme().to_string()));
|
return Err(TunnelError::InvalidProtocol(url.scheme().to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(T::from_url(url.clone(), IpVersion::Both)?)
|
Ok(T::from_url(url.clone(), ip_version)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_port(scheme: &str) -> Option<u16> {
|
||||||
|
match scheme {
|
||||||
|
"tcp" => Some(11010),
|
||||||
|
"udp" => Some(11010),
|
||||||
|
"ws" => Some(11011),
|
||||||
|
"wss" => Some(11012),
|
||||||
|
"quic" => Some(11012),
|
||||||
|
"wg" => Some(11011),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromUrl for SocketAddr {
|
impl FromUrl for SocketAddr {
|
||||||
fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> {
|
fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> {
|
||||||
let addrs = url.socket_addrs(|| None)?;
|
let addrs = url.socket_addrs(|| default_port(url.scheme()))?;
|
||||||
tracing::debug!(?addrs, ?ip_version, ?url, "convert url to socket addrs");
|
tracing::debug!(?addrs, ?ip_version, ?url, "convert url to socket addrs");
|
||||||
let addrs = addrs
|
let addrs = addrs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ impl QUICTunnelListener {
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl TunnelListener for QUICTunnelListener {
|
impl TunnelListener for QUICTunnelListener {
|
||||||
async fn listen(&mut self) -> Result<(), TunnelError> {
|
async fn listen(&mut self) -> Result<(), TunnelError> {
|
||||||
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "quic")?;
|
let addr =
|
||||||
|
check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "quic", IpVersion::Both)?;
|
||||||
let (endpoint, server_cert) = make_server_endpoint(addr).unwrap();
|
let (endpoint, server_cert) = make_server_endpoint(addr).unwrap();
|
||||||
self.endpoint = Some(endpoint);
|
self.endpoint = Some(endpoint);
|
||||||
self.server_cert = Some(server_cert);
|
self.server_cert = Some(server_cert);
|
||||||
|
|||||||
@@ -231,7 +231,11 @@ fn get_tunnel_for_server(conn: Arc<Connection>) -> impl Tunnel {
|
|||||||
|
|
||||||
impl RingTunnelListener {
|
impl RingTunnelListener {
|
||||||
fn get_addr(&self) -> Result<uuid::Uuid, TunnelError> {
|
fn get_addr(&self) -> Result<uuid::Uuid, TunnelError> {
|
||||||
check_scheme_and_get_socket_addr::<Uuid>(&self.listerner_addr, "ring")
|
check_scheme_and_get_socket_addr::<Uuid>(
|
||||||
|
&self.listerner_addr,
|
||||||
|
"ring",
|
||||||
|
super::IpVersion::Both,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +288,11 @@ impl RingTunnelConnector {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TunnelConnector for RingTunnelConnector {
|
impl TunnelConnector for RingTunnelConnector {
|
||||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||||
let remote_addr = check_scheme_and_get_socket_addr::<Uuid>(&self.remote_addr, "ring")?;
|
let remote_addr = check_scheme_and_get_socket_addr::<Uuid>(
|
||||||
|
&self.remote_addr,
|
||||||
|
"ring",
|
||||||
|
super::IpVersion::Both,
|
||||||
|
)?;
|
||||||
let entry = CONNECTION_MAP
|
let entry = CONNECTION_MAP
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ impl TcpTunnelListener {
|
|||||||
impl TunnelListener for TcpTunnelListener {
|
impl TunnelListener for TcpTunnelListener {
|
||||||
async fn listen(&mut self) -> Result<(), TunnelError> {
|
async fn listen(&mut self) -> Result<(), TunnelError> {
|
||||||
self.listener = None;
|
self.listener = None;
|
||||||
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
let addr =
|
||||||
|
check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp", IpVersion::Both)?;
|
||||||
|
|
||||||
let socket2_socket = socket2::Socket::new(
|
let socket2_socket = socket2::Socket::new(
|
||||||
socket2::Domain::for_address(addr),
|
socket2::Domain::for_address(addr),
|
||||||
@@ -150,9 +151,9 @@ impl TcpTunnelConnector {
|
|||||||
&mut self,
|
&mut self,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||||
tracing::info!(addr = ?self.addr, "connect tcp start");
|
tracing::info!(url = ?self.addr, ?addr, "connect tcp start, bind addrs: {:?}", self.bind_addrs);
|
||||||
let stream = TcpStream::connect(addr).await?;
|
let stream = TcpStream::connect(addr).await?;
|
||||||
tracing::info!(addr = ?self.addr, "connect tcp succ");
|
tracing::info!(url = ?self.addr, ?addr, "connect tcp succ");
|
||||||
return get_tunnel_with_tcp_stream(stream, self.addr.clone().into());
|
return get_tunnel_with_tcp_stream(stream, self.addr.clone().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +191,7 @@ impl super::TunnelConnector for TcpTunnelConnector {
|
|||||||
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
async fn connect(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||||
let addr =
|
let addr =
|
||||||
check_scheme_and_get_socket_addr_ext::<SocketAddr>(&self.addr, "tcp", self.ip_version)?;
|
check_scheme_and_get_socket_addr_ext::<SocketAddr>(&self.addr, "tcp", self.ip_version)?;
|
||||||
if self.bind_addrs.is_empty() || addr.is_ipv6() {
|
if self.bind_addrs.is_empty() {
|
||||||
self.connect_with_default_bind(addr).await
|
self.connect_with_default_bind(addr).await
|
||||||
} else {
|
} else {
|
||||||
self.connect_with_custom_bind(addr).await
|
self.connect_with_custom_bind(addr).await
|
||||||
|
|||||||
@@ -141,12 +141,27 @@ async fn respond_stun_packet(
|
|||||||
.encode_into_bytes(resp_msg.clone())
|
.encode_into_bytes(resp_msg.clone())
|
||||||
.map_err(|e| anyhow::anyhow!("stun encode error: {:?}", e))?;
|
.map_err(|e| anyhow::anyhow!("stun encode error: {:?}", e))?;
|
||||||
|
|
||||||
socket
|
let change_req = req_msg
|
||||||
.send_to(&rsp_buf, addr.clone())
|
.get_attribute::<ChangeRequest>()
|
||||||
.await
|
.map(|r| r.ip() || r.port())
|
||||||
.with_context(|| "send stun response error")?;
|
.unwrap_or(false);
|
||||||
|
|
||||||
tracing::debug!(?addr, ?req_msg, "udp respond stun packet done");
|
if !change_req {
|
||||||
|
socket
|
||||||
|
.send_to(&rsp_buf, addr.clone())
|
||||||
|
.await
|
||||||
|
.with_context(|| "send stun response error")?;
|
||||||
|
} else {
|
||||||
|
// send from a new udp socket
|
||||||
|
let socket = if addr.is_ipv4() {
|
||||||
|
UdpSocket::bind("0.0.0.0:0").await?
|
||||||
|
} else {
|
||||||
|
UdpSocket::bind("[::]:0").await?
|
||||||
|
};
|
||||||
|
socket.send_to(&rsp_buf, addr.clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!(?addr, ?req_msg, ?change_req, "udp respond stun packet done");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +473,11 @@ impl UdpTunnelListener {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TunnelListener for UdpTunnelListener {
|
impl TunnelListener for UdpTunnelListener {
|
||||||
async fn listen(&mut self) -> Result<(), super::TunnelError> {
|
async fn listen(&mut self) -> Result<(), super::TunnelError> {
|
||||||
let addr = super::check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "udp")?;
|
let addr = super::check_scheme_and_get_socket_addr::<SocketAddr>(
|
||||||
|
&self.addr,
|
||||||
|
"udp",
|
||||||
|
IpVersion::Both,
|
||||||
|
)?;
|
||||||
|
|
||||||
let socket2_socket = socket2::Socket::new(
|
let socket2_socket = socket2::Socket::new(
|
||||||
socket2::Domain::for_address(addr),
|
socket2::Domain::for_address(addr),
|
||||||
@@ -942,6 +961,7 @@ mod tests {
|
|||||||
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(
|
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(
|
||||||
&format!("udp://{}:11111", ip.to_string()).parse().unwrap(),
|
&format!("udp://{}:11111", ip.to_string()).parse().unwrap(),
|
||||||
"udp",
|
"udp",
|
||||||
|
IpVersion::Both,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let socket2_socket = socket2::Socket::new(
|
let socket2_socket = socket2::Socket::new(
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ impl WSTunnelConnector {
|
|||||||
) -> Result<Box<dyn Tunnel>, TunnelError> {
|
) -> Result<Box<dyn Tunnel>, TunnelError> {
|
||||||
let is_wss = is_wss(&addr)?;
|
let is_wss = is_wss(&addr)?;
|
||||||
let socket_addr = SocketAddr::from_url(addr.clone(), ip_version)?;
|
let socket_addr = SocketAddr::from_url(addr.clone(), ip_version)?;
|
||||||
|
let domain = addr.domain();
|
||||||
let host = socket_addr.ip();
|
let host = socket_addr.ip();
|
||||||
let stream = tcp_socket.connect(socket_addr).await?;
|
let stream = tcp_socket.connect(socket_addr).await?;
|
||||||
|
|
||||||
@@ -203,8 +204,16 @@ impl WSTunnelConnector {
|
|||||||
init_crypto_provider();
|
init_crypto_provider();
|
||||||
let tls_conn =
|
let tls_conn =
|
||||||
tokio_rustls::TlsConnector::from(Arc::new(get_insecure_tls_client_config()));
|
tokio_rustls::TlsConnector::from(Arc::new(get_insecure_tls_client_config()));
|
||||||
|
let domain_or_ip = match domain {
|
||||||
|
None => {
|
||||||
|
host.to_string()
|
||||||
|
}
|
||||||
|
Some(domain) => {
|
||||||
|
domain.to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
let stream = tls_conn
|
let stream = tls_conn
|
||||||
.connect(host.to_string().try_into().unwrap(), stream)
|
.connect(domain_or_ip.try_into().unwrap(), stream)
|
||||||
.await?;
|
.await?;
|
||||||
MaybeTlsStream::Rustls(stream)
|
MaybeTlsStream::Rustls(stream)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||