Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
815c728837 | ||
|
|
bd7bc52c43 | ||
|
|
34cbd57e71 | ||
|
|
46e9ecd47d | ||
|
|
d0770e6c06 | ||
|
|
f87372ead0 | ||
|
|
04efb7033c | ||
|
|
9bae9f2f39 | ||
|
|
7c2057c360 | ||
|
|
f61b31f04e | ||
|
|
bb455a3eb7 | ||
|
|
631618a6be | ||
|
|
34946219c3 | ||
|
|
5d1b4ba78a | ||
|
|
7e921be539 | ||
|
|
716041bac1 | ||
|
|
6b72cefbc5 | ||
|
|
386c83874c | ||
|
|
5cc309dc9f | ||
|
|
4e8d6d08e2 | ||
|
|
c8a5d41906 | ||
|
|
2c57e51ec5 | ||
|
|
796a75af5c | ||
|
|
f09b79c6da | ||
|
|
1e84478837 | ||
|
|
2417c7ddd0 | ||
|
|
235a3c326c | ||
|
|
e4c85764f9 | ||
|
|
c650217919 | ||
|
|
262a1d49f8 | ||
|
|
f0a6c3de0b | ||
|
|
621da3c8fe | ||
|
|
801826f36d | ||
|
|
8557c4ab82 | ||
|
|
1fd1a2a3b6 | ||
|
|
bcdfda43af | ||
|
|
11a1f99169 | ||
|
|
464a687043 | ||
|
|
66c78c23fe | ||
|
|
e251161c5d | ||
|
|
b9e5968299 | ||
|
|
7a509f6975 | ||
|
|
772db7422f | ||
|
|
2f246537df | ||
|
|
4c74586ce0 | ||
|
|
2ac3169d77 | ||
|
|
6f18a97644 | ||
|
|
3b386b6b54 | ||
|
|
b37033d92c | ||
|
|
b7bab80bfe | ||
|
|
041a603173 | ||
|
|
e40994d62e | ||
|
|
5078a1f797 | ||
|
|
a22dacce0c | ||
|
|
064c9e4bb4 | ||
|
|
3cf0f6560f | ||
|
|
c4c26dd6d7 | ||
|
|
4240c47244 | ||
|
|
6e91f41a10 | ||
|
|
1a7cee157c | ||
|
|
72641270f1 | ||
|
|
19f8d3a0f4 | ||
|
|
bac9548f86 | ||
|
|
79f0eb497b | ||
|
|
94ae51458c | ||
|
|
778c89efb1 | ||
|
|
a7a0fa7cb5 | ||
|
|
2d8f6ae4f4 | ||
|
|
324dfd6a1f | ||
|
|
70242e6eb2 | ||
|
|
42cdfb0885 | ||
|
|
cea8403dbc | ||
|
|
0ebfc09f8b | ||
|
|
2e06125974 | ||
|
|
fc775102ff | ||
|
|
891f388040 | ||
|
|
8b7f3491b1 | ||
|
|
27ac9dec56 | ||
|
|
acf2c6d787 | ||
|
|
5f137710be | ||
|
|
1a6016f08f | ||
|
|
f67e8991ef | ||
|
|
d81010224d | ||
|
|
4c27143125 | ||
|
|
9461bbe8f3 | ||
|
|
1142cf105b | ||
|
|
5133af1863 | ||
|
|
7c7d554609 | ||
|
|
272a094fde | ||
|
|
b33d7954be | ||
|
|
f519be8e92 | ||
|
|
33331be361 | ||
|
|
04a9d307c5 | ||
|
|
35c2386c98 | ||
|
|
4f41300450 | ||
|
|
d6f99ed9a2 | ||
|
|
95fe0e5a78 | ||
|
|
b713303c15 | ||
|
|
d8f88e72c1 | ||
|
|
d3a459542d | ||
|
|
afeebe852d | ||
|
|
f1e941bf9f | ||
|
|
c871978475 | ||
|
|
411502cd0b | ||
|
|
243fb1fb06 | ||
|
|
9657dcf596 | ||
|
|
946845cd01 | ||
|
|
fd1c21b114 | ||
|
|
d8e3cb9e65 | ||
|
|
3a7904fa8e | ||
|
|
85a20769fb | ||
|
|
aeeca0d7d1 | ||
|
|
482d7fb8cc | ||
|
|
c291900e37 | ||
|
|
089352420f | ||
|
|
1addf8c9eb | ||
|
|
1f7d3fa05c | ||
|
|
c3b6e1351f | ||
|
|
336b281657 | ||
|
|
57898642e8 | ||
|
|
fa0c006ac5 | ||
|
|
178ff59623 | ||
|
|
7bbed69ad2 | ||
|
|
d4b6e6ee28 | ||
|
|
b3fbb9e179 | ||
|
|
b44eb1bbc3 | ||
|
|
d88286642e | ||
|
|
74ff886900 | ||
|
|
9e716b3b7b | ||
|
|
bea99ae315 | ||
|
|
6068b5941c | ||
|
|
675bf3c1f5 | ||
|
|
dc81956d42 | ||
|
|
ffe736be17 | ||
|
|
b83dae4cc4 | ||
|
|
8d9203ecdb | ||
|
|
13321b5a90 | ||
|
|
0a8c39c11f | ||
|
|
7dd812c79a | ||
|
|
9d524443ec | ||
|
|
35a192a478 | ||
|
|
2f4235a968 | ||
|
|
12b57238d2 | ||
|
|
fe805e8554 | ||
|
|
4d6d439b1a | ||
|
|
ec202209f3 | ||
|
|
49f10a288d | ||
|
|
986d16eb2d | ||
|
|
4fd83deaf1 | ||
|
|
85150127bb | ||
|
|
26d8c13fe4 | ||
|
|
388ae586ec | ||
|
|
6ad923d519 | ||
|
|
fe661fe067 | ||
|
|
ad40d65070 | ||
|
|
10bb0530ae | ||
|
|
7c3be2d9fb | ||
|
|
14301a7d5f | ||
|
|
d0841f7558 | ||
|
|
467298efa7 | ||
|
|
75203d2e4e | ||
|
|
27d8f9cbb4 | ||
|
|
7a0e300ff9 | ||
|
|
b2f381913d | ||
|
|
6ec46cb95f | ||
|
|
e2f4962ba8 | ||
|
|
7e307a5a1c | ||
|
|
33f54ba5aa | ||
|
|
6a83ffea62 | ||
|
|
af848f96df | ||
|
|
d88e4b5151 | ||
|
|
fe3b42809a | ||
|
|
2830be95a7 | ||
|
|
a974906fdc | ||
|
|
17ddc89bd0 | ||
|
|
088a009078 | ||
|
|
be2ce5c93b | ||
|
|
f8936eff93 | ||
|
|
accd96f1d8 | ||
|
|
32ee474813 | ||
|
|
46a7c025a0 | ||
|
|
3ca4035d0c | ||
|
|
86a75451d8 | ||
|
|
8cdfe0fec6 | ||
|
|
5aaad36729 | ||
|
|
fc83fa0a04 | ||
|
|
338af1af9d | ||
|
|
6538023a11 | ||
|
|
f139ad69a1 | ||
|
|
55fcf241c6 | ||
|
|
ebd73e1a09 | ||
|
|
00e6a016f8 | ||
|
|
78d7e9437e | ||
|
|
6bd5621fb0 | ||
|
|
ee794d2e40 | ||
|
|
605d0dd6c1 | ||
|
|
ebbe5d5297 | ||
|
|
cd1a9885db | ||
|
|
81d4fb6d6a | ||
|
|
a766aaf165 | ||
|
|
55b841afb5 | ||
|
|
d48913d7b5 | ||
|
|
1557203912 | ||
|
|
0e01cfcd3a | ||
|
|
e70d82b30f | ||
|
|
60a6d672c5 | ||
|
|
d7b2060a5b | ||
|
|
75a40412b4 | ||
|
|
93a89b8ea3 | ||
|
|
8f5ce48939 | ||
|
|
2314783d42 | ||
|
|
e732599941 | ||
|
|
29b45dddb4 | ||
|
|
650f2410ed | ||
|
|
c16101a44c | ||
|
|
4baab96183 | ||
|
|
ca2bc99a38 |
8
.cargo/config.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||||
|
[target.i686-pc-windows-msvc]
|
||||||
|
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||||
|
[target.'cfg(target_os="macos")']
|
||||||
|
rustflags = [
|
||||||
|
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
|
||||||
|
]
|
||||||
14
.github/ISSUE_TEMPLATE/ask-a-question.md
vendored
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
name: Ask a question
|
|
||||||
about: Ask the community for help
|
|
||||||
title: ''
|
|
||||||
labels: 'question'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This is the place for generic questions. Please stay on topic and be polite.
|
|
||||||
|
|
||||||
**Notes**
|
|
||||||
- Please write in english only. If you provide some images in different languages, you're required to write a translation in english.
|
|
||||||
- In any case, **NEVER** put here the content if your `id_ed25519` file
|
|
||||||
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gitsubmodule"
|
||||||
|
directory: "/"
|
||||||
|
target-branch: "master"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: "Git submodule"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
299
.github/workflows/build.yaml
vendored
@@ -4,8 +4,12 @@ name: build
|
|||||||
# please setup some secrets before running this workflow:
|
# please setup some secrets before running this workflow:
|
||||||
# DOCKER_IMAGE should be the target image name on docker hub (e.g. "rustdesk/rustdesk-server-s6" )
|
# DOCKER_IMAGE should be the target image name on docker hub (e.g. "rustdesk/rustdesk-server-s6" )
|
||||||
# DOCKER_IMAGE_CLASSIC should be the target image name on docker hub for the old build (e.g. "rustdesk/rustdesk-server" )
|
# DOCKER_IMAGE_CLASSIC should be the target image name on docker hub for the old build (e.g. "rustdesk/rustdesk-server" )
|
||||||
# DOCKER_USERNAME is the username you normally use to login at https://hub.docker.com/
|
# DOCKER_HUB_USERNAME is the username you normally use to login at https://hub.docker.com/
|
||||||
# DOCKER_PASSWORD is a token you should create under "account settings / security" with read/write access
|
# DOCKER_HUB_PASSWORD is a token you should create under "account settings / security" with read/write access
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -19,6 +23,8 @@ on:
|
|||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
LATEST_TAG: latest
|
LATEST_TAG: latest
|
||||||
|
GHCR_IMAGE: ghcr.io/rustdesk/rustdesk-server-s6
|
||||||
|
GHCR_IMAGE_CLASSIC: ghcr.io/rustdesk/rustdesk-server
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -35,16 +41,19 @@ jobs:
|
|||||||
- { name: "arm64v8", target: "aarch64-unknown-linux-musl" }
|
- { name: "arm64v8", target: "aarch64-unknown-linux-musl" }
|
||||||
- { name: "armv7", target: "armv7-unknown-linux-musleabihf" }
|
- { name: "armv7", target: "armv7-unknown-linux-musleabihf" }
|
||||||
- { name: "i386", target: "i686-unknown-linux-musl" }
|
- { name: "i386", target: "i686-unknown-linux-musl" }
|
||||||
|
#- { name: "amd64fb", target: "x86_64-unknown-freebsd" }
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install toolchain
|
- name: Install toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.90"
|
||||||
override: true
|
override: true
|
||||||
default: true
|
default: true
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
@@ -62,7 +71,7 @@ jobs:
|
|||||||
run: chmod -v a+x target/${{ matrix.job.target }}/release/*
|
run: chmod -v a+x target/${{ matrix.job.target }}/release/*
|
||||||
|
|
||||||
- name: Publish Artifacts
|
- name: Publish Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binaries-linux-${{ matrix.job.name }}
|
name: binaries-linux-${{ matrix.job.name }}
|
||||||
path: |
|
path: |
|
||||||
@@ -73,17 +82,19 @@ jobs:
|
|||||||
|
|
||||||
build-win:
|
build-win:
|
||||||
name: Build - windows
|
name: Build - windows
|
||||||
runs-on: windows-2019
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install toolchain
|
- name: Install toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: "1.62"
|
toolchain: "1.90"
|
||||||
override: true
|
override: true
|
||||||
default: true
|
default: true
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
@@ -97,14 +108,67 @@ jobs:
|
|||||||
args: --release --all-features --target=x86_64-pc-windows-msvc
|
args: --release --all-features --target=x86_64-pc-windows-msvc
|
||||||
use-cross: true
|
use-cross: true
|
||||||
|
|
||||||
|
- name: Install NSIS
|
||||||
|
run: |
|
||||||
|
iwr -useb get.scoop.sh -outfile 'install.ps1'
|
||||||
|
.\install.ps1 -RunAsAdmin
|
||||||
|
scoop update
|
||||||
|
scoop bucket add extras
|
||||||
|
scoop install nsis
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
|
||||||
|
- name: Sign exe files
|
||||||
|
uses: GermanBluefox/code-sign-action@v7
|
||||||
|
if: false
|
||||||
|
with:
|
||||||
|
certificate: '${{ secrets.WINDOWS_PFX_BASE64 }}'
|
||||||
|
password: '${{ secrets.WINDOWS_PFX_PASSWORD }}'
|
||||||
|
certificatesha1: '${{ secrets.WINDOWS_PFX_SHA1_THUMBPRINT }}'
|
||||||
|
folder: 'target\x86_64-pc-windows-msvc\release'
|
||||||
|
recursive: false
|
||||||
|
|
||||||
|
- name: Build UI browser file
|
||||||
|
run: |
|
||||||
|
npm i
|
||||||
|
npm run build
|
||||||
|
working-directory: ./ui/html
|
||||||
|
|
||||||
|
- name: Build UI setup file
|
||||||
|
run: |
|
||||||
|
rustup default nightly
|
||||||
|
cargo build --release
|
||||||
|
xcopy /y ..\target\x86_64-pc-windows-msvc\release\*.exe setup\bin\
|
||||||
|
xcopy /y target\release\*.exe setup\
|
||||||
|
mkdir setup\logs
|
||||||
|
makensis /V1 setup.nsi
|
||||||
|
mkdir SignOutput
|
||||||
|
mv RustDeskServer.Setup.exe SignOutput\
|
||||||
|
mv ..\target\x86_64-pc-windows-msvc\release\*.exe SignOutput\
|
||||||
|
working-directory: ./ui
|
||||||
|
|
||||||
|
- name: Sign UI setup file
|
||||||
|
uses: GermanBluefox/code-sign-action@v7
|
||||||
|
if: false
|
||||||
|
with:
|
||||||
|
certificate: '${{ secrets.WINDOWS_PFX_BASE64 }}'
|
||||||
|
password: '${{ secrets.WINDOWS_PFX_PASSWORD }}'
|
||||||
|
certificatesha1: '${{ secrets.WINDOWS_PFX_SHA1_THUMBPRINT }}'
|
||||||
|
folder: './ui/SignOutput'
|
||||||
|
recursive: false
|
||||||
|
|
||||||
- name: Publish Artifacts
|
- name: Publish Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binaries-windows-x86_64
|
name: binaries-windows-x86_64
|
||||||
path: |
|
path: |
|
||||||
target\x86_64-pc-windows-msvc\release\hbbr.exe
|
ui\SignOutput\hbbr.exe
|
||||||
target\x86_64-pc-windows-msvc\release\hbbs.exe
|
ui\SignOutput\hbbs.exe
|
||||||
target\x86_64-pc-windows-msvc\release\rustdesk-utils.exe
|
ui\SignOutput\rustdesk-utils.exe
|
||||||
|
ui\SignOutput\RustDeskServer.Setup.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
# github (draft) release with all binaries
|
# github (draft) release with all binaries
|
||||||
@@ -119,16 +183,17 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { os: "linux", name: "amd64" }
|
- { os: "linux", name: "amd64", suffix: "" }
|
||||||
- { os: "linux", name: "arm64v8" }
|
- { os: "linux", name: "arm64v8", suffix: "" }
|
||||||
- { os: "linux", name: "armv7" }
|
- { os: "linux", name: "armv7", suffix: "" }
|
||||||
- { os: "linux", name: "i386" }
|
- { os: "linux", name: "i386", suffix: "" }
|
||||||
- { os: "windows", name: "x86_64" }
|
#- { os: "linux", name: "amd64fb", suffix: "" }
|
||||||
|
- { os: "windows", name: "x86_64", suffix: "-unsigned" }
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Download binaries (${{ matrix.job.os }} - ${{ matrix.job.name }})
|
- name: Download binaries (${{ matrix.job.os }} - ${{ matrix.job.name }})
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binaries-${{ matrix.job.os }}-${{ matrix.job.name }}
|
name: binaries-${{ matrix.job.os }}-${{ matrix.job.name }}
|
||||||
path: ${{ matrix.job.name }}
|
path: ${{ matrix.job.name }}
|
||||||
@@ -140,13 +205,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y zip
|
DEBIAN_FRONTEND=noninteractive sudo apt install -y zip
|
||||||
zip ${{ matrix.job.name }}/rustdesk-server-${{ matrix.job.os }}-${{ matrix.job.name }}.zip ${{ matrix.job.name }}/*
|
zip ${{ matrix.job.name }}/rustdesk-server-${{ matrix.job.os }}-${{ matrix.job.name }}${{ matrix.job.suffix }}.zip ${{ matrix.job.name }}/*
|
||||||
|
|
||||||
- name: Create Release (${{ matrix.job.os }} - (${{ matrix.job.name }})
|
- name: Create Release (${{ matrix.job.os }} - (${{ matrix.job.name }})
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: ${{ matrix.job.name }}/rustdesk-server-${{ matrix.job.os }}-${{ matrix.job.name }}.zip
|
files: ${{ matrix.job.name }}/rustdesk-server-${{ matrix.job.os }}-${{ matrix.job.name }}${{ matrix.job.suffix }}.zip
|
||||||
|
|
||||||
# docker build and push of single-arch images
|
# docker build and push of single-arch images
|
||||||
docker:
|
docker:
|
||||||
@@ -167,9 +232,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Download binaries
|
- name: Download binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binaries-linux-${{ matrix.job.name }}
|
name: binaries-linux-${{ matrix.job.name }}
|
||||||
path: docker/rootfs/usr/bin
|
path: docker/rootfs/usr/bin
|
||||||
@@ -187,8 +254,16 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: rustdesk
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
@@ -205,17 +280,21 @@ jobs:
|
|||||||
echo "MAJOR_TAG=$M" >> $GITHUB_ENV
|
echo "MAJOR_TAG=$M" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: "./docker"
|
context: "./docker"
|
||||||
platforms: ${{ matrix.job.docker_platform }}
|
platforms: ${{ matrix.job.docker_platform }}
|
||||||
push: true
|
push: true
|
||||||
|
provenance: false
|
||||||
build-args: |
|
build-args: |
|
||||||
S6_ARCH=${{ matrix.job.s6_platform }}
|
S6_ARCH=${{ matrix.job.s6_platform }}
|
||||||
tags: |
|
tags: |
|
||||||
${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }}
|
${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }}
|
||||||
${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-${{ matrix.job.name }}
|
${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-${{ matrix.job.name }}
|
||||||
${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }}
|
${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ env.GHCR_IMAGE }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ env.GHCR_IMAGE }}:${{ env.GIT_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ env.GHCR_IMAGE }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
# docker build and push of multiarch images
|
# docker build and push of multiarch images
|
||||||
@@ -231,8 +310,8 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||||
|
|
||||||
- name: Get git tag
|
- name: Get git tag
|
||||||
id: vars
|
id: vars
|
||||||
@@ -242,10 +321,18 @@ jobs:
|
|||||||
echo "GIT_TAG=$T" >> $GITHUB_ENV
|
echo "GIT_TAG=$T" >> $GITHUB_ENV
|
||||||
echo "MAJOR_TAG=$M" >> $GITHUB_ENV
|
echo "MAJOR_TAG=$M" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: rustdesk
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# manifest for :1.2.3 tag
|
# manifest for :1.2.3 tag
|
||||||
# this has to run only if invoked by a new tag
|
# this has to run only if invoked by a new tag
|
||||||
- name: Create and push manifest (:ve.rs.ion)
|
- name: Create and push manifest (:ve.rs.ion)
|
||||||
uses: Noelware/docker-manifest-action@master
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
with:
|
with:
|
||||||
base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}
|
base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}
|
||||||
@@ -254,7 +341,7 @@ jobs:
|
|||||||
|
|
||||||
# manifest for :1 tag (major release)
|
# manifest for :1 tag (major release)
|
||||||
- name: Create and push manifest (:major)
|
- name: Create and push manifest (:major)
|
||||||
uses: Noelware/docker-manifest-action@master
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
with:
|
with:
|
||||||
base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}
|
base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}
|
||||||
extra-images: ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-amd64,${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-armv7,${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-i386
|
extra-images: ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-amd64,${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-armv7,${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-i386
|
||||||
@@ -262,40 +349,68 @@ jobs:
|
|||||||
|
|
||||||
# manifest for :latest tag
|
# manifest for :latest tag
|
||||||
- name: Create and push manifest (:latest)
|
- name: Create and push manifest (:latest)
|
||||||
uses: Noelware/docker-manifest-action@master
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
with:
|
with:
|
||||||
base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}
|
base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}
|
||||||
extra-images: ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-amd64,${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-armv7,${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-i386
|
extra-images: ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-amd64,${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-armv7,${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-i386
|
||||||
push: true
|
push: true
|
||||||
|
|
||||||
|
# GHCR manifests
|
||||||
|
# manifest for :1.2.3 tag
|
||||||
|
- name: Create and push GHCR manifest (:ve.rs.ion)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
with:
|
||||||
|
base-image: ${{ env.GHCR_IMAGE }}:${{ env.GIT_TAG }}
|
||||||
|
extra-images: ${{ env.GHCR_IMAGE }}:${{ env.GIT_TAG }}-amd64,${{ env.GHCR_IMAGE }}:${{ env.GIT_TAG }}-arm64v8,${{ env.GHCR_IMAGE }}:${{ env.GIT_TAG }}-armv7,${{ env.GHCR_IMAGE }}:${{ env.GIT_TAG }}-i386
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# manifest for :1 tag (major release)
|
||||||
|
- name: Create and push GHCR manifest (:major)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
with:
|
||||||
|
base-image: ${{ env.GHCR_IMAGE }}:${{ env.MAJOR_TAG }}
|
||||||
|
extra-images: ${{ env.GHCR_IMAGE }}:${{ env.MAJOR_TAG }}-amd64,${{ env.GHCR_IMAGE }}:${{ env.MAJOR_TAG }}-arm64v8,${{ env.GHCR_IMAGE }}:${{ env.MAJOR_TAG }}-armv7,${{ env.GHCR_IMAGE }}:${{ env.MAJOR_TAG }}-i386
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# manifest for :latest tag
|
||||||
|
- name: Create and push GHCR manifest (:latest)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
with:
|
||||||
|
base-image: ${{ env.GHCR_IMAGE }}:${{ env.LATEST_TAG }}
|
||||||
|
extra-images: ${{ env.GHCR_IMAGE }}:${{ env.LATEST_TAG }}-amd64,${{ env.GHCR_IMAGE }}:${{ env.LATEST_TAG }}-arm64v8,${{ env.GHCR_IMAGE }}:${{ env.LATEST_TAG }}-armv7,${{ env.GHCR_IMAGE }}:${{ env.LATEST_TAG }}-i386
|
||||||
|
push: true
|
||||||
|
|
||||||
|
|
||||||
# docker build and push of classic images
|
# docker build and push of single-arch images
|
||||||
docker-classic:
|
docker-classic:
|
||||||
|
|
||||||
name: Docker push classic - ${{ matrix.job.name }}
|
name: Docker push - ${{ matrix.job.name }}
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { name: "amd64", docker_platform: "linux/amd64", tag: "latest" }
|
- { name: "amd64", docker_platform: "linux/amd64" }
|
||||||
- { name: "arm64v8", docker_platform: "linux/arm64", tag: "latest-arm64v8" }
|
- { name: "arm64v8", docker_platform: "linux/arm64" }
|
||||||
|
- { name: "armv7", docker_platform: "linux/arm/v7" }
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Download binaries
|
- name: Download binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binaries-linux-${{ matrix.job.name }}
|
name: binaries-linux-${{ matrix.job.name }}
|
||||||
path: docker-classic/
|
path: docker-classic/
|
||||||
|
|
||||||
- name: Make binaries executable
|
- name: Make binaries executable
|
||||||
run: chmod -v a+x docker-classic/hbb*
|
run: chmod -v a+x docker-classic/*
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
@@ -307,8 +422,16 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: rustdesk
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
@@ -316,16 +439,114 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: registry.hub.docker.com/${{ secrets.DOCKER_IMAGE_CLASSIC }}
|
images: registry.hub.docker.com/${{ secrets.DOCKER_IMAGE_CLASSIC }}
|
||||||
|
|
||||||
|
- name: Get git tag
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
T=${GITHUB_REF#refs/*/}
|
||||||
|
M=${T%%.*}
|
||||||
|
echo "GIT_TAG=$T" >> $GITHUB_ENV
|
||||||
|
echo "MAJOR_TAG=$M" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: "./docker-classic"
|
context: "./docker-classic"
|
||||||
platforms: ${{ matrix.job.docker_platform }}
|
platforms: ${{ matrix.job.docker_platform }}
|
||||||
push: true
|
push: true
|
||||||
|
provenance: false
|
||||||
tags: |
|
tags: |
|
||||||
${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ matrix.job.tag }}
|
${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-${{ matrix.job.name }}
|
||||||
|
${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
||||||
|
# docker build and push of multiarch images
|
||||||
|
docker-manifest-classic:
|
||||||
|
|
||||||
|
name: Docker manifest
|
||||||
|
needs: docker
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Get git tag
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
T=${GITHUB_REF#refs/*/}
|
||||||
|
M=${T%%.*}
|
||||||
|
echo "GIT_TAG=$T" >> $GITHUB_ENV
|
||||||
|
echo "MAJOR_TAG=$M" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: rustdesk
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# manifest for :1.2.3 tag
|
||||||
|
# this has to run only if invoked by a new tag
|
||||||
|
- name: Create and push manifest (:ve.rs.ion)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
with:
|
||||||
|
base-image: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}
|
||||||
|
extra-images: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-amd64,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-armv7
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# manifest for :1 tag (major release)
|
||||||
|
- name: Create and push manifest (:major)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
with:
|
||||||
|
base-image: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}
|
||||||
|
extra-images: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-amd64,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-armv7
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# manifest for :latest tag
|
||||||
|
- name: Create and push manifest (:latest)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
with:
|
||||||
|
base-image: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}
|
||||||
|
extra-images: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-amd64,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-armv7
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# GHCR manifests
|
||||||
|
# manifest for :1.2.3 tag
|
||||||
|
- name: Create and push GHCR manifest (:ve.rs.ion)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
if: github.event_name != 'workflow_dispatch'
|
||||||
|
with:
|
||||||
|
base-image: ${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}
|
||||||
|
extra-images: ${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-amd64,${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-arm64v8,${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-armv7
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# manifest for :1 tag (major release)
|
||||||
|
- name: Create and push GHCR manifest (:major)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
with:
|
||||||
|
base-image: ${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}
|
||||||
|
extra-images: ${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-amd64,${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-arm64v8,${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-armv7
|
||||||
|
push: true
|
||||||
|
|
||||||
|
# manifest for :latest tag
|
||||||
|
- name: Create and push GHCR manifest (:latest)
|
||||||
|
uses: Noelware/docker-manifest-action@0.4.3
|
||||||
|
with:
|
||||||
|
base-image: ${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}
|
||||||
|
extra-images: ${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-amd64,${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-arm64v8,${{ env.GHCR_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-armv7
|
||||||
|
push: true
|
||||||
|
|
||||||
|
|
||||||
deb-package:
|
deb-package:
|
||||||
|
|
||||||
@@ -345,6 +566,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
@@ -356,7 +579,7 @@ jobs:
|
|||||||
mkdir -p debian-build/${{ matrix.job.name }}/bin
|
mkdir -p debian-build/${{ matrix.job.name }}/bin
|
||||||
|
|
||||||
- name: Download binaries
|
- name: Download binaries
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binaries-linux-${{ matrix.job.name }}
|
name: binaries-linux-${{ matrix.job.name }}
|
||||||
path: debian-build/${{ matrix.job.name }}/bin
|
path: debian-build/${{ matrix.job.name }}/bin
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -6,3 +6,7 @@ debian/.debhelper
|
|||||||
debian/debhelper-build-stamp
|
debian/debhelper-build-stamp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode
|
.vscode
|
||||||
|
src/version.rs
|
||||||
|
db_v2.sqlite3
|
||||||
|
test.*
|
||||||
|
.idea
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libs/hbb_common"]
|
||||||
|
path = libs/hbb_common
|
||||||
|
url = https://github.com/rustdesk/hbb_common
|
||||||
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"rust.checkWith": "clippy",
|
||||||
|
"rust.formatOnSave": true,
|
||||||
|
"rust.checkOnSave": true,
|
||||||
|
"rust.useNewErrorFormat": true
|
||||||
|
}
|
||||||
1790
Cargo.lock
generated
29
Cargo.toml
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hbbs"
|
name = "hbbs"
|
||||||
version = "1.1.6"
|
version = "1.1.15"
|
||||||
authors = ["open-trade <info@rustdesk.com>"]
|
authors = ["rustdesk <info@rustdesk.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
default-run = "hbbs"
|
default-run = "hbbs"
|
||||||
@@ -26,7 +26,7 @@ clap = "2"
|
|||||||
rust-ini = "0.18"
|
rust-ini = "0.18"
|
||||||
minreq = { version = "2.4", features = ["punycode"] }
|
minreq = { version = "2.4", features = ["punycode"] }
|
||||||
machine-uid = "0.2"
|
machine-uid = "0.2"
|
||||||
mac_address = "1.1"
|
mac_address = "1.1.5"
|
||||||
whoami = "1.2"
|
whoami = "1.2"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
axum = { version = "0.5", features = ["headers"] }
|
axum = { version = "0.5", features = ["headers"] }
|
||||||
@@ -46,14 +46,33 @@ tungstenite = "0.17"
|
|||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
tower-http = { version = "0.3", features = ["fs", "trace", "cors"] }
|
tower-http = { version = "0.3", features = ["fs", "trace", "cors"] }
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
flexi_logger = { version = "0.22", features = ["async", "use_chrono_for_offset"] }
|
flexi_logger = { version = "0.22", features = ["async", "use_chrono_for_offset", "dont_minimize_extra_stacks"] }
|
||||||
ipnetwork = "0.20"
|
ipnetwork = "0.20"
|
||||||
local-ip-address = "0.4"
|
local-ip-address = "0.5.1"
|
||||||
dns-lookup = "1.0.8"
|
dns-lookup = "1.0.8"
|
||||||
ping = "0.4.0"
|
ping = "0.4.0"
|
||||||
|
flate2 = "1.0"
|
||||||
|
|
||||||
|
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||||
|
# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support
|
||||||
|
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "native-tls", "gzip"], default-features=false }
|
||||||
|
|
||||||
|
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
|
||||||
|
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
hbb_common = { path = "libs/hbb_common" }
|
hbb_common = { path = "libs/hbb_common" }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["libs/hbb_common"]
|
members = ["libs/hbb_common"]
|
||||||
|
exclude = ["ui"]
|
||||||
|
|
||||||
|
#https://github.com/johnthagen/min-sized-rust
|
||||||
|
#https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = 'abort'
|
||||||
|
strip = true
|
||||||
|
#opt-level = 'z' # only have smaller size after strip # Default is 3, better performance
|
||||||
|
#rpath = true # Not needed
|
||||||
|
|||||||
297
README.md
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||||
|
|
||||||
|
[**How to migrate OSS to Pro**](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/installscript/#convert-from-open-source)
|
||||||
|
|
||||||
Self-host your own RustDesk server, it is free and open source.
|
Self-host your own RustDesk server, it is free and open source.
|
||||||
|
|
||||||
## How to build manually
|
## How to build manually
|
||||||
@@ -22,297 +24,12 @@ Three executables will be generated in target/release.
|
|||||||
- hbbr - RustDesk relay server
|
- hbbr - RustDesk relay server
|
||||||
- rustdesk-utils - RustDesk CLI utilities
|
- rustdesk-utils - RustDesk CLI utilities
|
||||||
|
|
||||||
You can find updated binaries on the [releases](https://github.com/rustdesk/rustdesk-server/releases) page.
|
You can find updated binaries on the [Releases](https://github.com/rustdesk/rustdesk-server/releases) page.
|
||||||
|
|
||||||
If you wanna develop your own server, [rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) might be a better and simpler start for you than this repo.
|
If you want extra features, [RustDesk Server Pro](https://rustdesk.com/pricing.html) might suit you better.
|
||||||
|
|
||||||
## Docker images
|
If you want to develop your own server, [rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) might be a better and simpler start for you than this repo.
|
||||||
|
|
||||||
Docker images are automatically generated and published on every github release. We have 2 kind of images.
|
## Installation
|
||||||
|
|
||||||
### Classic image
|
Please follow this [doc](https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/)
|
||||||
|
|
||||||
These images are build against `ubuntu-20.04` with the only addition of the main binaries (`hbbr` and `hbbs`). They're available on [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server/) with these tags:
|
|
||||||
|
|
||||||
| architecture | image:tag |
|
|
||||||
| --- | --- |
|
|
||||||
| amd64 | `rustdesk/rustdesk-server:latest` |
|
|
||||||
| arm64v8 | `rustdesk/rustdesk-server:latest-arm64v8` |
|
|
||||||
|
|
||||||
You can start these images directly with `docker run` with these commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name hbbs --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r <relay-server-ip[:port]>
|
|
||||||
docker run --name hbbr --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr
|
|
||||||
```
|
|
||||||
|
|
||||||
or without --net=host, but P2P direct connection can not work.
|
|
||||||
|
|
||||||
For systems using SELinux, replacing `/root` by `/root:z` is required for the containers to run correctly. Alternatively, SELinux container separation can be disabled completely adding the option `--security-opt label=disable`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r <relay-server-ip[:port]>
|
|
||||||
docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr
|
|
||||||
```
|
|
||||||
|
|
||||||
The `relay-server-ip` parameter is the IP address (or dns name) of the server running these containers. The **optional** `port` parameter has to be used if you use a port different than **21117** for `hbbr`.
|
|
||||||
|
|
||||||
You can also use docker-compose, using this configuration as a template:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
networks:
|
|
||||||
rustdesk-net:
|
|
||||||
external: false
|
|
||||||
|
|
||||||
services:
|
|
||||||
hbbs:
|
|
||||||
container_name: hbbs
|
|
||||||
ports:
|
|
||||||
- 21115:21115
|
|
||||||
- 21116:21116
|
|
||||||
- 21116:21116/udp
|
|
||||||
- 21118:21118
|
|
||||||
image: rustdesk/rustdesk-server:latest
|
|
||||||
command: hbbs -r rustdesk.example.com:21117
|
|
||||||
volumes:
|
|
||||||
- ./data:/root
|
|
||||||
networks:
|
|
||||||
- rustdesk-net
|
|
||||||
depends_on:
|
|
||||||
- hbbr
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
hbbr:
|
|
||||||
container_name: hbbr
|
|
||||||
ports:
|
|
||||||
- 21117:21117
|
|
||||||
- 21119:21119
|
|
||||||
image: rustdesk/rustdesk-server:latest
|
|
||||||
command: hbbr
|
|
||||||
volumes:
|
|
||||||
- ./data:/root
|
|
||||||
networks:
|
|
||||||
- rustdesk-net
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
Edit line 16 to point to your relay server (the one listening on port 21117). You can also edit the volume lines (L18 and L33) if you need.
|
|
||||||
|
|
||||||
(docker-compose credit goes to @lukebarone and @QuiGonLeong)
|
|
||||||
|
|
||||||
## S6-overlay based images
|
|
||||||
|
|
||||||
These images are build against `busybox:stable` with the addition of the binaries (both hbbr and hbbs) and [S6-overlay](https://github.com/just-containers/s6-overlay). They're available on [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server-s6/) with these tags:
|
|
||||||
|
|
||||||
| architecture | version | image:tag |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| multiarch | latest | `rustdesk/rustdesk-server-s6:latest` |
|
|
||||||
| amd64 | latest | `rustdesk/rustdesk-server-s6:latest-amd64` |
|
|
||||||
| i386 | latest | `rustdesk/rustdesk-server-s6:latest-i386` |
|
|
||||||
| arm64v8 | latest | `rustdesk/rustdesk-server-s6:latest-arm64v8` |
|
|
||||||
| armv7 | latest | `rustdesk/rustdesk-server-s6:latest-armv7` |
|
|
||||||
| multiarch | 2 | `rustdesk/rustdesk-server-s6:2` |
|
|
||||||
| amd64 | 2 | `rustdesk/rustdesk-server-s6:2-amd64` |
|
|
||||||
| i386 | 2 | `rustdesk/rustdesk-server-s6:2-i386` |
|
|
||||||
| arm64v8 | 2 | `rustdesk/rustdesk-server-s6:2-arm64v8` |
|
|
||||||
| armv7 | 2 | `rustdesk/rustdesk-server-s6:2-armv7` |
|
|
||||||
| multiarch | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0` |
|
|
||||||
| amd64 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-amd64` |
|
|
||||||
| i386 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-i386` |
|
|
||||||
| arm64v8 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-arm64v8` |
|
|
||||||
| armv7 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-armv7` |
|
|
||||||
|
|
||||||
You're strongly encuraged to use the `multiarch` image either with the `major version` or `latest` tag.
|
|
||||||
|
|
||||||
The S6-overlay acts as a supervisor and keeps both process running, so with this image there's no need to have two separate running containers.
|
|
||||||
|
|
||||||
You can start these images directly with `docker run` with this command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name rustdesk-server \
|
|
||||||
--net=host \
|
|
||||||
-e "RELAY=rustdeskrelay.example.com" \
|
|
||||||
-e "ENCRYPTED_ONLY=1" \
|
|
||||||
-v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
or without --net=host, but P2P direct connection can not work.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name rustdesk-server \
|
|
||||||
-p 21115:21115 -p 21116:21116 -p 21116:21116/udp \
|
|
||||||
-p 21117:21117 -p 21118:21118 -p 21119:21119 \
|
|
||||||
-e "RELAY=rustdeskrelay.example.com" \
|
|
||||||
-e "ENCRYPTED_ONLY=1" \
|
|
||||||
-v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can use a docker-compose file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
rustdesk-server:
|
|
||||||
container_name: rustdesk-server
|
|
||||||
ports:
|
|
||||||
- 21115:21115
|
|
||||||
- 21116:21116
|
|
||||||
- 21116:21116/udp
|
|
||||||
- 21117:21117
|
|
||||||
- 21118:21118
|
|
||||||
- 21119:21119
|
|
||||||
image: rustdesk/rustdesk-server-s6:latest
|
|
||||||
environment:
|
|
||||||
- "RELAY=rustdesk.example.com:21117"
|
|
||||||
- "ENCRYPTED_ONLY=1"
|
|
||||||
volumes:
|
|
||||||
- ./data:/data
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
We use these environment variables:
|
|
||||||
|
|
||||||
| variable | optional | description |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| RELAY | no | the IP address/DNS name of the machine running this container |
|
|
||||||
| ENCRYPTED_ONLY | yes | if set to **"1"** unencrypted connection will not be accepted |
|
|
||||||
| DB_URL | yes | path for database file |
|
|
||||||
| KEY_PUB | yes | public part of the key pair |
|
|
||||||
| KEY_PRIV | yes | private part of the key pair |
|
|
||||||
| RUST_LOG | yes | set debug level (error|warn|info|debug|trace) |
|
|
||||||
|
|
||||||
### Secret management in S6-overlay based images
|
|
||||||
|
|
||||||
You can obviously keep the key pair in a docker volume, but the best practices tells you to not write the keys on the filesystem; so we provide a couple of options.
|
|
||||||
|
|
||||||
On container startup, the presence of the keypair is checked (`/data/id_ed25519.pub` and `/data/id_ed25519`) and if one of these keys doesn't exist, it's recreated from ENV variables or docker secrets.
|
|
||||||
Then the validity of the keypair is checked: if public and private keys doesn't match, the container will stop.
|
|
||||||
If you provide no keys, `hbbs` will generate one for you, and it'll place it in the default location.
|
|
||||||
|
|
||||||
#### Use ENV to store the key pair
|
|
||||||
|
|
||||||
You can use docker environment variables to store the keys. Just follow this examples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --name rustdesk-server \
|
|
||||||
--net=host \
|
|
||||||
-e "RELAY=rustdeskrelay.example.com" \
|
|
||||||
-e "ENCRYPTED_ONLY=1" \
|
|
||||||
-e "DB_URL=/db/db_v2.sqlite3" \
|
|
||||||
-e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \
|
|
||||||
-e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \
|
|
||||||
-v "$PWD/db:/db" -d rustdesk/rustdesk-server-s6:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
rustdesk-server:
|
|
||||||
container_name: rustdesk-server
|
|
||||||
ports:
|
|
||||||
- 21115:21115
|
|
||||||
- 21116:21116
|
|
||||||
- 21116:21116/udp
|
|
||||||
- 21117:21117
|
|
||||||
- 21118:21118
|
|
||||||
- 21119:21119
|
|
||||||
image: rustdesk/rustdesk-server-s6:latest
|
|
||||||
environment:
|
|
||||||
- "RELAY=rustdesk.example.com:21117"
|
|
||||||
- "ENCRYPTED_ONLY=1"
|
|
||||||
- "DB_URL=/db/db_v2.sqlite3"
|
|
||||||
- "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ=="
|
|
||||||
- "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE="
|
|
||||||
volumes:
|
|
||||||
- ./db:/db
|
|
||||||
restart: unless-stopped
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Use Docker secrets to store the key pair
|
|
||||||
|
|
||||||
You can alternatively use docker secrets to store the keys.
|
|
||||||
This is useful if you're using **docker-compose** or **docker swarm**.
|
|
||||||
Just follow this examples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat secrets/id_ed25519.pub | docker secret create key_pub -
|
|
||||||
cat secrets/id_ed25519 | docker secret create key_priv -
|
|
||||||
docker service create --name rustdesk-server \
|
|
||||||
--secret key_priv --secret key_pub \
|
|
||||||
--net=host \
|
|
||||||
-e "RELAY=rustdeskrelay.example.com" \
|
|
||||||
-e "ENCRYPTED_ONLY=1" \
|
|
||||||
-e "DB_URL=/db/db_v2.sqlite3" \
|
|
||||||
--mount "type=bind,source=$PWD/db,destination=/db" \
|
|
||||||
rustdesk/rustdesk-server-s6:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
rustdesk-server:
|
|
||||||
container_name: rustdesk-server
|
|
||||||
ports:
|
|
||||||
- 21115:21115
|
|
||||||
- 21116:21116
|
|
||||||
- 21116:21116/udp
|
|
||||||
- 21117:21117
|
|
||||||
- 21118:21118
|
|
||||||
- 21119:21119
|
|
||||||
image: rustdesk/rustdesk-server-s6:latest
|
|
||||||
environment:
|
|
||||||
- "RELAY=rustdesk.example.com:21117"
|
|
||||||
- "ENCRYPTED_ONLY=1"
|
|
||||||
- "DB_URL=/db/db_v2.sqlite3"
|
|
||||||
volumes:
|
|
||||||
- ./db:/db
|
|
||||||
restart: unless-stopped
|
|
||||||
secrets:
|
|
||||||
- key_pub
|
|
||||||
- key_priv
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
key_pub:
|
|
||||||
file: secrets/id_ed25519.pub
|
|
||||||
key_priv:
|
|
||||||
file: secrets/id_ed25519
|
|
||||||
```
|
|
||||||
|
|
||||||
## How to create a keypair
|
|
||||||
|
|
||||||
A keypair is needed for encryption; you can provide it, as explained before, but you need a way to create one.
|
|
||||||
|
|
||||||
You can use this command to generate a keypair:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/usr/bin/rustdesk-utils genkeypair
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't have (or don't want) the `rustdesk-utils` package installed on your system, you can invoke the same command with docker:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --rm --entrypoint /usr/bin/rustdesk-utils rustdesk/rustdesk-server-s6:latest genkeypair
|
|
||||||
```
|
|
||||||
|
|
||||||
The output will be something like this:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA=
|
|
||||||
Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA==
|
|
||||||
```
|
|
||||||
|
|
||||||
## .deb packages
|
|
||||||
|
|
||||||
Separate .deb packages are available for each binary, you can find them in the [releases](https://github.com/rustdesk/rustdesk-server/releases).
|
|
||||||
These packages are meant for the following distributions:
|
|
||||||
|
|
||||||
- Ubuntu 22.04 LTS
|
|
||||||
- Ubuntu 20.04 LTS
|
|
||||||
- Ubuntu 18.04 LTS
|
|
||||||
- Debian 11 bullseye
|
|
||||||
- Debian 10 buster
|
|
||||||
|
|||||||
BIN
db_v2.sqlite3
62
debian/README.source
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#
|
||||||
|
# Juli Augustus 2025 was building the downloadable Debian packages done
|
||||||
|
# by github.com CI, meaning you being depended on github.com
|
||||||
|
# for getting a .deb of the libre source code (four GNU freedoms) you have.
|
||||||
|
#
|
||||||
|
# And in those days there was no official Debian package.
|
||||||
|
#
|
||||||
|
# What follows are instructions that make it possible to do
|
||||||
|
# a succesfull
|
||||||
|
#
|
||||||
|
# debuild -uc -us
|
||||||
|
#
|
||||||
|
# in the directory target/rustdesk-server
|
||||||
|
#
|
||||||
|
# The instructions are written in bash syntax,
|
||||||
|
#
|
||||||
|
# bash debian/README.source
|
||||||
|
#
|
||||||
|
# should help you immens
|
||||||
|
#
|
||||||
|
|
||||||
|
# Make sure the submodules are present
|
||||||
|
git submodule update --init --recursive
|
||||||
|
|
||||||
|
rm -rf target/rustdesk-server # avoid clutter from previous iteration
|
||||||
|
mkdir -p target/rustdesk-server # FYI: target/ is ignored by git
|
||||||
|
|
||||||
|
tar cf - \
|
||||||
|
--exclude .git \
|
||||||
|
Cargo.toml Cargo.lock \
|
||||||
|
LICENSE README.md \
|
||||||
|
db_v2.sqlite3 \
|
||||||
|
build.rs \
|
||||||
|
debian \
|
||||||
|
libs rcd src \
|
||||||
|
systemd ui \
|
||||||
|
| ( cd target/rustdesk-server && tar x )
|
||||||
|
|
||||||
|
mv target/rustdesk-server/debian/Makefile target/rustdesk-server/
|
||||||
|
|
||||||
|
tar cjf target/rustdesk-server-orig.tar.xz \
|
||||||
|
--strip-components=1 \
|
||||||
|
target/rustdesk-server
|
||||||
|
|
||||||
|
# an .orig.tar.xz tarball exists,
|
||||||
|
# work on the debian directory
|
||||||
|
|
||||||
|
eval $( dpkg-architecture )
|
||||||
|
sed -e "s/{{ ARCH }}/${DEB_TARGET_ARCH}/" \
|
||||||
|
debian/control.tpl > target/rustdesk-server/debian/control
|
||||||
|
|
||||||
|
|
||||||
|
cd target/rustdesk-server
|
||||||
|
dpkg-checkbuilddeps || echo sudo apt install dpkg-dev
|
||||||
|
debuild -uc -us
|
||||||
|
|
||||||
|
#
|
||||||
|
# For what it is worth:
|
||||||
|
# Early September 2025 there were
|
||||||
|
# several WARNINGS and ERRORS from Lintian
|
||||||
|
#
|
||||||
|
# l l
|
||||||
73
debian/changelog
vendored
@@ -1,3 +1,76 @@
|
|||||||
|
rustdesk-server (1.1.15) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Fix: 127.0.0.1 is not loopback (#515)
|
||||||
|
* Higher default bandwidth
|
||||||
|
* Define the HOME env to allow running rootless (#612)
|
||||||
|
* Add option to log (failed) authentication attempts to enable the usage of tools like fail2ban and crowdsec (#435)
|
||||||
|
* Connection log query
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Sat, 10 Jan 2026 16:04:19 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.14) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Fix windows crash
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Sat, 25 Jan 2025 20:47:19 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.13) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Version check and refactor hbb_common to share with rustdesk client
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Tue, 21 Jan 2025 01:33:42 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.12) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* WS real ip
|
||||||
|
* Bump s6-overlay to v3.2.0.0 and fix env warnings
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Mon, 7 Oct 2024 16:21:36 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.11-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* set reuse port to make restart friendly
|
||||||
|
* revert hbbr `-k` to not ruin back-compatibility
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Fri, 24 May 2024 18:37:11 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.11) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* change -k to default '-', so you need not to set -k any more
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Fri, 24 May 2024 18:09:12 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.10-3) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* fix on -2
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Wed, 31 Jan 2024 11:30:42 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.10-2) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* fix hangup signal exit when run with nohup
|
||||||
|
* some minors
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Tue, 30 Jan 2024 19:23:36 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.9) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* remove unsafe
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Tue, 5 Dec 2023 17:22:40 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.8) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* fix test_hbbs and mask in lan
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Thu, 6 Jul 2023 00:50:11 +0800
|
||||||
|
|
||||||
|
rustdesk-server (1.1.7) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* ipv6 support
|
||||||
|
|
||||||
|
-- rustdesk <info@rustdesk.com> Wed, 11 Jan 2023 11:27:00 +0800
|
||||||
|
|
||||||
rustdesk-server (1.1.6) UNRELEASED; urgency=medium
|
rustdesk-server (1.1.6) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
* Initial release
|
* Initial release
|
||||||
|
|||||||
4
debian/rustdesk-server-hbbr.postinst
vendored
@@ -3,6 +3,10 @@ set -e
|
|||||||
|
|
||||||
SERVICE=rustdesk-hbbr.service
|
SERVICE=rustdesk-hbbr.service
|
||||||
|
|
||||||
|
if [ "$1" = "configure" ]; then
|
||||||
|
mkdir -p /var/log/rustdesk-server
|
||||||
|
fi
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
configure|abort-upgrade|abort-deconfigure|abort-remove)
|
configure|abort-upgrade|abort-deconfigure|abort-remove)
|
||||||
mkdir -p /var/lib/rustdesk-server/
|
mkdir -p /var/lib/rustdesk-server/
|
||||||
|
|||||||
2
debian/rustdesk-server-hbbr.postrm
vendored
@@ -6,7 +6,7 @@ SERVICE=rustdesk-hbbr.service
|
|||||||
systemctl --system daemon-reload >/dev/null || true
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
|
||||||
if [ "$1" = "purge" ]; then
|
if [ "$1" = "purge" ]; then
|
||||||
rm -rf /var/lib/rustdesk-server/
|
rm -rf /var/log/rustdesk-server/rustdesk-hbbr.*
|
||||||
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
|
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
|
||||||
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
|
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|||||||
4
debian/rustdesk-server-hbbs.postinst
vendored
@@ -3,6 +3,10 @@ set -e
|
|||||||
|
|
||||||
SERVICE=rustdesk-hbbs.service
|
SERVICE=rustdesk-hbbs.service
|
||||||
|
|
||||||
|
if [ "$1" = "configure" ]; then
|
||||||
|
mkdir -p /var/log/rustdesk-server
|
||||||
|
fi
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
configure|abort-upgrade|abort-deconfigure|abort-remove)
|
configure|abort-upgrade|abort-deconfigure|abort-remove)
|
||||||
mkdir -p /var/lib/rustdesk-server/
|
mkdir -p /var/lib/rustdesk-server/
|
||||||
|
|||||||
2
debian/rustdesk-server-hbbs.postrm
vendored
@@ -6,7 +6,7 @@ SERVICE=rustdesk-hbbs.service
|
|||||||
systemctl --system daemon-reload >/dev/null || true
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
|
||||||
if [ "$1" = "purge" ]; then
|
if [ "$1" = "purge" ]; then
|
||||||
rm -rf /var/lib/rustdesk-server/
|
rm -rf /var/lib/rustdesk-server/ /var/log/rustdesk-server/rustdesk-hbbs.*
|
||||||
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
|
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
|
||||||
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
|
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
FROM ubuntu:20.04
|
FROM scratch
|
||||||
COPY hbbs /usr/bin/hbbs
|
COPY hbbs /usr/bin/hbbs
|
||||||
COPY hbbr /usr/bin/hbbr
|
COPY hbbr /usr/bin/hbbr
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
ENV HOME=/root
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM busybox:stable
|
FROM busybox:stable
|
||||||
|
|
||||||
ARG S6_OVERLAY_VERSION=3.1.1.2
|
ARG S6_OVERLAY_VERSION=3.2.0.0
|
||||||
ARG S6_ARCH=x86_64
|
ARG S6_ARCH=x86_64
|
||||||
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
|
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
|
||||||
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz /tmp
|
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz /tmp
|
||||||
@@ -12,8 +12,8 @@ RUN \
|
|||||||
|
|
||||||
COPY rootfs /
|
COPY rootfs /
|
||||||
|
|
||||||
ENV RELAY relay.example.com
|
ENV RELAY=relay.example.com
|
||||||
ENV ENCRYPTED_ONLY 0
|
ENV ENCRYPTED_ONLY=0
|
||||||
|
|
||||||
EXPOSE 21115 21116 21116/udp 21117 21118 21119
|
EXPOSE 21115 21116 21116/udp 21117 21118 21119
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
#!/command/execlineb -P
|
#!/command/with-contenv sh
|
||||||
posix-cd /data
|
cd /data
|
||||||
/usr/bin/hbbr
|
PARAMS=
|
||||||
|
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k _"
|
||||||
|
/usr/bin/hbbr $PARAMS
|
||||||
|
|||||||
87
kubernetes/example.yaml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: rustdesk-server
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: rustdesk
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: rustdesk
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
# id Server (HBBS)
|
||||||
|
- name: hbbs
|
||||||
|
image: rustdesk/rustdesk-server:latest
|
||||||
|
env:
|
||||||
|
- name: RELAY_HOST
|
||||||
|
value: "rustdesk.yourdomain.tld"
|
||||||
|
command: ["hbbs", "-r", "$(RELAY_HOST):21117", "-k", "_"]
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /root
|
||||||
|
name: rustdesk-data
|
||||||
|
ports:
|
||||||
|
- containerPort: 21115
|
||||||
|
- containerPort: 21116
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 21116
|
||||||
|
protocol: UDP
|
||||||
|
- containerPort: 21118
|
||||||
|
|
||||||
|
# Relay Server (HBBR)
|
||||||
|
- name: hbbr
|
||||||
|
image: rustdesk/rustdesk-server:latest
|
||||||
|
command: ["hbbr", "-k", "_"]
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /root
|
||||||
|
name: rustdesk-data
|
||||||
|
ports:
|
||||||
|
- containerPort: 21117
|
||||||
|
- containerPort: 21119
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: rustdesk-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: rustdesk-pvc
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: rustdesk-service
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer # Or NodePort
|
||||||
|
selector:
|
||||||
|
app: rustdesk
|
||||||
|
ports:
|
||||||
|
- name: hbbs-tcp
|
||||||
|
port: 21115
|
||||||
|
targetPort: 21115
|
||||||
|
protocol: TCP
|
||||||
|
- name: hbbs-udp
|
||||||
|
port: 21116
|
||||||
|
targetPort: 21116
|
||||||
|
protocol: UDP
|
||||||
|
- name: hbbs-tcp-main
|
||||||
|
port: 21116
|
||||||
|
targetPort: 21116
|
||||||
|
protocol: TCP
|
||||||
|
- name: hbbr-tcp
|
||||||
|
port: 21117
|
||||||
|
targetPort: 21117
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: rustdesk-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce # RWM would be available only with pro version
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
1
libs/hbb_common
Submodule
4
libs/hbb_common/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
||||||
src/protos/
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "hbb_common"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["open-trade <info@opentradesolutions.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
protobuf = { version = "3.1", features = ["with-bytes"] }
|
|
||||||
tokio = { version = "1.20", features = ["full"] }
|
|
||||||
tokio-util = { version = "0.7", features = ["full"] }
|
|
||||||
futures = "0.3"
|
|
||||||
bytes = "1.2"
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.9"
|
|
||||||
socket2 = { version = "0.3", features = ["reuseport"] }
|
|
||||||
zstd = "0.9"
|
|
||||||
quinn = {version = "0.8", optional = true }
|
|
||||||
anyhow = "1.0"
|
|
||||||
futures-util = "0.3"
|
|
||||||
directories-next = "2.0"
|
|
||||||
rand = "0.8"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde = "1.0"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
confy = { git = "https://github.com/open-trade/confy" }
|
|
||||||
dirs-next = "2.0"
|
|
||||||
filetime = "0.2"
|
|
||||||
sodiumoxide = "0.2"
|
|
||||||
regex = "1.4"
|
|
||||||
tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
|
||||||
mac_address = "1.1"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
quic = []
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
protobuf-codegen = "3.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
winapi = { version = "0.3", features = ["winuser"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
toml = "0.5"
|
|
||||||
serde_json = "1.0"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
std::fs::create_dir_all("src/protos").unwrap();
|
|
||||||
protobuf_codegen::Codegen::new()
|
|
||||||
.pure()
|
|
||||||
.out_dir("src/protos")
|
|
||||||
.inputs(&["protos/rendezvous.proto", "protos/message.proto"])
|
|
||||||
.include("protos")
|
|
||||||
.customize(
|
|
||||||
protobuf_codegen::Customize::default()
|
|
||||||
.tokio_bytes(true)
|
|
||||||
)
|
|
||||||
.run()
|
|
||||||
.expect("Codegen failed.");
|
|
||||||
}
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
package hbb;
|
|
||||||
|
|
||||||
message VP9 {
|
|
||||||
bytes data = 1;
|
|
||||||
bool key = 2;
|
|
||||||
int64 pts = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VP9s { repeated VP9 frames = 1; }
|
|
||||||
|
|
||||||
message RGB { bool compress = 1; }
|
|
||||||
|
|
||||||
// planes data send directly in binary for better use arraybuffer on web
|
|
||||||
message YUV {
|
|
||||||
bool compress = 1;
|
|
||||||
int32 stride = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VideoFrame {
|
|
||||||
oneof union {
|
|
||||||
VP9s vp9s = 6;
|
|
||||||
RGB rgb = 7;
|
|
||||||
YUV yuv = 8;
|
|
||||||
}
|
|
||||||
int64 timestamp = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IdPk {
|
|
||||||
string id = 1;
|
|
||||||
bytes pk = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DisplayInfo {
|
|
||||||
sint32 x = 1;
|
|
||||||
sint32 y = 2;
|
|
||||||
int32 width = 3;
|
|
||||||
int32 height = 4;
|
|
||||||
string name = 5;
|
|
||||||
bool online = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PortForward {
|
|
||||||
string host = 1;
|
|
||||||
int32 port = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransfer {
|
|
||||||
string dir = 1;
|
|
||||||
bool show_hidden = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginRequest {
|
|
||||||
string username = 1;
|
|
||||||
bytes password = 2;
|
|
||||||
string my_id = 4;
|
|
||||||
string my_name = 5;
|
|
||||||
OptionMessage option = 6;
|
|
||||||
oneof union {
|
|
||||||
FileTransfer file_transfer = 7;
|
|
||||||
PortForward port_forward = 8;
|
|
||||||
}
|
|
||||||
bool video_ack_required = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ChatMessage { string text = 1; }
|
|
||||||
|
|
||||||
message PeerInfo {
|
|
||||||
string username = 1;
|
|
||||||
string hostname = 2;
|
|
||||||
string platform = 3;
|
|
||||||
repeated DisplayInfo displays = 4;
|
|
||||||
int32 current_display = 5;
|
|
||||||
bool sas_enabled = 6;
|
|
||||||
string version = 7;
|
|
||||||
int32 conn_id = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginResponse {
|
|
||||||
oneof union {
|
|
||||||
string error = 1;
|
|
||||||
PeerInfo peer_info = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message MouseEvent {
|
|
||||||
int32 mask = 1;
|
|
||||||
sint32 x = 2;
|
|
||||||
sint32 y = 3;
|
|
||||||
repeated ControlKey modifiers = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ControlKey {
|
|
||||||
Unknown = 0;
|
|
||||||
Alt = 1;
|
|
||||||
Backspace = 2;
|
|
||||||
CapsLock = 3;
|
|
||||||
Control = 4;
|
|
||||||
Delete = 5;
|
|
||||||
DownArrow = 6;
|
|
||||||
End = 7;
|
|
||||||
Escape = 8;
|
|
||||||
F1 = 9;
|
|
||||||
F10 = 10;
|
|
||||||
F11 = 11;
|
|
||||||
F12 = 12;
|
|
||||||
F2 = 13;
|
|
||||||
F3 = 14;
|
|
||||||
F4 = 15;
|
|
||||||
F5 = 16;
|
|
||||||
F6 = 17;
|
|
||||||
F7 = 18;
|
|
||||||
F8 = 19;
|
|
||||||
F9 = 20;
|
|
||||||
Home = 21;
|
|
||||||
LeftArrow = 22;
|
|
||||||
/// meta key (also known as "windows"; "super"; and "command")
|
|
||||||
Meta = 23;
|
|
||||||
/// option key on macOS (alt key on Linux and Windows)
|
|
||||||
Option = 24; // deprecated, use Alt instead
|
|
||||||
PageDown = 25;
|
|
||||||
PageUp = 26;
|
|
||||||
Return = 27;
|
|
||||||
RightArrow = 28;
|
|
||||||
Shift = 29;
|
|
||||||
Space = 30;
|
|
||||||
Tab = 31;
|
|
||||||
UpArrow = 32;
|
|
||||||
Numpad0 = 33;
|
|
||||||
Numpad1 = 34;
|
|
||||||
Numpad2 = 35;
|
|
||||||
Numpad3 = 36;
|
|
||||||
Numpad4 = 37;
|
|
||||||
Numpad5 = 38;
|
|
||||||
Numpad6 = 39;
|
|
||||||
Numpad7 = 40;
|
|
||||||
Numpad8 = 41;
|
|
||||||
Numpad9 = 42;
|
|
||||||
Cancel = 43;
|
|
||||||
Clear = 44;
|
|
||||||
Menu = 45; // deprecated, use Alt instead
|
|
||||||
Pause = 46;
|
|
||||||
Kana = 47;
|
|
||||||
Hangul = 48;
|
|
||||||
Junja = 49;
|
|
||||||
Final = 50;
|
|
||||||
Hanja = 51;
|
|
||||||
Kanji = 52;
|
|
||||||
Convert = 53;
|
|
||||||
Select = 54;
|
|
||||||
Print = 55;
|
|
||||||
Execute = 56;
|
|
||||||
Snapshot = 57;
|
|
||||||
Insert = 58;
|
|
||||||
Help = 59;
|
|
||||||
Sleep = 60;
|
|
||||||
Separator = 61;
|
|
||||||
Scroll = 62;
|
|
||||||
NumLock = 63;
|
|
||||||
RWin = 64;
|
|
||||||
Apps = 65;
|
|
||||||
Multiply = 66;
|
|
||||||
Add = 67;
|
|
||||||
Subtract = 68;
|
|
||||||
Decimal = 69;
|
|
||||||
Divide = 70;
|
|
||||||
Equals = 71;
|
|
||||||
NumpadEnter = 72;
|
|
||||||
RShift = 73;
|
|
||||||
RControl = 74;
|
|
||||||
RAlt = 75;
|
|
||||||
CtrlAltDel = 100;
|
|
||||||
LockScreen = 101;
|
|
||||||
}
|
|
||||||
|
|
||||||
message KeyEvent {
|
|
||||||
bool down = 1;
|
|
||||||
bool press = 2;
|
|
||||||
oneof union {
|
|
||||||
ControlKey control_key = 3;
|
|
||||||
uint32 chr = 4;
|
|
||||||
uint32 unicode = 5;
|
|
||||||
string seq = 6;
|
|
||||||
}
|
|
||||||
repeated ControlKey modifiers = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CursorData {
|
|
||||||
uint64 id = 1;
|
|
||||||
sint32 hotx = 2;
|
|
||||||
sint32 hoty = 3;
|
|
||||||
int32 width = 4;
|
|
||||||
int32 height = 5;
|
|
||||||
bytes colors = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CursorPosition {
|
|
||||||
sint32 x = 1;
|
|
||||||
sint32 y = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Hash {
|
|
||||||
string salt = 1;
|
|
||||||
string challenge = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Clipboard {
|
|
||||||
bool compress = 1;
|
|
||||||
bytes content = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FileType {
|
|
||||||
Dir = 0;
|
|
||||||
DirLink = 2;
|
|
||||||
DirDrive = 3;
|
|
||||||
File = 4;
|
|
||||||
FileLink = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileEntry {
|
|
||||||
FileType entry_type = 1;
|
|
||||||
string name = 2;
|
|
||||||
bool is_hidden = 3;
|
|
||||||
uint64 size = 4;
|
|
||||||
uint64 modified_time = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileDirectory {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2;
|
|
||||||
repeated FileEntry entries = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ReadDir {
|
|
||||||
string path = 1;
|
|
||||||
bool include_hidden = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ReadAllFiles {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2;
|
|
||||||
bool include_hidden = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileAction {
|
|
||||||
oneof union {
|
|
||||||
ReadDir read_dir = 1;
|
|
||||||
FileTransferSendRequest send = 2;
|
|
||||||
FileTransferReceiveRequest receive = 3;
|
|
||||||
FileDirCreate create = 4;
|
|
||||||
FileRemoveDir remove_dir = 5;
|
|
||||||
FileRemoveFile remove_file = 6;
|
|
||||||
ReadAllFiles all_files = 7;
|
|
||||||
FileTransferCancel cancel = 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransferCancel { int32 id = 1; }
|
|
||||||
|
|
||||||
message FileResponse {
|
|
||||||
oneof union {
|
|
||||||
FileDirectory dir = 1;
|
|
||||||
FileTransferBlock block = 2;
|
|
||||||
FileTransferError error = 3;
|
|
||||||
FileTransferDone done = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransferBlock {
|
|
||||||
int32 id = 1;
|
|
||||||
sint32 file_num = 2;
|
|
||||||
bytes data = 3;
|
|
||||||
bool compressed = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransferError {
|
|
||||||
int32 id = 1;
|
|
||||||
string error = 2;
|
|
||||||
sint32 file_num = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransferSendRequest {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2;
|
|
||||||
bool include_hidden = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransferDone {
|
|
||||||
int32 id = 1;
|
|
||||||
sint32 file_num = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileTransferReceiveRequest {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2; // path written to
|
|
||||||
repeated FileEntry files = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileRemoveDir {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2;
|
|
||||||
bool recursive = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileRemoveFile {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2;
|
|
||||||
sint32 file_num = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FileDirCreate {
|
|
||||||
int32 id = 1;
|
|
||||||
string path = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// main logic from freeRDP
|
|
||||||
message CliprdrMonitorReady {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrFormat {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
int32 id = 2;
|
|
||||||
string format = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrServerFormatList {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
repeated CliprdrFormat formats = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrServerFormatListResponse {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
int32 msg_flags = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrServerFormatDataRequest {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
int32 requested_format_id = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrServerFormatDataResponse {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
int32 msg_flags = 2;
|
|
||||||
bytes format_data = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrFileContentsRequest {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
int32 stream_id = 2;
|
|
||||||
int32 list_index = 3;
|
|
||||||
int32 dw_flags = 4;
|
|
||||||
int32 n_position_low = 5;
|
|
||||||
int32 n_position_high = 6;
|
|
||||||
int32 cb_requested = 7;
|
|
||||||
bool have_clip_data_id = 8;
|
|
||||||
int32 clip_data_id = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CliprdrFileContentsResponse {
|
|
||||||
int32 conn_id = 1;
|
|
||||||
int32 msg_flags = 3;
|
|
||||||
int32 stream_id = 4;
|
|
||||||
bytes requested_data = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Cliprdr {
|
|
||||||
oneof union {
|
|
||||||
CliprdrMonitorReady ready = 1;
|
|
||||||
CliprdrServerFormatList format_list = 2;
|
|
||||||
CliprdrServerFormatListResponse format_list_response = 3;
|
|
||||||
CliprdrServerFormatDataRequest format_data_request = 4;
|
|
||||||
CliprdrServerFormatDataResponse format_data_response = 5;
|
|
||||||
CliprdrFileContentsRequest file_contents_request = 6;
|
|
||||||
CliprdrFileContentsResponse file_contents_response = 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message SwitchDisplay {
|
|
||||||
int32 display = 1;
|
|
||||||
sint32 x = 2;
|
|
||||||
sint32 y = 3;
|
|
||||||
int32 width = 4;
|
|
||||||
int32 height = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PermissionInfo {
|
|
||||||
enum Permission {
|
|
||||||
Keyboard = 0;
|
|
||||||
Clipboard = 2;
|
|
||||||
Audio = 3;
|
|
||||||
File = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
Permission permission = 1;
|
|
||||||
bool enabled = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ImageQuality {
|
|
||||||
NotSet = 0;
|
|
||||||
Low = 2;
|
|
||||||
Balanced = 3;
|
|
||||||
Best = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OptionMessage {
|
|
||||||
enum BoolOption {
|
|
||||||
NotSet = 0;
|
|
||||||
No = 1;
|
|
||||||
Yes = 2;
|
|
||||||
}
|
|
||||||
ImageQuality image_quality = 1;
|
|
||||||
BoolOption lock_after_session_end = 2;
|
|
||||||
BoolOption show_remote_cursor = 3;
|
|
||||||
BoolOption privacy_mode = 4;
|
|
||||||
BoolOption block_input = 5;
|
|
||||||
int32 custom_image_quality = 6;
|
|
||||||
BoolOption disable_audio = 7;
|
|
||||||
BoolOption disable_clipboard = 8;
|
|
||||||
BoolOption enable_file_transfer = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OptionResponse {
|
|
||||||
OptionMessage opt = 1;
|
|
||||||
string error = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TestDelay {
|
|
||||||
int64 time = 1;
|
|
||||||
bool from_client = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PublicKey {
|
|
||||||
bytes asymmetric_value = 1;
|
|
||||||
bytes symmetric_value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SignedId { bytes id = 1; }
|
|
||||||
|
|
||||||
message AudioFormat {
|
|
||||||
uint32 sample_rate = 1;
|
|
||||||
uint32 channels = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AudioFrame {
|
|
||||||
bytes data = 1;
|
|
||||||
int64 timestamp = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Misc {
|
|
||||||
oneof union {
|
|
||||||
ChatMessage chat_message = 4;
|
|
||||||
SwitchDisplay switch_display = 5;
|
|
||||||
PermissionInfo permission_info = 6;
|
|
||||||
OptionMessage option = 7;
|
|
||||||
AudioFormat audio_format = 8;
|
|
||||||
string close_reason = 9;
|
|
||||||
bool refresh_video = 10;
|
|
||||||
OptionResponse option_response = 11;
|
|
||||||
bool video_received = 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Message {
|
|
||||||
oneof union {
|
|
||||||
SignedId signed_id = 3;
|
|
||||||
PublicKey public_key = 4;
|
|
||||||
TestDelay test_delay = 5;
|
|
||||||
VideoFrame video_frame = 6;
|
|
||||||
LoginRequest login_request = 7;
|
|
||||||
LoginResponse login_response = 8;
|
|
||||||
Hash hash = 9;
|
|
||||||
MouseEvent mouse_event = 10;
|
|
||||||
AudioFrame audio_frame = 11;
|
|
||||||
CursorData cursor_data = 12;
|
|
||||||
CursorPosition cursor_position = 13;
|
|
||||||
uint64 cursor_id = 14;
|
|
||||||
KeyEvent key_event = 15;
|
|
||||||
Clipboard clipboard = 16;
|
|
||||||
FileAction file_action = 17;
|
|
||||||
FileResponse file_response = 18;
|
|
||||||
Misc misc = 19;
|
|
||||||
Cliprdr cliprdr = 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
package hbb;
|
|
||||||
|
|
||||||
message RegisterPeer {
|
|
||||||
string id = 1;
|
|
||||||
int32 serial = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ConnType {
|
|
||||||
DEFAULT_CONN = 0;
|
|
||||||
FILE_TRANSFER = 1;
|
|
||||||
PORT_FORWARD = 2;
|
|
||||||
RDP = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RegisterPeerResponse { bool request_pk = 2; }
|
|
||||||
|
|
||||||
message PunchHoleRequest {
|
|
||||||
string id = 1;
|
|
||||||
NatType nat_type = 2;
|
|
||||||
string licence_key = 3;
|
|
||||||
ConnType conn_type = 4;
|
|
||||||
string token = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PunchHole {
|
|
||||||
bytes socket_addr = 1;
|
|
||||||
string relay_server = 2;
|
|
||||||
NatType nat_type = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TestNatRequest {
|
|
||||||
int32 serial = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// per my test, uint/int has no difference in encoding, int not good for negative, use sint for negative
|
|
||||||
message TestNatResponse {
|
|
||||||
int32 port = 1;
|
|
||||||
ConfigUpdate cu = 2; // for mobile
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NatType {
|
|
||||||
UNKNOWN_NAT = 0;
|
|
||||||
ASYMMETRIC = 1;
|
|
||||||
SYMMETRIC = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PunchHoleSent {
|
|
||||||
bytes socket_addr = 1;
|
|
||||||
string id = 2;
|
|
||||||
string relay_server = 3;
|
|
||||||
NatType nat_type = 4;
|
|
||||||
string version = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RegisterPk {
|
|
||||||
string id = 1;
|
|
||||||
bytes uuid = 2;
|
|
||||||
bytes pk = 3;
|
|
||||||
string old_id = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RegisterPkResponse {
|
|
||||||
enum Result {
|
|
||||||
OK = 0;
|
|
||||||
UUID_MISMATCH = 2;
|
|
||||||
ID_EXISTS = 3;
|
|
||||||
TOO_FREQUENT = 4;
|
|
||||||
INVALID_ID_FORMAT = 5;
|
|
||||||
NOT_SUPPORT = 6;
|
|
||||||
SERVER_ERROR = 7;
|
|
||||||
}
|
|
||||||
Result result = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PunchHoleResponse {
|
|
||||||
bytes socket_addr = 1;
|
|
||||||
bytes pk = 2;
|
|
||||||
enum Failure {
|
|
||||||
ID_NOT_EXIST = 0;
|
|
||||||
OFFLINE = 2;
|
|
||||||
LICENSE_MISMATCH = 3;
|
|
||||||
LICENSE_OVERUSE = 4;
|
|
||||||
}
|
|
||||||
Failure failure = 3;
|
|
||||||
string relay_server = 4;
|
|
||||||
oneof union {
|
|
||||||
NatType nat_type = 5;
|
|
||||||
bool is_local = 6;
|
|
||||||
}
|
|
||||||
string other_failure = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ConfigUpdate {
|
|
||||||
int32 serial = 1;
|
|
||||||
repeated string rendezvous_servers = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestRelay {
|
|
||||||
string id = 1;
|
|
||||||
string uuid = 2;
|
|
||||||
bytes socket_addr = 3;
|
|
||||||
string relay_server = 4;
|
|
||||||
bool secure = 5;
|
|
||||||
string licence_key = 6;
|
|
||||||
ConnType conn_type = 7;
|
|
||||||
string token = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RelayResponse {
|
|
||||||
bytes socket_addr = 1;
|
|
||||||
string uuid = 2;
|
|
||||||
string relay_server = 3;
|
|
||||||
oneof union {
|
|
||||||
string id = 4;
|
|
||||||
bytes pk = 5;
|
|
||||||
}
|
|
||||||
string refuse_reason = 6;
|
|
||||||
string version = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SoftwareUpdate { string url = 1; }
|
|
||||||
|
|
||||||
// if in same intranet, punch hole won't work both for udp and tcp,
|
|
||||||
// even some router has below connection error if we connect itself,
|
|
||||||
// { kind: Other, error: "could not resolve to any address" },
|
|
||||||
// so we request local address to connect.
|
|
||||||
message FetchLocalAddr {
|
|
||||||
bytes socket_addr = 1;
|
|
||||||
string relay_server = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LocalAddr {
|
|
||||||
bytes socket_addr = 1;
|
|
||||||
bytes local_addr = 2;
|
|
||||||
string relay_server = 3;
|
|
||||||
string id = 4;
|
|
||||||
string version = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PeerDiscovery {
|
|
||||||
string cmd = 1;
|
|
||||||
string mac = 2;
|
|
||||||
string id = 3;
|
|
||||||
string username = 4;
|
|
||||||
string hostname = 5;
|
|
||||||
string platform = 6;
|
|
||||||
string misc = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OnlineRequest {
|
|
||||||
string id = 1;
|
|
||||||
repeated string peers = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OnlineResponse {
|
|
||||||
bytes states = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RendezvousMessage {
|
|
||||||
oneof union {
|
|
||||||
RegisterPeer register_peer = 6;
|
|
||||||
RegisterPeerResponse register_peer_response = 7;
|
|
||||||
PunchHoleRequest punch_hole_request = 8;
|
|
||||||
PunchHole punch_hole = 9;
|
|
||||||
PunchHoleSent punch_hole_sent = 10;
|
|
||||||
PunchHoleResponse punch_hole_response = 11;
|
|
||||||
FetchLocalAddr fetch_local_addr = 12;
|
|
||||||
LocalAddr local_addr = 13;
|
|
||||||
ConfigUpdate configure_update = 14;
|
|
||||||
RegisterPk register_pk = 15;
|
|
||||||
RegisterPkResponse register_pk_response = 16;
|
|
||||||
SoftwareUpdate software_update = 17;
|
|
||||||
RequestRelay request_relay = 18;
|
|
||||||
RelayResponse relay_response = 19;
|
|
||||||
TestNatRequest test_nat_request = 20;
|
|
||||||
TestNatResponse test_nat_response = 21;
|
|
||||||
PeerDiscovery peer_discovery = 22;
|
|
||||||
OnlineRequest online_request = 23;
|
|
||||||
OnlineResponse online_response = 24;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
|
||||||
use std::io;
|
|
||||||
use tokio_util::codec::{Decoder, Encoder};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct BytesCodec {
|
|
||||||
state: DecodeState,
|
|
||||||
raw: bool,
|
|
||||||
max_packet_length: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum DecodeState {
|
|
||||||
Head,
|
|
||||||
Data(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BytesCodec {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
state: DecodeState::Head,
|
|
||||||
raw: false,
|
|
||||||
max_packet_length: usize::MAX,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_raw(&mut self) {
|
|
||||||
self.raw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_packet_length(&mut self, n: usize) {
|
|
||||||
self.max_packet_length = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_head(&mut self, src: &mut BytesMut) -> io::Result<Option<usize>> {
|
|
||||||
if src.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let head_len = ((src[0] & 0x3) + 1) as usize;
|
|
||||||
if src.len() < head_len {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let mut n = src[0] as usize;
|
|
||||||
if head_len > 1 {
|
|
||||||
n |= (src[1] as usize) << 8;
|
|
||||||
}
|
|
||||||
if head_len > 2 {
|
|
||||||
n |= (src[2] as usize) << 16;
|
|
||||||
}
|
|
||||||
if head_len > 3 {
|
|
||||||
n |= (src[3] as usize) << 24;
|
|
||||||
}
|
|
||||||
n >>= 2;
|
|
||||||
if n > self.max_packet_length {
|
|
||||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "Too big packet"));
|
|
||||||
}
|
|
||||||
src.advance(head_len);
|
|
||||||
src.reserve(n);
|
|
||||||
return Ok(Some(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
|
||||||
if src.len() < n {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Ok(Some(src.split_to(n)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decoder for BytesCodec {
|
|
||||||
type Item = BytesMut;
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
|
|
||||||
if self.raw {
|
|
||||||
if !src.is_empty() {
|
|
||||||
let len = src.len();
|
|
||||||
return Ok(Some(src.split_to(len)));
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let n = match self.state {
|
|
||||||
DecodeState::Head => match self.decode_head(src)? {
|
|
||||||
Some(n) => {
|
|
||||||
self.state = DecodeState::Data(n);
|
|
||||||
n
|
|
||||||
}
|
|
||||||
None => return Ok(None),
|
|
||||||
},
|
|
||||||
DecodeState::Data(n) => n,
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.decode_data(n, src)? {
|
|
||||||
Some(data) => {
|
|
||||||
self.state = DecodeState::Head;
|
|
||||||
Ok(Some(data))
|
|
||||||
}
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encoder<Bytes> for BytesCodec {
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn encode(&mut self, data: Bytes, buf: &mut BytesMut) -> Result<(), io::Error> {
|
|
||||||
if self.raw {
|
|
||||||
buf.reserve(data.len());
|
|
||||||
buf.put(data);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if data.len() <= 0x3F {
|
|
||||||
buf.put_u8((data.len() << 2) as u8);
|
|
||||||
} else if data.len() <= 0x3FFF {
|
|
||||||
buf.put_u16_le((data.len() << 2) as u16 | 0x1);
|
|
||||||
} else if data.len() <= 0x3FFFFF {
|
|
||||||
let h = (data.len() << 2) as u32 | 0x2;
|
|
||||||
buf.put_u16_le((h & 0xFFFF) as u16);
|
|
||||||
buf.put_u8((h >> 16) as u8);
|
|
||||||
} else if data.len() <= 0x3FFFFFFF {
|
|
||||||
buf.put_u32_le((data.len() << 2) as u32 | 0x3);
|
|
||||||
} else {
|
|
||||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Overflow"));
|
|
||||||
}
|
|
||||||
buf.extend(data);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_codec1() {
|
|
||||||
let mut codec = BytesCodec::new();
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
bytes.resize(0x3F, 1);
|
|
||||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
|
||||||
let buf_saved = buf.clone();
|
|
||||||
assert_eq!(buf.len(), 0x3F + 1);
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0x3F);
|
|
||||||
assert_eq!(res[0], 1);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
let mut codec2 = BytesCodec::new();
|
|
||||||
let mut buf2 = BytesMut::new();
|
|
||||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
buf2.extend(&buf_saved[0..1]);
|
|
||||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
buf2.extend(&buf_saved[1..]);
|
|
||||||
if let Ok(Some(res)) = codec2.decode(&mut buf2) {
|
|
||||||
assert_eq!(res.len(), 0x3F);
|
|
||||||
assert_eq!(res[0], 1);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_codec2() {
|
|
||||||
let mut codec = BytesCodec::new();
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
assert!(!codec.encode("".into(), &mut buf).is_err());
|
|
||||||
assert_eq!(buf.len(), 1);
|
|
||||||
bytes.resize(0x3F + 1, 2);
|
|
||||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
|
||||||
assert_eq!(buf.len(), 0x3F + 2 + 2);
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0x3F + 1);
|
|
||||||
assert_eq!(res[0], 2);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_codec3() {
|
|
||||||
let mut codec = BytesCodec::new();
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
bytes.resize(0x3F - 1, 3);
|
|
||||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
|
||||||
assert_eq!(buf.len(), 0x3F + 1 - 1);
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0x3F - 1);
|
|
||||||
assert_eq!(res[0], 3);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_codec4() {
|
|
||||||
let mut codec = BytesCodec::new();
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
bytes.resize(0x3FFF, 4);
|
|
||||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
|
||||||
assert_eq!(buf.len(), 0x3FFF + 2);
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0x3FFF);
|
|
||||||
assert_eq!(res[0], 4);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_codec5() {
|
|
||||||
let mut codec = BytesCodec::new();
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
bytes.resize(0x3FFFFF, 5);
|
|
||||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
|
||||||
assert_eq!(buf.len(), 0x3FFFFF + 3);
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0x3FFFFF);
|
|
||||||
assert_eq!(res[0], 5);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_codec6() {
|
|
||||||
let mut codec = BytesCodec::new();
|
|
||||||
let mut buf = BytesMut::new();
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
bytes.resize(0x3FFFFF + 1, 6);
|
|
||||||
assert!(!codec.encode(bytes.into(), &mut buf).is_err());
|
|
||||||
let buf_saved = buf.clone();
|
|
||||||
assert_eq!(buf.len(), 0x3FFFFF + 4 + 1);
|
|
||||||
if let Ok(Some(res)) = codec.decode(&mut buf) {
|
|
||||||
assert_eq!(res.len(), 0x3FFFFF + 1);
|
|
||||||
assert_eq!(res[0], 6);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
let mut codec2 = BytesCodec::new();
|
|
||||||
let mut buf2 = BytesMut::new();
|
|
||||||
buf2.extend(&buf_saved[0..1]);
|
|
||||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
buf2.extend(&buf_saved[1..6]);
|
|
||||||
if let Ok(None) = codec2.decode(&mut buf2) {
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
buf2.extend(&buf_saved[6..]);
|
|
||||||
if let Ok(Some(res)) = codec2.decode(&mut buf2) {
|
|
||||||
assert_eq!(res.len(), 0x3FFFFF + 1);
|
|
||||||
assert_eq!(res[0], 6);
|
|
||||||
} else {
|
|
||||||
assert!(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use zstd::block::{Compressor, Decompressor};
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static COMPRESSOR: RefCell<Compressor> = RefCell::new(Compressor::new());
|
|
||||||
static DECOMPRESSOR: RefCell<Decompressor> = RefCell::new(Decompressor::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The library supports regular compression levels from 1 up to ZSTD_maxCLevel(),
|
|
||||||
/// which is currently 22. Levels >= 20
|
|
||||||
/// Default level is ZSTD_CLEVEL_DEFAULT==3.
|
|
||||||
/// value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT
|
|
||||||
pub fn compress(data: &[u8], level: i32) -> Vec<u8> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
COMPRESSOR.with(|c| {
|
|
||||||
if let Ok(mut c) = c.try_borrow_mut() {
|
|
||||||
match c.compress(data, level) {
|
|
||||||
Ok(res) => out = res,
|
|
||||||
Err(err) => {
|
|
||||||
crate::log::debug!("Failed to compress: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decompress(data: &[u8]) -> Vec<u8> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
DECOMPRESSOR.with(|d| {
|
|
||||||
if let Ok(mut d) = d.try_borrow_mut() {
|
|
||||||
const MAX: usize = 1024 * 1024 * 64;
|
|
||||||
const MIN: usize = 1024 * 1024;
|
|
||||||
let mut n = 30 * data.len();
|
|
||||||
if n > MAX {
|
|
||||||
n = MAX;
|
|
||||||
}
|
|
||||||
if n < MIN {
|
|
||||||
n = MIN;
|
|
||||||
}
|
|
||||||
match d.decompress(data, n) {
|
|
||||||
Ok(res) => out = res,
|
|
||||||
Err(err) => {
|
|
||||||
crate::log::debug!("Failed to decompress: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
out
|
|
||||||
}
|
|
||||||
@@ -1,886 +0,0 @@
|
|||||||
use crate::log;
|
|
||||||
use directories_next::ProjectDirs;
|
|
||||||
use rand::Rng;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use sodiumoxide::crypto::sign;
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
fs,
|
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::{Arc, Mutex, RwLock},
|
|
||||||
time::SystemTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RENDEZVOUS_TIMEOUT: u64 = 12_000;
|
|
||||||
pub const CONNECT_TIMEOUT: u64 = 18_000;
|
|
||||||
pub const REG_INTERVAL: i64 = 12_000;
|
|
||||||
pub const COMPRESS_LEVEL: i32 = 3;
|
|
||||||
const SERIAL: i32 = 1;
|
|
||||||
// 128x128
|
|
||||||
#[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding
|
|
||||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII=
|
|
||||||
";
|
|
||||||
#[cfg(not(target_os = "macos"))] // 128x128 no padding
|
|
||||||
pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAA7VBMVEUAAAAAcf8Acf8Acf8Adf8Acf8Acf8AcP8Acv8AcP8Acf8Acf8Acf8Acv8Acf8Acf8Ab/8AcP8Acf8Acf8Acf/////7/f8Dc/8TfP/1+f/n8v9Hmf/u9v+Uw//Q5f9hp/8Yfv8Qev8Ld/+52P+z1f+s0f81j/8wjP8Hdf/3+/8mh/8fg//x9//h7//H4P9xsP9rrf9oq/8rif/r9P/D3v+92/+Duv9bpP/d7f/U5/9NnP8/lP8jhP/L4v/B3P+OwP9+t/95tf9Rn/8bgf/Z6v+Zx/90sv9lqf85kf+hy/9UoP+Wxf+kzP+dyP+Lvv/H4q8IAAAAFHRSTlMA+u6bB6x5XR4V0+S4i4k5N+a81W8MiAQAAAVcSURBVHjazdvpWtpAGIbhgEutdW3fL2GHsMsiq4KI+66t5384XahF/GbizJAy3j/1Ah5CJhNCxpm1vbryLRrBfxKJrq+sbjtSa5u7WIDdzTVH5PNSBAsSWfrsMJ+iWKDoJ2fW8hIWbGl55vW/YuE2XhUsb8CCr9OCJVix9G//gyWf/o6/KCyJfrbwAfAPYS0CayK/j4mbsGjrV8AXWLTrONuwasdZhVWrzgqsWnG+wap1Jwqrok4EVkUcmKhdVvBaOVnzYEY/oJpMD4mo6ONF/ZSIUsX2FZjQA7xRqUET+y/v2W/Sy59u62DCDMgdJmhqgIk7eqWQBBNWwPhmj147w8QTzTjKVsGEEBBLuzSrhIkivTF8DD/Aa6forQNMHBD/VyXkgHGfuBN5ALln1TADOnESyGCiT8L/1kILqD6Q0BEm9kkofhdSwNUJiV1jQvZ/SnthBNSaJJGZbgGJUnX+gEqCZPpsJ2T2Y/MGVBrE8eOAvCA/X8A4QXLnmEhTgIPqPAG5IQU4fhmkFOT7HAFenwIU8Jd/TUEODQIUtu1eOj/dUD9cknOTpgEDkup3YrOfVStDUomcWcBVisTiNxVw3TPpgCl4RgFFybZ/9iHmn8uS2yYBA8m7qUEu9oOEejH9gHxC+PazCHbcFM8K+gGHJNAs4z2xgnAkVHQDcnG1IzvnCSfvom7AM3EZ9voah4+KXoAvGFJHMSgqEfegF3BBTKoOVfkMMXFfJ8AT7MuXUDeOE9PWCUiKBpKOlmAP1gngH2LChw7vhJgr9YD8Hnt0BxrE27CtHnDJR4AHTX1+KFAP4Ef0LHTxN9HwlAMSbAjmoavKZ8ayakDXYAhwN3wzqgZk2UPvwRjshmeqATeCT09f3mWnEqoBGf4NxAB/moRqADuOtmDiid6KqQVcsQeOYOKW3uqqBRwL5nITj/yrlFpAVrDpTJT5llQLaLMHwshY7UDgvD+VujDC96WWWsBtSAE5FnChFnAeUkDMdAvw88EqTNT5SYXpTlgPaRQM1AIGorkolNnoUS1gJHigCX48SaoF3Asuspg4Mz0U8+FTgIkCG01V09kwBQP8xG5ofD5AXeirkPEJSUlwSVIfP5ykVQNaggvz+k7prTvVgDKF8BnUXP4kqgEe/257E8Ig7EE1gA8g2stBTz7FLxqrB3SIeYaeQ2IG6gE5l2+Cmt5MGOfP4KsGiH8DOYWOoujnDY2ALHF3810goZFOQDVBTFx9Uj7eI6bp6QTgnLjeGGq6KeJuoRUQixN3pDYWyz1Rva8XIL5UPFQZCsmG3gV7R+dieS+Jd3iHLglce7oBuCOhp3zwHLxPQpfQDvBOSKjZqUIml3ZJ6AD6AajFSZJwewWR8ZPsEY26SQDaJOMeZP23w6bTJ6kBjAJQILm9hzqm7otu4G+nhgGxIQUlPLKzL7GhbxqAboMCuN2XXd+lAL0ajAMwclV+FD6jAPEy5ghAlhfwX2FODX445gHKxyN++fs64PUHmDMAbbYN2DlKk2QaScwdgMs4SZxMv4OJJSoIIQBl2Qtk3gk4qiOUANRPJQHB+0A6j5AC4J27QQEZ4eZPAsYBXFk0N/YD7iUrxRBqALxOTzoMC3x8lCFlfkMjuz8iLfk6fzQCQgjg8q3ZEd8RzUVuKelBh96Nzcc3qelL1V+2zfRv1xc56Ino3tpdPT7cd//MspfTrD/7R6p4W4O2qLMObfnyIHvvYcrPtkZjDybW7d/eb32Bg/UlHnYXuXz5CMt8rC90sr7Uy/5iN+vL/ewveLS/5NNKwcbyR1r2a3/h8wdY+v3L2tZC5oUvW2uO1M7qyvp/Xv6/48z4CTxjJEfyjEaMAAAAAElFTkSuQmCC
|
|
||||||
";
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref ORG: Arc<RwLock<String>> = Arc::new(RwLock::new("com.carriez".to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
type Size = (i32, i32, i32, i32);
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::load()));
|
|
||||||
static ref CONFIG2: Arc<RwLock<Config2>> = Arc::new(RwLock::new(Config2::load()));
|
|
||||||
static ref LOCAL_CONFIG: Arc<RwLock<LocalConfig>> = Arc::new(RwLock::new(LocalConfig::load()));
|
|
||||||
pub static ref ONLINE: Arc<Mutex<HashMap<String, i64>>> = Default::default();
|
|
||||||
pub static ref PROD_RENDEZVOUS_SERVER: Arc<RwLock<String>> = Default::default();
|
|
||||||
pub static ref APP_NAME: Arc<RwLock<String>> = Arc::new(RwLock::new("RustDesk".to_owned()));
|
|
||||||
}
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref APP_DIR: Arc<RwLock<String>> = Default::default();
|
|
||||||
pub static ref APP_HOME_DIR: Arc<RwLock<String>> = Default::default();
|
|
||||||
}
|
|
||||||
const CHARS: &'static [char] = &[
|
|
||||||
'2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
|
|
||||||
'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
|
||||||
];
|
|
||||||
|
|
||||||
pub const RENDEZVOUS_SERVERS: &'static [&'static str] = &[
|
|
||||||
"rs-ny.rustdesk.com",
|
|
||||||
"rs-sg.rustdesk.com",
|
|
||||||
"rs-cn.rustdesk.com",
|
|
||||||
];
|
|
||||||
pub const RENDEZVOUS_PORT: i32 = 21116;
|
|
||||||
pub const RELAY_PORT: i32 = 21117;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub enum NetworkType {
|
|
||||||
Direct,
|
|
||||||
ProxySocks,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
|
||||||
pub struct Config {
|
|
||||||
#[serde(default)]
|
|
||||||
pub id: String,
|
|
||||||
#[serde(default)]
|
|
||||||
password: String,
|
|
||||||
#[serde(default)]
|
|
||||||
salt: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub key_pair: (Vec<u8>, Vec<u8>), // sk, pk
|
|
||||||
#[serde(default)]
|
|
||||||
key_confirmed: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
keys_confirmed: HashMap<String, bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Socks5Server {
|
|
||||||
#[serde(default)]
|
|
||||||
pub proxy: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub username: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// more variable configs
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
|
|
||||||
pub struct Config2 {
|
|
||||||
#[serde(default)]
|
|
||||||
rendezvous_server: String,
|
|
||||||
#[serde(default)]
|
|
||||||
nat_type: i32,
|
|
||||||
#[serde(default)]
|
|
||||||
serial: i32,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
socks: Option<Socks5Server>,
|
|
||||||
|
|
||||||
// the other scalar value must before this
|
|
||||||
#[serde(default)]
|
|
||||||
pub options: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct PeerConfig {
|
|
||||||
#[serde(default)]
|
|
||||||
pub password: Vec<u8>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub size: Size,
|
|
||||||
#[serde(default)]
|
|
||||||
pub size_ft: Size,
|
|
||||||
#[serde(default)]
|
|
||||||
pub size_pf: Size,
|
|
||||||
#[serde(default)]
|
|
||||||
pub view_style: String, // original (default), scale
|
|
||||||
#[serde(default)]
|
|
||||||
pub image_quality: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub custom_image_quality: Vec<i32>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub show_remote_cursor: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub lock_after_session_end: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub privacy_mode: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub port_forwards: Vec<(i32, String, i32)>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub direct_failures: i32,
|
|
||||||
#[serde(default)]
|
|
||||||
pub disable_audio: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub disable_clipboard: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub enable_file_transfer: bool,
|
|
||||||
|
|
||||||
// the other scalar value must before this
|
|
||||||
#[serde(default)]
|
|
||||||
pub options: HashMap<String, String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub info: PeerInfoSerde,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct PeerInfoSerde {
|
|
||||||
#[serde(default)]
|
|
||||||
pub username: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub hostname: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub platform: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn patch(path: PathBuf) -> PathBuf {
|
|
||||||
if let Some(_tmp) = path.to_str() {
|
|
||||||
#[cfg(windows)]
|
|
||||||
return _tmp
|
|
||||||
.replace(
|
|
||||||
"system32\\config\\systemprofile",
|
|
||||||
"ServiceProfiles\\LocalService",
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
return _tmp.replace("Application Support", "Preferences").into();
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
if _tmp == "/root" {
|
|
||||||
if let Ok(output) = std::process::Command::new("whoami").output() {
|
|
||||||
let user = String::from_utf8_lossy(&output.stdout)
|
|
||||||
.to_string()
|
|
||||||
.trim()
|
|
||||||
.to_owned();
|
|
||||||
if user != "root" {
|
|
||||||
return format!("/home/{}", user).into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config2 {
|
|
||||||
fn load() -> Config2 {
|
|
||||||
Config::load_::<Config2>("2")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file() -> PathBuf {
|
|
||||||
Config::file_("2")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn store(&self) {
|
|
||||||
Config::store_(self, "2");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get() -> Config2 {
|
|
||||||
return CONFIG2.read().unwrap().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(cfg: Config2) -> bool {
|
|
||||||
let mut lock = CONFIG2.write().unwrap();
|
|
||||||
if *lock == cfg {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*lock = cfg;
|
|
||||||
lock.store();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_path<T: serde::Serialize + serde::de::DeserializeOwned + Default + std::fmt::Debug>(
|
|
||||||
file: PathBuf,
|
|
||||||
) -> T {
|
|
||||||
let cfg = match confy::load_path(&file) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to load config: {}", err);
|
|
||||||
T::default()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn load_<T: serde::Serialize + serde::de::DeserializeOwned + Default + std::fmt::Debug>(
|
|
||||||
suffix: &str,
|
|
||||||
) -> T {
|
|
||||||
let file = Self::file_(suffix);
|
|
||||||
log::debug!("Configuration path: {}", file.display());
|
|
||||||
let cfg = load_path(file);
|
|
||||||
if suffix.is_empty() {
|
|
||||||
log::debug!("{:?}", cfg);
|
|
||||||
}
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
fn store_<T: serde::Serialize>(config: &T, suffix: &str) {
|
|
||||||
let file = Self::file_(suffix);
|
|
||||||
if let Err(err) = confy::store_path(file, config) {
|
|
||||||
log::error!("Failed to store config: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load() -> Config {
|
|
||||||
Config::load_::<Config>("")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn store(&self) {
|
|
||||||
Config::store_(self, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file() -> PathBuf {
|
|
||||||
Self::file_("")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_(suffix: &str) -> PathBuf {
|
|
||||||
let name = format!("{}{}", *APP_NAME.read().unwrap(), suffix);
|
|
||||||
Config::with_extension(Self::path(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_home() -> PathBuf {
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
return Self::path(APP_HOME_DIR.read().unwrap().as_str());
|
|
||||||
if let Some(path) = dirs_next::home_dir() {
|
|
||||||
patch(path)
|
|
||||||
} else if let Ok(path) = std::env::current_dir() {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
std::env::temp_dir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path<P: AsRef<Path>>(p: P) -> PathBuf {
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
{
|
|
||||||
let mut path: PathBuf = APP_DIR.read().unwrap().clone().into();
|
|
||||||
path.push(p);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let org = "";
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let org = ORG.read().unwrap().clone();
|
|
||||||
// /var/root for root
|
|
||||||
if let Some(project) = ProjectDirs::from("", &org, &*APP_NAME.read().unwrap()) {
|
|
||||||
let mut path = patch(project.config_dir().to_path_buf());
|
|
||||||
path.push(p);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
return "".into();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unreachable_code)]
|
|
||||||
pub fn log_path() -> PathBuf {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
if let Some(path) = dirs_next::home_dir().as_mut() {
|
|
||||||
path.push(format!("Library/Logs/{}", *APP_NAME.read().unwrap()));
|
|
||||||
return path.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
let mut path = Self::get_home();
|
|
||||||
path.push(format!(".local/share/logs/{}", *APP_NAME.read().unwrap()));
|
|
||||||
std::fs::create_dir_all(&path).ok();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
if let Some(path) = Self::path("").parent() {
|
|
||||||
let mut path: PathBuf = path.into();
|
|
||||||
path.push("log");
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
"".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ipc_path(postfix: &str) -> String {
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
// \\ServerName\pipe\PipeName
|
|
||||||
// where ServerName is either the name of a remote computer or a period, to specify the local computer.
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/ipc/pipe-names
|
|
||||||
format!(
|
|
||||||
"\\\\.\\pipe\\{}\\query{}",
|
|
||||||
*APP_NAME.read().unwrap(),
|
|
||||||
postfix
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
{
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
let mut path: PathBuf =
|
|
||||||
format!("{}/{}", *APP_DIR.read().unwrap(), *APP_NAME.read().unwrap()).into();
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
let mut path: PathBuf = format!("/tmp/{}", *APP_NAME.read().unwrap()).into();
|
|
||||||
fs::create_dir(&path).ok();
|
|
||||||
fs::set_permissions(&path, fs::Permissions::from_mode(0o0777)).ok();
|
|
||||||
path.push(format!("ipc{}", postfix));
|
|
||||||
path.to_str().unwrap_or("").to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn icon_path() -> PathBuf {
|
|
||||||
let mut path = Self::path("icons");
|
|
||||||
if fs::create_dir_all(&path).is_err() {
|
|
||||||
path = std::env::temp_dir();
|
|
||||||
}
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_any_listen_addr() -> SocketAddr {
|
|
||||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rendezvous_server() -> String {
|
|
||||||
let mut rendezvous_server = Self::get_option("custom-rendezvous-server");
|
|
||||||
if rendezvous_server.is_empty() {
|
|
||||||
rendezvous_server = PROD_RENDEZVOUS_SERVER.read().unwrap().clone();
|
|
||||||
}
|
|
||||||
if rendezvous_server.is_empty() {
|
|
||||||
rendezvous_server = CONFIG2.read().unwrap().rendezvous_server.clone();
|
|
||||||
}
|
|
||||||
if rendezvous_server.is_empty() {
|
|
||||||
rendezvous_server = Self::get_rendezvous_servers()
|
|
||||||
.drain(..)
|
|
||||||
.next()
|
|
||||||
.unwrap_or("".to_owned());
|
|
||||||
}
|
|
||||||
if !rendezvous_server.contains(":") {
|
|
||||||
rendezvous_server = format!("{}:{}", rendezvous_server, RENDEZVOUS_PORT);
|
|
||||||
}
|
|
||||||
rendezvous_server
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rendezvous_servers() -> Vec<String> {
|
|
||||||
let s = Self::get_option("custom-rendezvous-server");
|
|
||||||
if !s.is_empty() {
|
|
||||||
return vec![s];
|
|
||||||
}
|
|
||||||
let s = PROD_RENDEZVOUS_SERVER.read().unwrap().clone();
|
|
||||||
if !s.is_empty() {
|
|
||||||
return vec![s];
|
|
||||||
}
|
|
||||||
let serial_obsolute = CONFIG2.read().unwrap().serial > SERIAL;
|
|
||||||
if serial_obsolute {
|
|
||||||
let ss: Vec<String> = Self::get_option("rendezvous-servers")
|
|
||||||
.split(",")
|
|
||||||
.filter(|x| x.contains("."))
|
|
||||||
.map(|x| x.to_owned())
|
|
||||||
.collect();
|
|
||||||
if !ss.is_empty() {
|
|
||||||
return ss;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RENDEZVOUS_SERVERS.iter().map(|x| x.to_string()).collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_online() {
|
|
||||||
*ONLINE.lock().unwrap() = Default::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_latency(host: &str, latency: i64) {
|
|
||||||
ONLINE.lock().unwrap().insert(host.to_owned(), latency);
|
|
||||||
let mut host = "".to_owned();
|
|
||||||
let mut delay = i64::MAX;
|
|
||||||
for (tmp_host, tmp_delay) in ONLINE.lock().unwrap().iter() {
|
|
||||||
if tmp_delay > &0 && tmp_delay < &delay {
|
|
||||||
delay = tmp_delay.clone();
|
|
||||||
host = tmp_host.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !host.is_empty() {
|
|
||||||
let mut config = CONFIG2.write().unwrap();
|
|
||||||
if host != config.rendezvous_server {
|
|
||||||
log::debug!("Update rendezvous_server in config to {}", host);
|
|
||||||
log::debug!("{:?}", *ONLINE.lock().unwrap());
|
|
||||||
config.rendezvous_server = host;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_id(id: &str) {
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
if id == config.id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.id = id.into();
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_nat_type(nat_type: i32) {
|
|
||||||
let mut config = CONFIG2.write().unwrap();
|
|
||||||
if nat_type == config.nat_type {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.nat_type = nat_type;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_nat_type() -> i32 {
|
|
||||||
CONFIG2.read().unwrap().nat_type
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_serial(serial: i32) {
|
|
||||||
let mut config = CONFIG2.write().unwrap();
|
|
||||||
if serial == config.serial {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.serial = serial;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_serial() -> i32 {
|
|
||||||
std::cmp::max(CONFIG2.read().unwrap().serial, SERIAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_auto_id() -> Option<String> {
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
{
|
|
||||||
return Some(
|
|
||||||
rand::thread_rng()
|
|
||||||
.gen_range(1_000_000_000..2_000_000_000)
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut id = 0u32;
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
if let Ok(Some(ma)) = mac_address::get_mac_address() {
|
|
||||||
for x in &ma.bytes()[2..] {
|
|
||||||
id = (id << 8) | (*x as u32);
|
|
||||||
}
|
|
||||||
id = id & 0x1FFFFFFF;
|
|
||||||
Some(id.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_auto_password() -> String {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
(0..6)
|
|
||||||
.map(|_| CHARS[rng.gen::<usize>() % CHARS.len()])
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_key_confirmed() -> bool {
|
|
||||||
CONFIG.read().unwrap().key_confirmed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_key_confirmed(v: bool) {
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
if config.key_confirmed == v {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.key_confirmed = v;
|
|
||||||
if !v {
|
|
||||||
config.keys_confirmed = Default::default();
|
|
||||||
}
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_host_key_confirmed(host: &str) -> bool {
|
|
||||||
if let Some(true) = CONFIG.read().unwrap().keys_confirmed.get(host) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_host_key_confirmed(host: &str, v: bool) {
|
|
||||||
if Self::get_host_key_confirmed(host) == v {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
config.keys_confirmed.insert(host.to_owned(), v);
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_key_pair(pair: (Vec<u8>, Vec<u8>)) {
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
if config.key_pair == pair {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.key_pair = pair;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_key_pair() -> (Vec<u8>, Vec<u8>) {
|
|
||||||
// lock here to make sure no gen_keypair more than once
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
if config.key_pair.0.is_empty() {
|
|
||||||
let (pk, sk) = sign::gen_keypair();
|
|
||||||
config.key_pair = (sk.0.to_vec(), pk.0.into());
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
config.key_pair.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_id() -> String {
|
|
||||||
let mut id = CONFIG.read().unwrap().id.clone();
|
|
||||||
if id.is_empty() {
|
|
||||||
if let Some(tmp) = Config::get_auto_id() {
|
|
||||||
id = tmp;
|
|
||||||
Config::set_id(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_id_or(b: String) -> String {
|
|
||||||
let a = CONFIG.read().unwrap().id.clone();
|
|
||||||
if a.is_empty() {
|
|
||||||
b
|
|
||||||
} else {
|
|
||||||
a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_options() -> HashMap<String, String> {
|
|
||||||
CONFIG2.read().unwrap().options.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_options(v: HashMap<String, String>) {
|
|
||||||
let mut config = CONFIG2.write().unwrap();
|
|
||||||
if config.options == v {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.options = v;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_option(k: &str) -> String {
|
|
||||||
if let Some(v) = CONFIG2.read().unwrap().options.get(k) {
|
|
||||||
v.clone()
|
|
||||||
} else {
|
|
||||||
"".to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_option(k: String, v: String) {
|
|
||||||
let mut config = CONFIG2.write().unwrap();
|
|
||||||
let v2 = if v.is_empty() { None } else { Some(&v) };
|
|
||||||
if v2 != config.options.get(&k) {
|
|
||||||
if v2.is_none() {
|
|
||||||
config.options.remove(&k);
|
|
||||||
} else {
|
|
||||||
config.options.insert(k, v);
|
|
||||||
}
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_id() {
|
|
||||||
// to-do: how about if one ip register a lot of ids?
|
|
||||||
let id = Self::get_id();
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let new_id = rng.gen_range(1_000_000_000..2_000_000_000).to_string();
|
|
||||||
Config::set_id(&new_id);
|
|
||||||
log::info!("id updated from {} to {}", id, new_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_password(password: &str) {
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
if password == config.password {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.password = password.into();
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_password() -> String {
|
|
||||||
let mut password = CONFIG.read().unwrap().password.clone();
|
|
||||||
if password.is_empty() {
|
|
||||||
password = Config::get_auto_password();
|
|
||||||
Config::set_password(&password);
|
|
||||||
}
|
|
||||||
password
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_salt(salt: &str) {
|
|
||||||
let mut config = CONFIG.write().unwrap();
|
|
||||||
if salt == config.salt {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.salt = salt.into();
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_salt() -> String {
|
|
||||||
let mut salt = CONFIG.read().unwrap().salt.clone();
|
|
||||||
if salt.is_empty() {
|
|
||||||
salt = Config::get_auto_password();
|
|
||||||
Config::set_salt(&salt);
|
|
||||||
}
|
|
||||||
salt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_socks(socks: Option<Socks5Server>) {
|
|
||||||
let mut config = CONFIG2.write().unwrap();
|
|
||||||
if config.socks == socks {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.socks = socks;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_socks() -> Option<Socks5Server> {
|
|
||||||
CONFIG2.read().unwrap().socks.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_network_type() -> NetworkType {
|
|
||||||
match &CONFIG2.read().unwrap().socks {
|
|
||||||
None => NetworkType::Direct,
|
|
||||||
Some(_) => NetworkType::ProxySocks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get() -> Config {
|
|
||||||
return CONFIG.read().unwrap().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(cfg: Config) -> bool {
|
|
||||||
let mut lock = CONFIG.write().unwrap();
|
|
||||||
if *lock == cfg {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*lock = cfg;
|
|
||||||
lock.store();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_extension(path: PathBuf) -> PathBuf {
|
|
||||||
let ext = path.extension();
|
|
||||||
if let Some(ext) = ext {
|
|
||||||
let ext = format!("{}.toml", ext.to_string_lossy());
|
|
||||||
path.with_extension(&ext)
|
|
||||||
} else {
|
|
||||||
path.with_extension("toml")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PEERS: &str = "peers";
|
|
||||||
|
|
||||||
impl PeerConfig {
|
|
||||||
pub fn load(id: &str) -> PeerConfig {
|
|
||||||
let _unused = CONFIG.read().unwrap(); // for lock
|
|
||||||
match confy::load_path(&Self::path(id)) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to load config: {}", err);
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store(&self, id: &str) {
|
|
||||||
let _unused = CONFIG.read().unwrap(); // for lock
|
|
||||||
if let Err(err) = confy::store_path(Self::path(id), self) {
|
|
||||||
log::error!("Failed to store config: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(id: &str) {
|
|
||||||
fs::remove_file(&Self::path(id)).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(id: &str) -> PathBuf {
|
|
||||||
let path: PathBuf = [PEERS, id].iter().collect();
|
|
||||||
Config::with_extension(Config::path(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn peers() -> Vec<(String, SystemTime, PeerConfig)> {
|
|
||||||
if let Ok(peers) = Config::path(PEERS).read_dir() {
|
|
||||||
if let Ok(peers) = peers
|
|
||||||
.map(|res| res.map(|e| e.path()))
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
{
|
|
||||||
let mut peers: Vec<_> = peers
|
|
||||||
.iter()
|
|
||||||
.filter(|p| {
|
|
||||||
p.is_file()
|
|
||||||
&& p.extension().map(|p| p.to_str().unwrap_or("")) == Some("toml")
|
|
||||||
})
|
|
||||||
.map(|p| {
|
|
||||||
let t = crate::get_modified_time(&p);
|
|
||||||
let id = p
|
|
||||||
.file_stem()
|
|
||||||
.map(|p| p.to_str().unwrap_or(""))
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_owned();
|
|
||||||
let c = PeerConfig::load(&id);
|
|
||||||
if c.info.platform.is_empty() {
|
|
||||||
fs::remove_file(&p).ok();
|
|
||||||
}
|
|
||||||
(id, t, c)
|
|
||||||
})
|
|
||||||
.filter(|p| !p.2.info.platform.is_empty())
|
|
||||||
.collect();
|
|
||||||
peers.sort_unstable_by(|a, b| b.1.cmp(&a.1));
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct LocalConfig {
|
|
||||||
#[serde(default)]
|
|
||||||
remote_id: String, // latest used one
|
|
||||||
#[serde(default)]
|
|
||||||
size: Size,
|
|
||||||
#[serde(default)]
|
|
||||||
pub fav: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
options: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalConfig {
|
|
||||||
fn load() -> LocalConfig {
|
|
||||||
Config::load_::<LocalConfig>("_local")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn store(&self) {
|
|
||||||
Config::store_(self, "_local");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_size() -> Size {
|
|
||||||
LOCAL_CONFIG.read().unwrap().size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_size(x: i32, y: i32, w: i32, h: i32) {
|
|
||||||
let mut config = LOCAL_CONFIG.write().unwrap();
|
|
||||||
let size = (x, y, w, h);
|
|
||||||
if size == config.size || size.2 < 300 || size.3 < 300 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.size = size;
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_remote_id(remote_id: &str) {
|
|
||||||
let mut config = LOCAL_CONFIG.write().unwrap();
|
|
||||||
if remote_id == config.remote_id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
config.remote_id = remote_id.into();
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_remote_id() -> String {
|
|
||||||
LOCAL_CONFIG.read().unwrap().remote_id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_fav(fav: Vec<String>) {
|
|
||||||
let mut lock = LOCAL_CONFIG.write().unwrap();
|
|
||||||
if lock.fav == fav {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lock.fav = fav;
|
|
||||||
lock.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_fav() -> Vec<String> {
|
|
||||||
LOCAL_CONFIG.read().unwrap().fav.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_option(k: &str) -> String {
|
|
||||||
if let Some(v) = LOCAL_CONFIG.read().unwrap().options.get(k) {
|
|
||||||
v.clone()
|
|
||||||
} else {
|
|
||||||
"".to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_option(k: String, v: String) {
|
|
||||||
let mut config = LOCAL_CONFIG.write().unwrap();
|
|
||||||
let v2 = if v.is_empty() { None } else { Some(&v) };
|
|
||||||
if v2 != config.options.get(&k) {
|
|
||||||
if v2.is_none() {
|
|
||||||
config.options.remove(&k);
|
|
||||||
} else {
|
|
||||||
config.options.insert(k, v);
|
|
||||||
}
|
|
||||||
config.store();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct LanPeers {
|
|
||||||
#[serde(default)]
|
|
||||||
pub peers: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LanPeers {
|
|
||||||
pub fn load() -> LanPeers {
|
|
||||||
let _unused = CONFIG.read().unwrap(); // for lock
|
|
||||||
match confy::load_path(&Config::file_("_lan_peers")) {
|
|
||||||
Ok(peers) => peers,
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to load lan peers: {}", err);
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store(peers: String) {
|
|
||||||
let f = LanPeers { peers };
|
|
||||||
if let Err(err) = confy::store_path(Config::file_("_lan_peers"), f) {
|
|
||||||
log::error!("Failed to store lan peers: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modify_time() -> crate::ResultType<u64> {
|
|
||||||
let p = Config::file_("_lan_peers");
|
|
||||||
Ok(fs::metadata(p)?
|
|
||||||
.modified()?
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)?
|
|
||||||
.as_millis() as _)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_serialize() {
|
|
||||||
let cfg: Config = Default::default();
|
|
||||||
let res = toml::to_string_pretty(&cfg);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let cfg: PeerConfig = Default::default();
|
|
||||||
let res = toml::to_string_pretty(&cfg);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,568 +0,0 @@
|
|||||||
use crate::{bail, message_proto::*, ResultType};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html
|
|
||||||
use crate::{
|
|
||||||
compress::{compress, decompress},
|
|
||||||
config::{Config, COMPRESS_LEVEL},
|
|
||||||
};
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::os::windows::prelude::*;
|
|
||||||
use tokio::{fs::File, io::*};
|
|
||||||
|
|
||||||
pub fn read_dir(path: &PathBuf, include_hidden: bool) -> ResultType<FileDirectory> {
|
|
||||||
let mut dir = FileDirectory {
|
|
||||||
path: get_string(&path),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
#[cfg(windows)]
|
|
||||||
if "/" == &get_string(&path) {
|
|
||||||
let drives = unsafe { winapi::um::fileapi::GetLogicalDrives() };
|
|
||||||
for i in 0..32 {
|
|
||||||
if drives & (1 << i) != 0 {
|
|
||||||
let name = format!(
|
|
||||||
"{}:",
|
|
||||||
std::char::from_u32('A' as u32 + i as u32).unwrap_or('A')
|
|
||||||
);
|
|
||||||
dir.entries.push(FileEntry {
|
|
||||||
name,
|
|
||||||
entry_type: FileType::DirDrive.into(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(dir);
|
|
||||||
}
|
|
||||||
for entry in path.read_dir()? {
|
|
||||||
if let Ok(entry) = entry {
|
|
||||||
let p = entry.path();
|
|
||||||
let name = p
|
|
||||||
.file_name()
|
|
||||||
.map(|p| p.to_str().unwrap_or(""))
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_owned();
|
|
||||||
if name.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut is_hidden = false;
|
|
||||||
let meta;
|
|
||||||
if let Ok(tmp) = std::fs::symlink_metadata(&p) {
|
|
||||||
meta = tmp;
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
|
||||||
#[cfg(windows)]
|
|
||||||
if meta.file_attributes() & 0x2 != 0 {
|
|
||||||
is_hidden = true;
|
|
||||||
}
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
if name.find('.').unwrap_or(usize::MAX) == 0 {
|
|
||||||
is_hidden = true;
|
|
||||||
}
|
|
||||||
if is_hidden && !include_hidden {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let (entry_type, size) = {
|
|
||||||
if p.is_dir() {
|
|
||||||
if meta.file_type().is_symlink() {
|
|
||||||
(FileType::DirLink.into(), 0)
|
|
||||||
} else {
|
|
||||||
(FileType::Dir.into(), 0)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if meta.file_type().is_symlink() {
|
|
||||||
(FileType::FileLink.into(), 0)
|
|
||||||
} else {
|
|
||||||
(FileType::File.into(), meta.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let modified_time = meta
|
|
||||||
.modified()
|
|
||||||
.map(|x| {
|
|
||||||
x.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
|
||||||
.map(|x| x.as_secs())
|
|
||||||
.unwrap_or(0)
|
|
||||||
})
|
|
||||||
.unwrap_or(0) as u64;
|
|
||||||
dir.entries.push(FileEntry {
|
|
||||||
name: get_file_name(&p),
|
|
||||||
entry_type,
|
|
||||||
is_hidden,
|
|
||||||
size,
|
|
||||||
modified_time,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_file_name(p: &PathBuf) -> String {
|
|
||||||
p.file_name()
|
|
||||||
.map(|p| p.to_str().unwrap_or(""))
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_string(path: &PathBuf) -> String {
|
|
||||||
path.to_str().unwrap_or("").to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_path(path: &str) -> PathBuf {
|
|
||||||
Path::new(path).to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_home_as_string() -> String {
|
|
||||||
get_string(&Config::get_home())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_dir_recursive(
|
|
||||||
path: &PathBuf,
|
|
||||||
prefix: &PathBuf,
|
|
||||||
include_hidden: bool,
|
|
||||||
) -> ResultType<Vec<FileEntry>> {
|
|
||||||
let mut files = Vec::new();
|
|
||||||
if path.is_dir() {
|
|
||||||
// to-do: symbol link handling, cp the link rather than the content
|
|
||||||
// to-do: file mode, for unix
|
|
||||||
let fd = read_dir(&path, include_hidden)?;
|
|
||||||
for entry in fd.entries.iter() {
|
|
||||||
match entry.entry_type.enum_value() {
|
|
||||||
Ok(FileType::File) => {
|
|
||||||
let mut entry = entry.clone();
|
|
||||||
entry.name = get_string(&prefix.join(entry.name));
|
|
||||||
files.push(entry);
|
|
||||||
}
|
|
||||||
Ok(FileType::Dir) => {
|
|
||||||
if let Ok(mut tmp) = read_dir_recursive(
|
|
||||||
&path.join(&entry.name),
|
|
||||||
&prefix.join(&entry.name),
|
|
||||||
include_hidden,
|
|
||||||
) {
|
|
||||||
for entry in tmp.drain(0..) {
|
|
||||||
files.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(files)
|
|
||||||
} else if path.is_file() {
|
|
||||||
let (size, modified_time) = if let Ok(meta) = std::fs::metadata(&path) {
|
|
||||||
(
|
|
||||||
meta.len(),
|
|
||||||
meta.modified()
|
|
||||||
.map(|x| {
|
|
||||||
x.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
|
||||||
.map(|x| x.as_secs())
|
|
||||||
.unwrap_or(0)
|
|
||||||
})
|
|
||||||
.unwrap_or(0) as u64,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(0, 0)
|
|
||||||
};
|
|
||||||
files.push(FileEntry {
|
|
||||||
entry_type: FileType::File.into(),
|
|
||||||
size,
|
|
||||||
modified_time,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
Ok(files)
|
|
||||||
} else {
|
|
||||||
bail!("Not exists");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_recursive_files(path: &str, include_hidden: bool) -> ResultType<Vec<FileEntry>> {
|
|
||||||
read_dir_recursive(&get_path(path), &get_path(""), include_hidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct TransferJob {
|
|
||||||
id: i32,
|
|
||||||
path: PathBuf,
|
|
||||||
files: Vec<FileEntry>,
|
|
||||||
file_num: i32,
|
|
||||||
file: Option<File>,
|
|
||||||
total_size: u64,
|
|
||||||
finished_size: u64,
|
|
||||||
transferred: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_ext(name: &str) -> &str {
|
|
||||||
if let Some(i) = name.rfind(".") {
|
|
||||||
return &name[i + 1..];
|
|
||||||
}
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_compressed_file(name: &str) -> bool {
|
|
||||||
let ext = get_ext(name);
|
|
||||||
ext == "xz"
|
|
||||||
|| ext == "gz"
|
|
||||||
|| ext == "zip"
|
|
||||||
|| ext == "7z"
|
|
||||||
|| ext == "rar"
|
|
||||||
|| ext == "bz2"
|
|
||||||
|| ext == "tgz"
|
|
||||||
|| ext == "png"
|
|
||||||
|| ext == "jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransferJob {
|
|
||||||
pub fn new_write(id: i32, path: String, files: Vec<FileEntry>) -> Self {
|
|
||||||
let total_size = files.iter().map(|x| x.size as u64).sum();
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
path: get_path(&path),
|
|
||||||
files,
|
|
||||||
total_size,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_read(id: i32, path: String, include_hidden: bool) -> ResultType<Self> {
|
|
||||||
let files = get_recursive_files(&path, include_hidden)?;
|
|
||||||
let total_size = files.iter().map(|x| x.size as u64).sum();
|
|
||||||
Ok(Self {
|
|
||||||
id,
|
|
||||||
path: get_path(&path),
|
|
||||||
files,
|
|
||||||
total_size,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn files(&self) -> &Vec<FileEntry> {
|
|
||||||
&self.files
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_files(&mut self, files: Vec<FileEntry>) {
|
|
||||||
self.files = files;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> i32 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn total_size(&self) -> u64 {
|
|
||||||
self.total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn finished_size(&self) -> u64 {
|
|
||||||
self.finished_size
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn transferred(&self) -> u64 {
|
|
||||||
self.transferred
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn file_num(&self) -> i32 {
|
|
||||||
self.file_num
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modify_time(&self) {
|
|
||||||
let file_num = self.file_num as usize;
|
|
||||||
if file_num < self.files.len() {
|
|
||||||
let entry = &self.files[file_num];
|
|
||||||
let path = self.join(&entry.name);
|
|
||||||
let download_path = format!("{}.download", get_string(&path));
|
|
||||||
std::fs::rename(&download_path, &path).ok();
|
|
||||||
filetime::set_file_mtime(
|
|
||||||
&path,
|
|
||||||
filetime::FileTime::from_unix_time(entry.modified_time as _, 0),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_download_file(&self) {
|
|
||||||
let file_num = self.file_num as usize;
|
|
||||||
if file_num < self.files.len() {
|
|
||||||
let entry = &self.files[file_num];
|
|
||||||
let path = self.join(&entry.name);
|
|
||||||
let download_path = format!("{}.download", get_string(&path));
|
|
||||||
std::fs::remove_file(&download_path).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn write(&mut self, block: FileTransferBlock, raw: Option<&[u8]>) -> ResultType<()> {
|
|
||||||
if block.id != self.id {
|
|
||||||
bail!("Wrong id");
|
|
||||||
}
|
|
||||||
let file_num = block.file_num as usize;
|
|
||||||
if file_num >= self.files.len() {
|
|
||||||
bail!("Wrong file number");
|
|
||||||
}
|
|
||||||
if file_num != self.file_num as usize || self.file.is_none() {
|
|
||||||
self.modify_time();
|
|
||||||
if let Some(file) = self.file.as_mut() {
|
|
||||||
file.sync_all().await?;
|
|
||||||
}
|
|
||||||
self.file_num = block.file_num;
|
|
||||||
let entry = &self.files[file_num];
|
|
||||||
let path = self.join(&entry.name);
|
|
||||||
if let Some(p) = path.parent() {
|
|
||||||
std::fs::create_dir_all(p).ok();
|
|
||||||
}
|
|
||||||
let path = format!("{}.download", get_string(&path));
|
|
||||||
self.file = Some(File::create(&path).await?);
|
|
||||||
}
|
|
||||||
let data = if let Some(data) = raw {
|
|
||||||
data
|
|
||||||
} else {
|
|
||||||
&block.data
|
|
||||||
};
|
|
||||||
if block.compressed {
|
|
||||||
let tmp = decompress(data);
|
|
||||||
self.file.as_mut().unwrap().write_all(&tmp).await?;
|
|
||||||
self.finished_size += tmp.len() as u64;
|
|
||||||
} else {
|
|
||||||
self.file.as_mut().unwrap().write_all(data).await?;
|
|
||||||
self.finished_size += data.len() as u64;
|
|
||||||
}
|
|
||||||
self.transferred += data.len() as u64;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn join(&self, name: &str) -> PathBuf {
|
|
||||||
if name.is_empty() {
|
|
||||||
self.path.clone()
|
|
||||||
} else {
|
|
||||||
self.path.join(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read(&mut self) -> ResultType<Option<FileTransferBlock>> {
|
|
||||||
let file_num = self.file_num as usize;
|
|
||||||
if file_num >= self.files.len() {
|
|
||||||
self.file.take();
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let name = &self.files[file_num].name;
|
|
||||||
if self.file.is_none() {
|
|
||||||
match File::open(self.join(&name)).await {
|
|
||||||
Ok(file) => {
|
|
||||||
self.file = Some(file);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
self.file_num += 1;
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const BUF_SIZE: usize = 128 * 1024;
|
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(BUF_SIZE);
|
|
||||||
unsafe {
|
|
||||||
buf.set_len(BUF_SIZE);
|
|
||||||
}
|
|
||||||
let mut compressed = false;
|
|
||||||
let mut offset: usize = 0;
|
|
||||||
loop {
|
|
||||||
match self.file.as_mut().unwrap().read(&mut buf[offset..]).await {
|
|
||||||
Err(err) => {
|
|
||||||
self.file_num += 1;
|
|
||||||
self.file = None;
|
|
||||||
return Err(err.into());
|
|
||||||
}
|
|
||||||
Ok(n) => {
|
|
||||||
offset += n;
|
|
||||||
if n == 0 || offset == BUF_SIZE {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe { buf.set_len(offset) };
|
|
||||||
if offset == 0 {
|
|
||||||
self.file_num += 1;
|
|
||||||
self.file = None;
|
|
||||||
} else {
|
|
||||||
self.finished_size += offset as u64;
|
|
||||||
if !is_compressed_file(name) {
|
|
||||||
let tmp = compress(&buf, COMPRESS_LEVEL);
|
|
||||||
if tmp.len() < buf.len() {
|
|
||||||
buf = tmp;
|
|
||||||
compressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.transferred += buf.len() as u64;
|
|
||||||
}
|
|
||||||
Ok(Some(FileTransferBlock {
|
|
||||||
id: self.id,
|
|
||||||
file_num: file_num as _,
|
|
||||||
data: buf.into(),
|
|
||||||
compressed,
|
|
||||||
..Default::default()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_error<T: std::string::ToString>(id: i32, err: T, file_num: i32) -> Message {
|
|
||||||
let mut resp = FileResponse::new();
|
|
||||||
resp.set_error(FileTransferError {
|
|
||||||
id,
|
|
||||||
error: err.to_string(),
|
|
||||||
file_num,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_file_response(resp);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_dir(id: i32, path: String, files: Vec<FileEntry>) -> Message {
|
|
||||||
let mut resp = FileResponse::new();
|
|
||||||
resp.set_dir(FileDirectory {
|
|
||||||
id,
|
|
||||||
path,
|
|
||||||
entries: files.into(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_file_response(resp);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_block(block: FileTransferBlock) -> Message {
|
|
||||||
let mut resp = FileResponse::new();
|
|
||||||
resp.set_block(block);
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_file_response(resp);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_receive(id: i32, path: String, files: Vec<FileEntry>) -> Message {
|
|
||||||
let mut action = FileAction::new();
|
|
||||||
action.set_receive(FileTransferReceiveRequest {
|
|
||||||
id,
|
|
||||||
path,
|
|
||||||
files: files.into(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_file_action(action);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_send(id: i32, path: String, include_hidden: bool) -> Message {
|
|
||||||
let mut action = FileAction::new();
|
|
||||||
action.set_send(FileTransferSendRequest {
|
|
||||||
id,
|
|
||||||
path,
|
|
||||||
include_hidden,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_file_action(action);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new_done(id: i32, file_num: i32) -> Message {
|
|
||||||
let mut resp = FileResponse::new();
|
|
||||||
resp.set_done(FileTransferDone {
|
|
||||||
id,
|
|
||||||
file_num,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
msg_out.set_file_response(resp);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn remove_job(id: i32, jobs: &mut Vec<TransferJob>) {
|
|
||||||
*jobs = jobs.drain(0..).filter(|x| x.id() != id).collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_job(id: i32, jobs: &mut Vec<TransferJob>) -> Option<&mut TransferJob> {
|
|
||||||
jobs.iter_mut().filter(|x| x.id() == id).next()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_read_jobs(
|
|
||||||
jobs: &mut Vec<TransferJob>,
|
|
||||||
stream: &mut crate::Stream,
|
|
||||||
) -> ResultType<()> {
|
|
||||||
let mut finished = Vec::new();
|
|
||||||
for job in jobs.iter_mut() {
|
|
||||||
match job.read().await {
|
|
||||||
Err(err) => {
|
|
||||||
stream
|
|
||||||
.send(&new_error(job.id(), err, job.file_num()))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(Some(block)) => {
|
|
||||||
stream.send(&new_block(block)).await?;
|
|
||||||
}
|
|
||||||
Ok(None) => {
|
|
||||||
finished.push(job.id());
|
|
||||||
stream.send(&new_done(job.id(), job.file_num())).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for id in finished {
|
|
||||||
remove_job(id, jobs);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_all_empty_dir(path: &PathBuf) -> ResultType<()> {
|
|
||||||
let fd = read_dir(path, true)?;
|
|
||||||
for entry in fd.entries.iter() {
|
|
||||||
match entry.entry_type.enum_value() {
|
|
||||||
Ok(FileType::Dir) => {
|
|
||||||
remove_all_empty_dir(&path.join(&entry.name)).ok();
|
|
||||||
}
|
|
||||||
Ok(FileType::DirLink) | Ok(FileType::FileLink) => {
|
|
||||||
std::fs::remove_file(&path.join(&entry.name)).ok();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::fs::remove_dir(path).ok();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn remove_file(file: &str) -> ResultType<()> {
|
|
||||||
std::fs::remove_file(get_path(file))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn create_dir(dir: &str) -> ResultType<()> {
|
|
||||||
std::fs::create_dir_all(get_path(dir))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn transform_windows_path(entries: &mut Vec<FileEntry>) {
|
|
||||||
for entry in entries {
|
|
||||||
entry.name = entry.name.replace("\\", "/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
pub mod compress;
|
|
||||||
#[path = "./protos/message.rs"]
|
|
||||||
pub mod message_proto;
|
|
||||||
#[path = "./protos/rendezvous.rs"]
|
|
||||||
pub mod rendezvous_proto;
|
|
||||||
pub use bytes;
|
|
||||||
pub use futures;
|
|
||||||
pub use protobuf;
|
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{self, BufRead},
|
|
||||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
|
||||||
path::Path,
|
|
||||||
time::{self, SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
pub use tokio;
|
|
||||||
pub use tokio_util;
|
|
||||||
pub mod socket_client;
|
|
||||||
pub mod tcp;
|
|
||||||
pub mod udp;
|
|
||||||
pub use env_logger;
|
|
||||||
pub use log;
|
|
||||||
pub mod bytes_codec;
|
|
||||||
#[cfg(feature = "quic")]
|
|
||||||
pub mod quic;
|
|
||||||
pub use anyhow::{self, bail};
|
|
||||||
pub use futures_util;
|
|
||||||
pub mod config;
|
|
||||||
pub mod fs;
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
pub use mac_address;
|
|
||||||
pub use rand;
|
|
||||||
pub use regex;
|
|
||||||
pub use sodiumoxide;
|
|
||||||
pub use tokio_socks;
|
|
||||||
pub use tokio_socks::IntoTargetAddr;
|
|
||||||
pub use tokio_socks::TargetAddr;
|
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
|
||||||
pub type Stream = quic::Connection;
|
|
||||||
#[cfg(not(feature = "quic"))]
|
|
||||||
pub type Stream = tcp::FramedStream;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn sleep(sec: f32) {
|
|
||||||
tokio::time::sleep(time::Duration::from_secs_f32(sec)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! allow_err {
|
|
||||||
($e:expr) => {
|
|
||||||
if let Err(err) = $e {
|
|
||||||
log::debug!(
|
|
||||||
"{:?}, {}:{}:{}:{}",
|
|
||||||
err,
|
|
||||||
module_path!(),
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn timeout<T: std::future::Future>(ms: u64, future: T) -> tokio::time::Timeout<T> {
|
|
||||||
tokio::time::timeout(std::time::Duration::from_millis(ms), future)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ResultType<F, E = anyhow::Error> = anyhow::Result<F, E>;
|
|
||||||
|
|
||||||
/// Certain router and firewalls scan the packet and if they
|
|
||||||
/// find an IP address belonging to their pool that they use to do the NAT mapping/translation, so here we mangle the ip address
|
|
||||||
|
|
||||||
pub struct AddrMangle();
|
|
||||||
|
|
||||||
impl AddrMangle {
|
|
||||||
pub fn encode(addr: SocketAddr) -> Vec<u8> {
|
|
||||||
match addr {
|
|
||||||
SocketAddr::V4(addr_v4) => {
|
|
||||||
let tm = (SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_micros() as u32) as u128;
|
|
||||||
let ip = u32::from_le_bytes(addr_v4.ip().octets()) as u128;
|
|
||||||
let port = addr.port() as u128;
|
|
||||||
let v = ((ip + tm) << 49) | (tm << 17) | (port + (tm & 0xFFFF));
|
|
||||||
let bytes = v.to_le_bytes();
|
|
||||||
let mut n_padding = 0;
|
|
||||||
for i in bytes.iter().rev() {
|
|
||||||
if i == &0u8 {
|
|
||||||
n_padding += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bytes[..(16 - n_padding)].to_vec()
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Only support ipv4");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode(bytes: &[u8]) -> SocketAddr {
|
|
||||||
let mut padded = [0u8; 16];
|
|
||||||
padded[..bytes.len()].copy_from_slice(&bytes);
|
|
||||||
let number = u128::from_le_bytes(padded);
|
|
||||||
let tm = (number >> 17) & (u32::max_value() as u128);
|
|
||||||
let ip = (((number >> 49) - tm) as u32).to_le_bytes();
|
|
||||||
let port = (number & 0xFFFFFF) - (tm & 0xFFFF);
|
|
||||||
SocketAddr::V4(SocketAddrV4::new(
|
|
||||||
Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]),
|
|
||||||
port as u16,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_version_from_url(url: &str) -> String {
|
|
||||||
let n = url.chars().count();
|
|
||||||
let a = url
|
|
||||||
.chars()
|
|
||||||
.rev()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, x)| x == &'-')
|
|
||||||
.next()
|
|
||||||
.map(|(i, _)| i);
|
|
||||||
if let Some(a) = a {
|
|
||||||
let b = url
|
|
||||||
.chars()
|
|
||||||
.rev()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, x)| x == &'.')
|
|
||||||
.next()
|
|
||||||
.map(|(i, _)| i);
|
|
||||||
if let Some(b) = b {
|
|
||||||
if a > b {
|
|
||||||
if url
|
|
||||||
.chars()
|
|
||||||
.skip(n - b)
|
|
||||||
.collect::<String>()
|
|
||||||
.parse::<i32>()
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
return url.chars().skip(n - a).collect();
|
|
||||||
} else {
|
|
||||||
return url.chars().skip(n - a).take(a - b - 1).collect();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return url.chars().skip(n - a).collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"".to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gen_version() {
|
|
||||||
let mut file = File::create("./src/version.rs").unwrap();
|
|
||||||
for line in read_lines("Cargo.toml").unwrap() {
|
|
||||||
if let Ok(line) = line {
|
|
||||||
let ab: Vec<&str> = line.split("=").map(|x| x.trim()).collect();
|
|
||||||
if ab.len() == 2 && ab[0] == "version" {
|
|
||||||
use std::io::prelude::*;
|
|
||||||
file.write_all(format!("pub const VERSION: &str = {};", ab[1]).as_bytes())
|
|
||||||
.ok();
|
|
||||||
file.sync_all().ok();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let file = File::open(filename)?;
|
|
||||||
Ok(io::BufReader::new(file).lines())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_valid_custom_id(id: &str) -> bool {
|
|
||||||
regex::Regex::new(r"^[a-zA-Z]\w{5,15}$")
|
|
||||||
.unwrap()
|
|
||||||
.is_match(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_version_number(v: &str) -> i64 {
|
|
||||||
let mut n = 0;
|
|
||||||
for x in v.split(".") {
|
|
||||||
n = n * 1000 + x.parse::<i64>().unwrap_or(0);
|
|
||||||
}
|
|
||||||
n
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_modified_time(path: &std::path::Path) -> SystemTime {
|
|
||||||
std::fs::metadata(&path)
|
|
||||||
.map(|m| m.modified().unwrap_or(UNIX_EPOCH))
|
|
||||||
.unwrap_or(UNIX_EPOCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_mangle() {
|
|
||||||
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 16, 32), 21116));
|
|
||||||
assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
use crate::{allow_err, anyhow::anyhow, ResultType};
|
|
||||||
use protobuf::Message;
|
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
|
||||||
use tokio::{self, stream::StreamExt, sync::mpsc};
|
|
||||||
|
|
||||||
const QUIC_HBB: &[&[u8]] = &[b"hbb"];
|
|
||||||
const SERVER_NAME: &str = "hbb";
|
|
||||||
|
|
||||||
type Sender = mpsc::UnboundedSender<Value>;
|
|
||||||
type Receiver = mpsc::UnboundedReceiver<Value>;
|
|
||||||
|
|
||||||
pub fn new_server(socket: std::net::UdpSocket) -> ResultType<(Server, SocketAddr)> {
|
|
||||||
let mut transport_config = quinn::TransportConfig::default();
|
|
||||||
transport_config.stream_window_uni(0);
|
|
||||||
let mut server_config = quinn::ServerConfig::default();
|
|
||||||
server_config.transport = Arc::new(transport_config);
|
|
||||||
let mut server_config = quinn::ServerConfigBuilder::new(server_config);
|
|
||||||
server_config.protocols(QUIC_HBB);
|
|
||||||
// server_config.enable_keylog();
|
|
||||||
// server_config.use_stateless_retry(true);
|
|
||||||
let mut endpoint = quinn::Endpoint::builder();
|
|
||||||
endpoint.listen(server_config.build());
|
|
||||||
let (end, incoming) = endpoint.with_socket(socket)?;
|
|
||||||
Ok((Server { incoming }, end.local_addr()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_client(local_addr: &SocketAddr, peer: &SocketAddr) -> ResultType<Connection> {
|
|
||||||
let mut endpoint = quinn::Endpoint::builder();
|
|
||||||
let mut client_config = quinn::ClientConfigBuilder::default();
|
|
||||||
client_config.protocols(QUIC_HBB);
|
|
||||||
//client_config.enable_keylog();
|
|
||||||
endpoint.default_client_config(client_config.build());
|
|
||||||
let (endpoint, _) = endpoint.bind(local_addr)?;
|
|
||||||
let new_conn = endpoint.connect(peer, SERVER_NAME)?.await?;
|
|
||||||
Connection::new_for_client(new_conn.connection).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Server {
|
|
||||||
incoming: quinn::Incoming,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Server {
|
|
||||||
#[inline]
|
|
||||||
pub async fn next(&mut self) -> ResultType<Option<Connection>> {
|
|
||||||
Connection::new_for_server(&mut self.incoming).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Connection {
|
|
||||||
conn: quinn::Connection,
|
|
||||||
tx: quinn::SendStream,
|
|
||||||
rx: Receiver,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Value = ResultType<Vec<u8>>;
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
async fn new_for_server(incoming: &mut quinn::Incoming) -> ResultType<Option<Self>> {
|
|
||||||
if let Some(conn) = incoming.next().await {
|
|
||||||
let quinn::NewConnection {
|
|
||||||
connection: conn,
|
|
||||||
// uni_streams,
|
|
||||||
mut bi_streams,
|
|
||||||
..
|
|
||||||
} = conn.await?;
|
|
||||||
let (tx, rx) = mpsc::unbounded_channel::<Value>();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
loop {
|
|
||||||
let stream = bi_streams.next().await;
|
|
||||||
if let Some(stream) = stream {
|
|
||||||
let stream = match stream {
|
|
||||||
Err(e) => {
|
|
||||||
tx.send(Err(e.into())).ok();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(s) => s,
|
|
||||||
};
|
|
||||||
let cloned = tx.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
allow_err!(handle_request(stream.1, cloned).await);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tx.send(Err(anyhow!("Reset by the peer"))).ok();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log::info!("Exit connection outer loop");
|
|
||||||
});
|
|
||||||
let tx = conn.open_uni().await?;
|
|
||||||
Ok(Some(Self { conn, tx, rx }))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn new_for_client(conn: quinn::Connection) -> ResultType<Self> {
|
|
||||||
let (tx, rx_quic) = conn.open_bi().await?;
|
|
||||||
let (tx_mpsc, rx) = mpsc::unbounded_channel::<Value>();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
allow_err!(handle_request(rx_quic, tx_mpsc).await);
|
|
||||||
});
|
|
||||||
Ok(Self { conn, tx, rx })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn next(&mut self) -> Option<Value> {
|
|
||||||
// None is returned when all Sender halves have dropped,
|
|
||||||
// indicating that no further values can be sent on the channel.
|
|
||||||
self.rx.recv().await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn remote_address(&self) -> SocketAddr {
|
|
||||||
self.conn.remote_address()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn send_raw(&mut self, bytes: &[u8]) -> ResultType<()> {
|
|
||||||
self.tx.write_all(bytes).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn send(&mut self, msg: &dyn Message) -> ResultType<()> {
|
|
||||||
match msg.write_to_bytes() {
|
|
||||||
Ok(bytes) => self.send_raw(&bytes).await?,
|
|
||||||
err => allow_err!(err),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_request(rx: quinn::RecvStream, tx: Sender) -> ResultType<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
config::{Config, NetworkType},
|
|
||||||
tcp::FramedStream,
|
|
||||||
udp::FramedSocket,
|
|
||||||
ResultType,
|
|
||||||
};
|
|
||||||
use anyhow::Context;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use tokio::net::ToSocketAddrs;
|
|
||||||
use tokio_socks::{IntoTargetAddr, TargetAddr};
|
|
||||||
|
|
||||||
fn to_socket_addr(host: &str) -> ResultType<SocketAddr> {
|
|
||||||
use std::net::ToSocketAddrs;
|
|
||||||
host.to_socket_addrs()?
|
|
||||||
.filter(|x| x.is_ipv4())
|
|
||||||
.next()
|
|
||||||
.context("Failed to solve")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_target_addr(host: &str) -> ResultType<TargetAddr<'static>> {
|
|
||||||
let addr = match Config::get_network_type() {
|
|
||||||
NetworkType::Direct => to_socket_addr(&host)?.into_target_addr()?,
|
|
||||||
NetworkType::ProxySocks => host.into_target_addr()?,
|
|
||||||
}
|
|
||||||
.to_owned();
|
|
||||||
Ok(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_if_valid_server(host: &str) -> String {
|
|
||||||
let mut host = host.to_owned();
|
|
||||||
if !host.contains(":") {
|
|
||||||
host = format!("{}:{}", host, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
match Config::get_network_type() {
|
|
||||||
NetworkType::Direct => match to_socket_addr(&host) {
|
|
||||||
Err(err) => err.to_string(),
|
|
||||||
Ok(_) => "".to_owned(),
|
|
||||||
},
|
|
||||||
NetworkType::ProxySocks => match &host.into_target_addr() {
|
|
||||||
Err(err) => err.to_string(),
|
|
||||||
Ok(_) => "".to_owned(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_tcp<'t, T: IntoTargetAddr<'t>>(
|
|
||||||
target: T,
|
|
||||||
local: SocketAddr,
|
|
||||||
ms_timeout: u64,
|
|
||||||
) -> ResultType<FramedStream> {
|
|
||||||
let target_addr = target.into_target_addr()?;
|
|
||||||
|
|
||||||
if let Some(conf) = Config::get_socks() {
|
|
||||||
FramedStream::connect(
|
|
||||||
conf.proxy.as_str(),
|
|
||||||
target_addr,
|
|
||||||
local,
|
|
||||||
conf.username.as_str(),
|
|
||||||
conf.password.as_str(),
|
|
||||||
ms_timeout,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
let addr = std::net::ToSocketAddrs::to_socket_addrs(&target_addr)?
|
|
||||||
.filter(|x| x.is_ipv4())
|
|
||||||
.next()
|
|
||||||
.context("Invalid target addr, no valid ipv4 address can be resolved.")?;
|
|
||||||
Ok(FramedStream::new(addr, local, ms_timeout).await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_udp<T: ToSocketAddrs>(local: T, ms_timeout: u64) -> ResultType<FramedSocket> {
|
|
||||||
match Config::get_socks() {
|
|
||||||
None => Ok(FramedSocket::new(local).await?),
|
|
||||||
Some(conf) => {
|
|
||||||
let socket = FramedSocket::new_proxy(
|
|
||||||
conf.proxy.as_str(),
|
|
||||||
local,
|
|
||||||
conf.username.as_str(),
|
|
||||||
conf.password.as_str(),
|
|
||||||
ms_timeout,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(socket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn rebind_udp<T: ToSocketAddrs>(local: T) -> ResultType<Option<FramedSocket>> {
|
|
||||||
match Config::get_network_type() {
|
|
||||||
NetworkType::Direct => Ok(Some(FramedSocket::new(local).await?)),
|
|
||||||
_ => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
use crate::{bail, bytes_codec::BytesCodec, ResultType};
|
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
|
||||||
use futures::{SinkExt, StreamExt};
|
|
||||||
use protobuf::Message;
|
|
||||||
use sodiumoxide::crypto::secretbox::{self, Key, Nonce};
|
|
||||||
use std::{
|
|
||||||
io::{self, Error, ErrorKind},
|
|
||||||
net::SocketAddr,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
use tokio::{
|
|
||||||
io::{AsyncRead, AsyncWrite, ReadBuf},
|
|
||||||
net::{lookup_host, TcpListener, TcpSocket, ToSocketAddrs},
|
|
||||||
};
|
|
||||||
use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr, ToProxyAddrs};
|
|
||||||
use tokio_util::codec::Framed;
|
|
||||||
|
|
||||||
pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {}
|
|
||||||
pub struct DynTcpStream(Box<dyn TcpStreamTrait + Send + Sync>);
|
|
||||||
|
|
||||||
pub struct FramedStream(
|
|
||||||
Framed<DynTcpStream, BytesCodec>,
|
|
||||||
SocketAddr,
|
|
||||||
Option<(Key, u64, u64)>,
|
|
||||||
u64,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Deref for FramedStream {
|
|
||||||
type Target = Framed<DynTcpStream, BytesCodec>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for FramedStream {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for DynTcpStream {
|
|
||||||
type Target = Box<dyn TcpStreamTrait + Send + Sync>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for DynTcpStream {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std::io::Error> {
|
|
||||||
let socket = match addr {
|
|
||||||
std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
|
||||||
std::net::SocketAddr::V6(..) => TcpSocket::new_v6()?,
|
|
||||||
};
|
|
||||||
if reuse {
|
|
||||||
// windows has no reuse_port, but it's reuse_address
|
|
||||||
// almost equals to unix's reuse_port + reuse_address,
|
|
||||||
// though may introduce nondeterministic behavior
|
|
||||||
#[cfg(unix)]
|
|
||||||
socket.set_reuseport(true)?;
|
|
||||||
socket.set_reuseaddr(true)?;
|
|
||||||
}
|
|
||||||
socket.bind(addr)?;
|
|
||||||
Ok(socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FramedStream {
|
|
||||||
pub async fn new<T1: ToSocketAddrs, T2: ToSocketAddrs>(
|
|
||||||
remote_addr: T1,
|
|
||||||
local_addr: T2,
|
|
||||||
ms_timeout: u64,
|
|
||||||
) -> ResultType<Self> {
|
|
||||||
for local_addr in lookup_host(&local_addr).await? {
|
|
||||||
for remote_addr in lookup_host(&remote_addr).await? {
|
|
||||||
let stream = super::timeout(
|
|
||||||
ms_timeout,
|
|
||||||
new_socket(local_addr, true)?.connect(remote_addr),
|
|
||||||
)
|
|
||||||
.await??;
|
|
||||||
stream.set_nodelay(true).ok();
|
|
||||||
let addr = stream.local_addr()?;
|
|
||||||
return Ok(Self(
|
|
||||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
|
||||||
addr,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bail!("could not resolve to any address");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect<'a, 't, P, T1, T2>(
|
|
||||||
proxy: P,
|
|
||||||
target: T1,
|
|
||||||
local: T2,
|
|
||||||
username: &'a str,
|
|
||||||
password: &'a str,
|
|
||||||
ms_timeout: u64,
|
|
||||||
) -> ResultType<Self>
|
|
||||||
where
|
|
||||||
P: ToProxyAddrs,
|
|
||||||
T1: IntoTargetAddr<'t>,
|
|
||||||
T2: ToSocketAddrs,
|
|
||||||
{
|
|
||||||
if let Some(local) = lookup_host(&local).await?.next() {
|
|
||||||
if let Some(proxy) = proxy.to_proxy_addrs().next().await {
|
|
||||||
let stream =
|
|
||||||
super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy?)).await??;
|
|
||||||
stream.set_nodelay(true).ok();
|
|
||||||
let stream = if username.trim().is_empty() {
|
|
||||||
super::timeout(
|
|
||||||
ms_timeout,
|
|
||||||
Socks5Stream::connect_with_socket(stream, target),
|
|
||||||
)
|
|
||||||
.await??
|
|
||||||
} else {
|
|
||||||
super::timeout(
|
|
||||||
ms_timeout,
|
|
||||||
Socks5Stream::connect_with_password_and_socket(
|
|
||||||
stream, target, username, password,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await??
|
|
||||||
};
|
|
||||||
let addr = stream.local_addr()?;
|
|
||||||
return Ok(Self(
|
|
||||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
|
||||||
addr,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
bail!("could not resolve to any address");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn local_addr(&self) -> SocketAddr {
|
|
||||||
self.1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_send_timeout(&mut self, ms: u64) {
|
|
||||||
self.3 = ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from(stream: impl TcpStreamTrait + Send + Sync + 'static, addr: SocketAddr) -> Self {
|
|
||||||
Self(
|
|
||||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
|
||||||
addr,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_raw(&mut self) {
|
|
||||||
self.0.codec_mut().set_raw();
|
|
||||||
self.2 = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_secured(&self) -> bool {
|
|
||||||
self.2.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn send(&mut self, msg: &impl Message) -> ResultType<()> {
|
|
||||||
self.send_raw(msg.write_to_bytes()?).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn send_raw(&mut self, msg: Vec<u8>) -> ResultType<()> {
|
|
||||||
let mut msg = msg;
|
|
||||||
if let Some(key) = self.2.as_mut() {
|
|
||||||
key.1 += 1;
|
|
||||||
let nonce = Self::get_nonce(key.1);
|
|
||||||
msg = secretbox::seal(&msg, &nonce, &key.0);
|
|
||||||
}
|
|
||||||
self.send_bytes(bytes::Bytes::from(msg)).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn send_bytes(&mut self, bytes: Bytes) -> ResultType<()> {
|
|
||||||
if self.3 > 0 {
|
|
||||||
super::timeout(self.3, self.0.send(bytes)).await??;
|
|
||||||
} else {
|
|
||||||
self.0.send(bytes).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn next(&mut self) -> Option<Result<BytesMut, Error>> {
|
|
||||||
let mut res = self.0.next().await;
|
|
||||||
if let Some(key) = self.2.as_mut() {
|
|
||||||
if let Some(Ok(bytes)) = res.as_mut() {
|
|
||||||
key.2 += 1;
|
|
||||||
let nonce = Self::get_nonce(key.2);
|
|
||||||
match secretbox::open(&bytes, &nonce, &key.0) {
|
|
||||||
Ok(res) => {
|
|
||||||
bytes.clear();
|
|
||||||
bytes.put_slice(&res);
|
|
||||||
}
|
|
||||||
Err(()) => {
|
|
||||||
return Some(Err(Error::new(ErrorKind::Other, "decryption error")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn next_timeout(&mut self, ms: u64) -> Option<Result<BytesMut, Error>> {
|
|
||||||
if let Ok(res) = super::timeout(ms, self.next()).await {
|
|
||||||
res
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_key(&mut self, key: Key) {
|
|
||||||
self.2 = Some((key, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_nonce(seqnum: u64) -> Nonce {
|
|
||||||
let mut nonce = Nonce([0u8; secretbox::NONCEBYTES]);
|
|
||||||
nonce.0[..std::mem::size_of_val(&seqnum)].copy_from_slice(&seqnum.to_le_bytes());
|
|
||||||
nonce
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_BACKLOG: u32 = 128;
|
|
||||||
|
|
||||||
#[allow(clippy::never_loop)]
|
|
||||||
pub async fn new_listener<T: ToSocketAddrs>(addr: T, reuse: bool) -> ResultType<TcpListener> {
|
|
||||||
if !reuse {
|
|
||||||
Ok(TcpListener::bind(addr).await?)
|
|
||||||
} else {
|
|
||||||
for addr in lookup_host(&addr).await? {
|
|
||||||
let socket = new_socket(addr, true)?;
|
|
||||||
return Ok(socket.listen(DEFAULT_BACKLOG)?);
|
|
||||||
}
|
|
||||||
bail!("could not resolve to any address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Unpin for DynTcpStream {}
|
|
||||||
|
|
||||||
impl AsyncRead for DynTcpStream {
|
|
||||||
fn poll_read(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
buf: &mut ReadBuf<'_>,
|
|
||||||
) -> Poll<io::Result<()>> {
|
|
||||||
AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncWrite for DynTcpStream {
|
|
||||||
fn poll_write(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
buf: &[u8],
|
|
||||||
) -> Poll<io::Result<usize>> {
|
|
||||||
AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
||||||
AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
||||||
AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: AsyncRead + AsyncWrite + Unpin> TcpStreamTrait for R {}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
use crate::{bail, ResultType};
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use bytes::{Bytes, BytesMut};
|
|
||||||
use futures::{SinkExt, StreamExt};
|
|
||||||
use protobuf::Message;
|
|
||||||
use socket2::{Domain, Socket, Type};
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use tokio::net::{ToSocketAddrs, UdpSocket};
|
|
||||||
use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs};
|
|
||||||
use tokio_util::{codec::BytesCodec, udp::UdpFramed};
|
|
||||||
|
|
||||||
pub enum FramedSocket {
|
|
||||||
Direct(UdpFramed<BytesCodec>),
|
|
||||||
ProxySocks(Socks5UdpFramed),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_socket(addr: SocketAddr, reuse: bool, buf_size: usize) -> Result<Socket, std::io::Error> {
|
|
||||||
let socket = match addr {
|
|
||||||
SocketAddr::V4(..) => Socket::new(Domain::ipv4(), Type::dgram(), None),
|
|
||||||
SocketAddr::V6(..) => Socket::new(Domain::ipv6(), Type::dgram(), None),
|
|
||||||
}?;
|
|
||||||
if reuse {
|
|
||||||
// windows has no reuse_port, but it's reuse_address
|
|
||||||
// almost equals to unix's reuse_port + reuse_address,
|
|
||||||
// though may introduce nondeterministic behavior
|
|
||||||
#[cfg(unix)]
|
|
||||||
socket.set_reuse_port(true)?;
|
|
||||||
socket.set_reuse_address(true)?;
|
|
||||||
}
|
|
||||||
// only nonblocking work with tokio, https://stackoverflow.com/questions/64649405/receiver-on-tokiompscchannel-only-receives-messages-when-buffer-is-full
|
|
||||||
socket.set_nonblocking(true)?;
|
|
||||||
if buf_size > 0 {
|
|
||||||
socket.set_recv_buffer_size(buf_size).ok();
|
|
||||||
}
|
|
||||||
log::info!(
|
|
||||||
"Receive buf size of udp {}: {:?}",
|
|
||||||
addr,
|
|
||||||
socket.recv_buffer_size()
|
|
||||||
);
|
|
||||||
socket.bind(&addr.into())?;
|
|
||||||
Ok(socket)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FramedSocket {
|
|
||||||
pub async fn new<T: ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
|
||||||
let socket = UdpSocket::bind(addr).await?;
|
|
||||||
Ok(Self::Direct(UdpFramed::new(socket, BytesCodec::new())))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::never_loop)]
|
|
||||||
pub async fn new_reuse<T: std::net::ToSocketAddrs>(addr: T) -> ResultType<Self> {
|
|
||||||
for addr in addr.to_socket_addrs()?.filter(|x| x.is_ipv4()) {
|
|
||||||
let socket = new_socket(addr, true, 0)?.into_udp_socket();
|
|
||||||
return Ok(Self::Direct(UdpFramed::new(
|
|
||||||
UdpSocket::from_std(socket)?,
|
|
||||||
BytesCodec::new(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
bail!("could not resolve to any address");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_with_buf_size<T: std::net::ToSocketAddrs>(
|
|
||||||
addr: T,
|
|
||||||
buf_size: usize,
|
|
||||||
) -> ResultType<Self> {
|
|
||||||
for addr in addr.to_socket_addrs()?.filter(|x| x.is_ipv4()) {
|
|
||||||
return Ok(Self::Direct(UdpFramed::new(
|
|
||||||
UdpSocket::from_std(new_socket(addr, false, buf_size)?.into_udp_socket())?,
|
|
||||||
BytesCodec::new(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
bail!("could not resolve to any address");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new_proxy<'a, 't, P: ToProxyAddrs, T: ToSocketAddrs>(
|
|
||||||
proxy: P,
|
|
||||||
local: T,
|
|
||||||
username: &'a str,
|
|
||||||
password: &'a str,
|
|
||||||
ms_timeout: u64,
|
|
||||||
) -> ResultType<Self> {
|
|
||||||
let framed = if username.trim().is_empty() {
|
|
||||||
super::timeout(ms_timeout, Socks5UdpFramed::connect(proxy, Some(local))).await??
|
|
||||||
} else {
|
|
||||||
super::timeout(
|
|
||||||
ms_timeout,
|
|
||||||
Socks5UdpFramed::connect_with_password(proxy, Some(local), username, password),
|
|
||||||
)
|
|
||||||
.await??
|
|
||||||
};
|
|
||||||
log::trace!(
|
|
||||||
"Socks5 udp connected, local addr: {:?}, target addr: {}",
|
|
||||||
framed.local_addr(),
|
|
||||||
framed.socks_addr()
|
|
||||||
);
|
|
||||||
Ok(Self::ProxySocks(framed))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn send(
|
|
||||||
&mut self,
|
|
||||||
msg: &impl Message,
|
|
||||||
addr: impl IntoTargetAddr<'_>,
|
|
||||||
) -> ResultType<()> {
|
|
||||||
let addr = addr.into_target_addr()?.to_owned();
|
|
||||||
let send_data = Bytes::from(msg.write_to_bytes()?);
|
|
||||||
let _ = match self {
|
|
||||||
Self::Direct(f) => match addr {
|
|
||||||
TargetAddr::Ip(addr) => f.send((send_data, addr)).await?,
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Self::ProxySocks(f) => f.send((send_data, addr)).await?,
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/68733302/1926020
|
|
||||||
#[inline]
|
|
||||||
pub async fn send_raw(
|
|
||||||
&mut self,
|
|
||||||
msg: &'static [u8],
|
|
||||||
addr: impl IntoTargetAddr<'static>,
|
|
||||||
) -> ResultType<()> {
|
|
||||||
let addr = addr.into_target_addr()?.to_owned();
|
|
||||||
|
|
||||||
let _ = match self {
|
|
||||||
Self::Direct(f) => match addr {
|
|
||||||
TargetAddr::Ip(addr) => f.send((Bytes::from(msg), addr)).await?,
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Self::ProxySocks(f) => f.send((Bytes::from(msg), addr)).await?,
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn next(&mut self) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
|
||||||
match self {
|
|
||||||
Self::Direct(f) => match f.next().await {
|
|
||||||
Some(Ok((data, addr))) => {
|
|
||||||
Some(Ok((data, addr.into_target_addr().ok()?.to_owned())))
|
|
||||||
}
|
|
||||||
Some(Err(e)) => Some(Err(anyhow!(e))),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
Self::ProxySocks(f) => match f.next().await {
|
|
||||||
Some(Ok((data, _))) => Some(Ok((data.data, data.dst_addr))),
|
|
||||||
Some(Err(e)) => Some(Err(anyhow!(e))),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn next_timeout(
|
|
||||||
&mut self,
|
|
||||||
ms: u64,
|
|
||||||
) -> Option<ResultType<(BytesMut, TargetAddr<'static>)>> {
|
|
||||||
if let Ok(res) =
|
|
||||||
tokio::time::timeout(std::time::Duration::from_millis(ms), self.next()).await
|
|
||||||
{
|
|
||||||
res
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
65
rcd/rustdesk-hbbr
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# PROVIDE: rustdesk_hbbr
|
||||||
|
# REQUIRE: LOGIN
|
||||||
|
# KEYWORD: shutdown
|
||||||
|
#
|
||||||
|
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||||
|
# to enable this service:
|
||||||
|
#
|
||||||
|
# rustdesk_hbbr_enable (bool): Set to NO by default.
|
||||||
|
# Set it to YES to enable rustdesk_hbbr.
|
||||||
|
# rustdesk_hbbr_args (string): Set extra arguments to pass to rustdesk_hbbr
|
||||||
|
# Default is "-k _".
|
||||||
|
# rustdesk_hbbr_user (string): Set user that rustdesk_hbbr will run under
|
||||||
|
# Default is "root".
|
||||||
|
# rustdesk_hbbr_group (string): Set group that rustdesk_hbbr will run under
|
||||||
|
# Default is "wheel".
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=rustdesk_hbbr
|
||||||
|
desc="Rustdesk Relay Server"
|
||||||
|
rcvar=rustdesk_hbbr_enable
|
||||||
|
|
||||||
|
load_rc_config $name
|
||||||
|
|
||||||
|
: ${rustdesk_hbbr_enable:=NO}
|
||||||
|
: ${rustdesk_hbbr_args="-k _"}
|
||||||
|
: ${rustdesk_hbbr_user:=rustdesk}
|
||||||
|
: ${rustdesk_hbbr_group:=rustdesk}
|
||||||
|
|
||||||
|
pidfile=/var/run/rustdesk_hbbr.pid
|
||||||
|
command=/usr/sbin/daemon
|
||||||
|
procname=/usr/local/sbin/hbbr
|
||||||
|
rustdesk_hbbr_chdir=/var/db/rustdesk-server
|
||||||
|
command_args="-p ${pidfile} -o /var/log/rustdesk-hbbr.log ${procname} ${rustdesk_hbbr_args}"
|
||||||
|
## If you want the daemon do its log over syslog comment out the above line and remove the comment from the below replacement
|
||||||
|
#command_args="-p ${pidfile} -T ${name} ${procname} ${rustdesk_hbbr_args}"
|
||||||
|
|
||||||
|
start_precmd=rustdesk_hbbr_startprecmd
|
||||||
|
|
||||||
|
rustdesk_hbbr_startprecmd()
|
||||||
|
{
|
||||||
|
if [ -e ${pidfile} ]; then
|
||||||
|
chown ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} ${pidfile};
|
||||||
|
else
|
||||||
|
install -o ${rustdesk_hbbr_user} -g ${rustdesk_hbbr_group} /dev/null ${pidfile};
|
||||||
|
fi
|
||||||
|
if [ -e ${rustdesk_hbbr_chdir} ]; then
|
||||||
|
chown -R ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} ${rustdesk_hbbr_chdir};
|
||||||
|
chmod -R 770 ${rustdesk_hbbr_chdir};
|
||||||
|
else
|
||||||
|
mkdir -m 770 ${rustdesk_hbbr_chdir};
|
||||||
|
chown ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} ${rustdesk_hbbr_chdir};
|
||||||
|
fi
|
||||||
|
if [ -e /var/log/rustdesk-hbbr.log ]; then
|
||||||
|
chown -R ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} /var/log/rustdesk-hbbr.log;
|
||||||
|
chmod 660 /var/log/rustdesk-hbbr.log;
|
||||||
|
else
|
||||||
|
install -o ${rustdesk_hbbr_user} -g ${rustdesk_hbbr_group} /dev/null /var/log/rustdesk-hbbr.log;
|
||||||
|
chmod 660 /var/log/rustdesk-hbbr.log;
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
68
rcd/rustdesk-hbbs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# PROVIDE: rustdesk_hbbs
|
||||||
|
# REQUIRE: LOGIN
|
||||||
|
# KEYWORD: shutdown
|
||||||
|
#
|
||||||
|
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||||
|
# to enable this service:
|
||||||
|
#
|
||||||
|
# rustdesk_hbbs_enable (bool): Set to NO by default.
|
||||||
|
# Set it to YES to enable rustdesk_hbbs.
|
||||||
|
# rustdesk_hbbs_ip (string): Set IP address/hostname of relay server to use
|
||||||
|
# Defaults to "127.0.0.1", please replace with your server hostname/IP.
|
||||||
|
# rustdesk_hbbs_args (string): Set extra arguments to pass to rustdesk_hbbs
|
||||||
|
# Default is "-r ${rustdesk_hbbs_ip} -k _".
|
||||||
|
# rustdesk_hbbs_user (string): Set user that rustdesk_hbbs will run under
|
||||||
|
# Default is "root".
|
||||||
|
# rustdesk_hbbs_group (string): Set group that rustdesk_hbbs will run under
|
||||||
|
# Default is "wheel".
|
||||||
|
|
||||||
|
. /etc/rc.subr
|
||||||
|
|
||||||
|
name=rustdesk_hbbs
|
||||||
|
desc="Rustdesk ID/Rendezvous Server"
|
||||||
|
rcvar=rustdesk_hbbs_enable
|
||||||
|
|
||||||
|
load_rc_config $name
|
||||||
|
|
||||||
|
: ${rustdesk_hbbs_enable:=NO}
|
||||||
|
: ${rustdesk_hbbs_ip:=127.0.0.1}
|
||||||
|
: ${rustdesk_hbbs_args="-r ${rustdesk_hbbs_ip} -k _"}
|
||||||
|
: ${rustdesk_hbbs_user:=rustdesk}
|
||||||
|
: ${rustdesk_hbbs_group:=rustdesk}
|
||||||
|
|
||||||
|
pidfile=/var/run/rustdesk_hbbs.pid
|
||||||
|
command=/usr/sbin/daemon
|
||||||
|
procname=/usr/local/sbin/hbbs
|
||||||
|
rustdesk_hbbs_chdir=/var/db/rustdesk-server
|
||||||
|
command_args="-p ${pidfile} -o /var/log/rustdesk-hbbs.log ${procname} ${rustdesk_hbbs_args}"
|
||||||
|
## If you want the daemon to do its log over syslog, comment out the above line and remove the comment from the below replacement
|
||||||
|
#command_args="-p ${pidfile} -T ${name} ${procname} ${rustdesk_hbbs_args}"
|
||||||
|
|
||||||
|
start_precmd=rustdesk_hbbs_startprecmd
|
||||||
|
|
||||||
|
rustdesk_hbbs_startprecmd()
|
||||||
|
{
|
||||||
|
if [ -e ${pidfile} ]; then
|
||||||
|
chown ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} ${pidfile};
|
||||||
|
else
|
||||||
|
install -o ${rustdesk_hbbs_user} -g ${rustdesk_hbbs_group} /dev/null ${pidfile};
|
||||||
|
fi
|
||||||
|
if [ -e ${rustdesk_hbbs_chdir} ]; then
|
||||||
|
chown -R ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} ${rustdesk_hbbs_chdir};
|
||||||
|
chmod -R 770 ${rustdesk_hbbs_chdir};
|
||||||
|
else
|
||||||
|
mkdir -m 770 ${rustdesk_hbbs_chdir};
|
||||||
|
chown ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} ${rustdesk_hbbs_chdir};
|
||||||
|
fi
|
||||||
|
if [ -e /var/log/rustdesk-hbbs.log ]; then
|
||||||
|
chown -R ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} /var/log/rustdesk-hbbs.log;
|
||||||
|
chmod 660 /var/log/rustdesk-hbbs.log;
|
||||||
|
else
|
||||||
|
install -o ${rustdesk_hbbs_user} -g ${rustdesk_hbbs_group} /dev/null /var/log/rustdesk-hbbs.log;
|
||||||
|
chmod 660 /var/log/rustdesk-hbbs.log;
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_rc_command "$1"
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
use clap::App;
|
use clap::App;
|
||||||
use hbb_common::{anyhow::Context, log, ResultType};
|
use hbb_common::{
|
||||||
|
allow_err, anyhow::{Context, Result}, get_version_number, log, tokio, ResultType
|
||||||
|
};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use sodiumoxide::crypto::sign;
|
use sodiumoxide::crypto::sign;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -9,15 +11,17 @@ use std::{
|
|||||||
time::{Instant, SystemTime},
|
time::{Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn get_expired_time() -> Instant {
|
pub(crate) fn get_expired_time() -> Instant {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
now.checked_sub(std::time::Duration::from_secs(3600))
|
now.checked_sub(std::time::Duration::from_secs(3600))
|
||||||
.unwrap_or(now)
|
.unwrap_or(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn test_if_valid_server(host: &str, name: &str) -> ResultType<SocketAddr> {
|
pub(crate) fn test_if_valid_server(host: &str, name: &str) -> ResultType<SocketAddr> {
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
let res = if host.contains(":") {
|
let res = if host.contains(':') {
|
||||||
host.to_socket_addrs()?.next().context("")
|
host.to_socket_addrs()?.next().context("")
|
||||||
} else {
|
} else {
|
||||||
format!("{}:{}", host, 0)
|
format!("{}:{}", host, 0)
|
||||||
@@ -31,9 +35,10 @@ pub(crate) fn test_if_valid_server(host: &str, name: &str) -> ResultType<SocketA
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn get_servers(s: &str, tag: &str) -> Vec<String> {
|
pub(crate) fn get_servers(s: &str, tag: &str) -> Vec<String> {
|
||||||
let servers: Vec<String> = s
|
let servers: Vec<String> = s
|
||||||
.split(",")
|
.split(',')
|
||||||
.filter(|x| !x.is_empty() && test_if_valid_server(x, tag).is_ok())
|
.filter(|x| !x.is_empty() && test_if_valid_server(x, tag).is_ok())
|
||||||
.map(|x| x.to_owned())
|
.map(|x| x.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
@@ -41,17 +46,19 @@ pub(crate) fn get_servers(s: &str, tag: &str) -> Vec<String> {
|
|||||||
servers
|
servers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn arg_name(name: &str) -> String {
|
fn arg_name(name: &str) -> String {
|
||||||
name.to_uppercase().replace("_", "-")
|
name.to_uppercase().replace('_', "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn init_args(args: &str, name: &str, about: &str) {
|
pub fn init_args(args: &str, name: &str, about: &str) {
|
||||||
let matches = App::new(name)
|
let matches = App::new(name)
|
||||||
.version(crate::version::VERSION)
|
.version(crate::version::VERSION)
|
||||||
.author("Purslane Ltd. <info@rustdesk.com>")
|
.author("Purslane Ltd. <info@rustdesk.com>")
|
||||||
.about(about)
|
.about(about)
|
||||||
.args_from_usage(&args)
|
.args_from_usage(args)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
if let Ok(v) = Ini::load_from_file(".env") {
|
if let Ok(v) = Ini::load_from_file(".env") {
|
||||||
if let Some(section) = v.section(None::<String>) {
|
if let Some(section) = v.section(None::<String>) {
|
||||||
@@ -70,22 +77,25 @@ pub fn init_args(args: &str, name: &str, about: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (k, v) in matches.args {
|
for (k, v) in matches.args {
|
||||||
if let Some(v) = v.vals.get(0) {
|
if let Some(v) = v.vals.first() {
|
||||||
std::env::set_var(arg_name(k), v.to_string_lossy().to_string());
|
std::env::set_var(arg_name(k), v.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_arg(name: &str) -> String {
|
pub fn get_arg(name: &str) -> String {
|
||||||
get_arg_or(name, "".to_owned())
|
get_arg_or(name, "".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_arg_or(name: &str, default: String) -> String {
|
pub fn get_arg_or(name: &str, default: String) -> String {
|
||||||
std::env::var(arg_name(name)).unwrap_or(default)
|
std::env::var(arg_name(name)).unwrap_or(default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn now() -> u64 {
|
pub fn now() -> u64 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
@@ -102,13 +112,18 @@ pub fn gen_sk(wait: u64) -> (String, Option<sign::SecretKey>) {
|
|||||||
if let Ok(mut file) = std::fs::File::open(sk_file) {
|
if let Ok(mut file) = std::fs::File::open(sk_file) {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
if file.read_to_string(&mut contents).is_ok() {
|
if file.read_to_string(&mut contents).is_ok() {
|
||||||
let sk = base64::decode(&contents).unwrap_or_default();
|
let contents = contents.trim();
|
||||||
|
let sk = base64::decode(contents).unwrap_or_default();
|
||||||
if sk.len() == sign::SECRETKEYBYTES {
|
if sk.len() == sign::SECRETKEYBYTES {
|
||||||
let mut tmp = [0u8; sign::SECRETKEYBYTES];
|
let mut tmp = [0u8; sign::SECRETKEYBYTES];
|
||||||
tmp[..].copy_from_slice(&sk);
|
tmp[..].copy_from_slice(&sk);
|
||||||
let pk = base64::encode(&tmp[sign::SECRETKEYBYTES / 2..]);
|
let pk = base64::encode(&tmp[sign::SECRETKEYBYTES / 2..]);
|
||||||
log::info!("Private key comes from {}", sk_file);
|
log::info!("Private key comes from {}", sk_file);
|
||||||
return (pk, Some(sign::SecretKey(tmp)));
|
return (pk, Some(sign::SecretKey(tmp)));
|
||||||
|
} else {
|
||||||
|
// don't use log here, since it is async
|
||||||
|
println!("Fatal error: malformed private key in {sk_file}.");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -118,12 +133,12 @@ pub fn gen_sk(wait: u64) -> (String, Option<sign::SecretKey>) {
|
|||||||
};
|
};
|
||||||
let (mut pk, mut sk) = gen_func();
|
let (mut pk, mut sk) = gen_func();
|
||||||
for _ in 0..300 {
|
for _ in 0..300 {
|
||||||
if !pk.contains("/") && !pk.contains(":") {
|
if !pk.contains('/') && !pk.contains(':') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
(pk, sk) = gen_func();
|
(pk, sk) = gen_func();
|
||||||
}
|
}
|
||||||
let pub_file = format!("{}.pub", sk_file);
|
let pub_file = format!("{sk_file}.pub");
|
||||||
if let Ok(mut f) = std::fs::File::create(&pub_file) {
|
if let Ok(mut f) = std::fs::File::create(&pub_file) {
|
||||||
f.write_all(pk.as_bytes()).ok();
|
f.write_all(pk.as_bytes()).ok();
|
||||||
if let Ok(mut f) = std::fs::File::create(sk_file) {
|
if let Ok(mut f) = std::fs::File::create(sk_file) {
|
||||||
@@ -138,3 +153,66 @@ pub fn gen_sk(wait: u64) -> (String, Option<sign::SecretKey>) {
|
|||||||
}
|
}
|
||||||
("".to_owned(), None)
|
("".to_owned(), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub async fn listen_signal() -> Result<()> {
|
||||||
|
use hbb_common::tokio;
|
||||||
|
use hbb_common::tokio::signal::unix::{signal, SignalKind};
|
||||||
|
|
||||||
|
tokio::spawn(async {
|
||||||
|
let mut s = signal(SignalKind::terminate())?;
|
||||||
|
let terminate = s.recv();
|
||||||
|
let mut s = signal(SignalKind::interrupt())?;
|
||||||
|
let interrupt = s.recv();
|
||||||
|
let mut s = signal(SignalKind::quit())?;
|
||||||
|
let quit = s.recv();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = terminate => {
|
||||||
|
log::info!("signal terminate");
|
||||||
|
}
|
||||||
|
_ = interrupt => {
|
||||||
|
log::info!("signal interrupt");
|
||||||
|
}
|
||||||
|
_ = quit => {
|
||||||
|
log::info!("signal quit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub async fn listen_signal() -> Result<()> {
|
||||||
|
let () = std::future::pending().await;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn check_software_update() {
|
||||||
|
const ONE_DAY_IN_SECONDS: u64 = 60 * 60 * 24;
|
||||||
|
std::thread::spawn(move || loop {
|
||||||
|
std::thread::spawn(move || allow_err!(check_software_update_()));
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(ONE_DAY_IN_SECONDS));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||||
|
let (request, url) = hbb_common::version_check_request(hbb_common::VER_TYPE_RUSTDESK_SERVER.to_string());
|
||||||
|
let latest_release_response = reqwest::Client::builder().build()?
|
||||||
|
.post(url)
|
||||||
|
.json(&request)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let bytes = latest_release_response.bytes().await?;
|
||||||
|
let resp: hbb_common::VersionCheckResponse = serde_json::from_slice(&bytes)?;
|
||||||
|
let response_url = resp.url;
|
||||||
|
let latest_release_version = response_url.rsplit('/').next().unwrap_or_default();
|
||||||
|
if get_version_number(&latest_release_version) > get_version_number(crate::version::VERSION) {
|
||||||
|
log::info!("new version is available: {}", latest_release_version);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use hbb_common::{log, ResultType};
|
use hbb_common::{log, ResultType};
|
||||||
use serde_json::value::Value;
|
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
sqlite::SqliteConnectOptions, ConnectOptions, Connection, Error as SqlxError, SqliteConnection,
|
sqlite::SqliteConnectOptions, ConnectOptions, Connection, Error as SqlxError, SqliteConnection,
|
||||||
};
|
};
|
||||||
@@ -8,7 +7,6 @@ use std::{ops::DerefMut, str::FromStr};
|
|||||||
//use sqlx::postgres::PgPoolOptions;
|
//use sqlx::postgres::PgPoolOptions;
|
||||||
//use sqlx::mysql::MySqlPoolOptions;
|
//use sqlx::mysql::MySqlPoolOptions;
|
||||||
|
|
||||||
pub(crate) type MapValue = serde_json::map::Map<String, Value>;
|
|
||||||
type Pool = deadpool::managed::Pool<DbPool>;
|
type Pool = deadpool::managed::Pool<DbPool>;
|
||||||
|
|
||||||
pub struct DbPool {
|
pub struct DbPool {
|
||||||
@@ -54,7 +52,7 @@ impl Database {
|
|||||||
std::fs::File::create(url).ok();
|
std::fs::File::create(url).ok();
|
||||||
}
|
}
|
||||||
let n: usize = std::env::var("MAX_DATABASE_CONNECTIONS")
|
let n: usize = std::env::var("MAX_DATABASE_CONNECTIONS")
|
||||||
.unwrap_or("1".to_owned())
|
.unwrap_or_else(|_| "1".to_owned())
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
log::debug!("MAX_DATABASE_CONNECTIONS={}", n);
|
log::debug!("MAX_DATABASE_CONNECTIONS={}", n);
|
||||||
@@ -105,24 +103,6 @@ impl Database {
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn get_conn(&self) -> ResultType<deadpool::managed::Object<DbPool>> {
|
|
||||||
Ok(self.pool.get().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_peer(&self, payload: MapValue, guid: &[u8]) -> ResultType<()> {
|
|
||||||
let mut conn = self.get_conn().await?;
|
|
||||||
let mut tx = conn.begin().await?;
|
|
||||||
if let Some(v) = payload.get("note") {
|
|
||||||
let v = get_str(v);
|
|
||||||
sqlx::query!("update peer set note = ? where guid = ?", v, guid)
|
|
||||||
.execute(&mut tx)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
tx.commit().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_peer(
|
pub async fn insert_peer(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
@@ -199,17 +179,3 @@ mod tests {
|
|||||||
hbb_common::futures::future::join_all(jobs).await;
|
hbb_common::futures::future::join_all(jobs).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_str(v: &Value) -> Option<&str> {
|
|
||||||
match v {
|
|
||||||
Value::String(v) => {
|
|
||||||
let v = v.trim();
|
|
||||||
if v.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
16
src/hbbr.rs
@@ -13,10 +13,9 @@ fn main() -> ResultType<()> {
|
|||||||
.write_mode(WriteMode::Async)
|
.write_mode(WriteMode::Async)
|
||||||
.start()?;
|
.start()?;
|
||||||
let args = format!(
|
let args = format!(
|
||||||
"-p, --port=[NUMBER(default={})] 'Sets the listening port'
|
"-p, --port=[NUMBER(default={RELAY_PORT})] 'Sets the listening port'
|
||||||
-k, --key=[KEY] 'Only allow the client with the same key'
|
-k, --key=[KEY] 'Only allow the client with the same key'
|
||||||
",
|
",
|
||||||
RELAY_PORT,
|
|
||||||
);
|
);
|
||||||
let matches = App::new("hbbr")
|
let matches = App::new("hbbr")
|
||||||
.version(version::VERSION)
|
.version(version::VERSION)
|
||||||
@@ -29,9 +28,18 @@ fn main() -> ResultType<()> {
|
|||||||
section.iter().for_each(|(k, v)| std::env::set_var(k, v));
|
section.iter().for_each(|(k, v)| std::env::set_var(k, v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut port = RELAY_PORT;
|
||||||
|
if let Ok(v) = std::env::var("PORT") {
|
||||||
|
let v: i32 = v.parse().unwrap_or_default();
|
||||||
|
if v > 0 {
|
||||||
|
port = v + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
start(
|
start(
|
||||||
matches.value_of("port").unwrap_or(&RELAY_PORT.to_string()),
|
matches.value_of("port").unwrap_or(&port.to_string()),
|
||||||
matches.value_of("key").unwrap_or(""),
|
matches
|
||||||
|
.value_of("key")
|
||||||
|
.unwrap_or(&std::env::var("KEY").unwrap_or_default()),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/main.rs
@@ -15,16 +15,14 @@ fn main() -> ResultType<()> {
|
|||||||
.start()?;
|
.start()?;
|
||||||
let args = format!(
|
let args = format!(
|
||||||
"-c --config=[FILE] +takes_value 'Sets a custom config file'
|
"-c --config=[FILE] +takes_value 'Sets a custom config file'
|
||||||
-p, --port=[NUMBER(default={})] 'Sets the listening port'
|
-p, --port=[NUMBER(default={RENDEZVOUS_PORT})] 'Sets the listening port'
|
||||||
-s, --serial=[NUMBER(default=0)] 'Sets configure update serial number'
|
-s, --serial=[NUMBER(default=0)] 'Sets configure update serial number'
|
||||||
-R, --rendezvous-servers=[HOSTS] 'Sets rendezvous servers, seperated by colon'
|
-R, --rendezvous-servers=[HOSTS] 'Sets rendezvous servers, separated by comma'
|
||||||
-u, --software-url=[URL] 'Sets download url of RustDesk software of newest version'
|
-u, --software-url=[URL] 'Sets download url of RustDesk software of newest version'
|
||||||
-r, --relay-servers=[HOST] 'Sets the default relay servers, seperated by colon'
|
-r, --relay-servers=[HOST] 'Sets the default relay servers, separated by comma'
|
||||||
-M, --rmem=[NUMBER(default={})] 'Sets UDP recv buffer size, set system rmem_max first, e.g., sudo sysctl -w net.core.rmem_max=52428800. vi /etc/sysctl.conf, net.core.rmem_max=52428800, sudo sysctl –p'
|
-M, --rmem=[NUMBER(default={RMEM})] 'Sets UDP recv buffer size, set system rmem_max first, e.g., sudo sysctl -w net.core.rmem_max=52428800. vi /etc/sysctl.conf, net.core.rmem_max=52428800, sudo sysctl –p'
|
||||||
, --mask=[MASK] 'Determine if the connection comes from LAN, e.g. 192.168.0.0/16'
|
, --mask=[MASK] 'Determine if the connection comes from LAN, e.g. 192.168.0.0/16'
|
||||||
-k, --key=[KEY] 'Only allow the client with the same key'",
|
-k, --key=[KEY] 'Only allow the client with the same key'",
|
||||||
RENDEZVOUS_PORT,
|
|
||||||
RMEM,
|
|
||||||
);
|
);
|
||||||
init_args(&args, "hbbs", "RustDesk ID/Rendezvous Server");
|
init_args(&args, "hbbs", "RustDesk ID/Rendezvous Server");
|
||||||
let port = get_arg_or("port", RENDEZVOUS_PORT.to_string()).parse::<i32>()?;
|
let port = get_arg_or("port", RENDEZVOUS_PORT.to_string()).parse::<i32>()?;
|
||||||
@@ -33,6 +31,7 @@ fn main() -> ResultType<()> {
|
|||||||
}
|
}
|
||||||
let rmem = get_arg("rmem").parse::<usize>().unwrap_or(RMEM);
|
let rmem = get_arg("rmem").parse::<usize>().unwrap_or(RMEM);
|
||||||
let serial: i32 = get_arg("serial").parse().unwrap_or(0);
|
let serial: i32 = get_arg("serial").parse().unwrap_or(0);
|
||||||
RendezvousServer::start(port, serial, &get_arg("key"), rmem)?;
|
crate::common::check_software_update();
|
||||||
|
RendezvousServer::start(port, serial, &get_arg_or("key", "-".to_owned()), rmem)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/peer.rs
@@ -1,24 +1,27 @@
|
|||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
|
bytes::Bytes,
|
||||||
log,
|
log,
|
||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
tokio::sync::{Mutex, RwLock},
|
tokio::sync::{Mutex, RwLock},
|
||||||
bytes::Bytes,
|
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, collections::HashSet, net::SocketAddr, sync::Arc, time::Instant};
|
use std::{collections::HashMap, collections::HashSet, net::SocketAddr, sync::Arc, time::Instant};
|
||||||
|
|
||||||
|
type IpBlockMap = HashMap<String, ((u32, Instant), (HashSet<String>, Instant))>;
|
||||||
|
type UserStatusMap = HashMap<Vec<u8>, Arc<(Option<Vec<u8>>, bool)>>;
|
||||||
|
type IpChangesMap = HashMap<String, (Instant, HashMap<String, i32>)>;
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub(crate) static ref IP_BLOCKER: Mutex<HashMap<String, ((u32, Instant), (HashSet<String>, Instant))>> = Default::default();
|
pub(crate) static ref IP_BLOCKER: Mutex<IpBlockMap> = Default::default();
|
||||||
pub(crate) static ref USER_STATUS: RwLock<HashMap<Vec<u8>, Arc<(Option<Vec<u8>>, bool)>>> = Default::default();
|
pub(crate) static ref USER_STATUS: RwLock<UserStatusMap> = Default::default();
|
||||||
pub(crate) static ref IP_CHANGES: Mutex<HashMap<String, (Instant, HashMap<String, i32>)>> = Default::default();
|
pub(crate) static ref IP_CHANGES: Mutex<IpChangesMap> = Default::default();
|
||||||
}
|
}
|
||||||
pub static IP_CHANGE_DUR: u64 = 180;
|
pub const IP_CHANGE_DUR: u64 = 180;
|
||||||
pub static IP_CHANGE_DUR_X2: u64 = IP_CHANGE_DUR * 2;
|
pub const IP_CHANGE_DUR_X2: u64 = IP_CHANGE_DUR * 2;
|
||||||
pub static DAY_SECONDS: u64 = 3600 * 24;
|
pub const DAY_SECONDS: u64 = 3600 * 24;
|
||||||
pub static IP_BLOCK_DUR: u64 = 60;
|
pub const IP_BLOCK_DUR: u64 = 60;
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub(crate) struct PeerInfo {
|
pub(crate) struct PeerInfo {
|
||||||
@@ -32,9 +35,9 @@ pub(crate) struct Peer {
|
|||||||
pub(crate) guid: Vec<u8>,
|
pub(crate) guid: Vec<u8>,
|
||||||
pub(crate) uuid: Bytes,
|
pub(crate) uuid: Bytes,
|
||||||
pub(crate) pk: Bytes,
|
pub(crate) pk: Bytes,
|
||||||
pub(crate) user: Option<Vec<u8>>,
|
// pub(crate) user: Option<Vec<u8>>,
|
||||||
pub(crate) info: PeerInfo,
|
pub(crate) info: PeerInfo,
|
||||||
pub(crate) disabled: bool,
|
// pub(crate) disabled: bool,
|
||||||
pub(crate) reg_pk: (u32, Instant), // how often register_pk
|
pub(crate) reg_pk: (u32, Instant), // how often register_pk
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +50,8 @@ impl Default for Peer {
|
|||||||
uuid: Bytes::new(),
|
uuid: Bytes::new(),
|
||||||
pk: Bytes::new(),
|
pk: Bytes::new(),
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
user: None,
|
// user: None,
|
||||||
disabled: false,
|
// disabled: false,
|
||||||
reg_pk: (0, get_expired_time()),
|
reg_pk: (0, get_expired_time()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +68,6 @@ pub(crate) struct PeerMap {
|
|||||||
impl PeerMap {
|
impl PeerMap {
|
||||||
pub(crate) async fn new() -> ResultType<Self> {
|
pub(crate) async fn new() -> ResultType<Self> {
|
||||||
let db = std::env::var("DB_URL").unwrap_or({
|
let db = std::env::var("DB_URL").unwrap_or({
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut db = "db_v2.sqlite3".to_owned();
|
let mut db = "db_v2.sqlite3".to_owned();
|
||||||
#[cfg(all(windows, not(debug_assertions)))]
|
#[cfg(all(windows, not(debug_assertions)))]
|
||||||
{
|
{
|
||||||
@@ -75,7 +77,7 @@ impl PeerMap {
|
|||||||
}
|
}
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
db = format!("./{}", db);
|
db = format!("./{db}");
|
||||||
}
|
}
|
||||||
db
|
db
|
||||||
});
|
});
|
||||||
@@ -132,24 +134,22 @@ impl PeerMap {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn get(&self, id: &str) -> Option<LockPeer> {
|
pub(crate) async fn get(&self, id: &str) -> Option<LockPeer> {
|
||||||
let p = self.map.read().await.get(id).map(|x| x.clone());
|
let p = self.map.read().await.get(id).cloned();
|
||||||
if p.is_some() {
|
if p.is_some() {
|
||||||
return p;
|
return p;
|
||||||
} else {
|
} else if let Ok(Some(v)) = self.db.get_peer(id).await {
|
||||||
if let Ok(Some(v)) = self.db.get_peer(id).await {
|
let peer = Peer {
|
||||||
let peer = Peer {
|
guid: v.guid,
|
||||||
guid: v.guid,
|
uuid: v.uuid.into(),
|
||||||
uuid: v.uuid.into(),
|
pk: v.pk.into(),
|
||||||
pk: v.pk.into(),
|
// user: v.user,
|
||||||
user: v.user,
|
info: serde_json::from_str::<PeerInfo>(&v.info).unwrap_or_default(),
|
||||||
info: serde_json::from_str::<PeerInfo>(&v.info).unwrap_or_default(),
|
// disabled: v.status == Some(0),
|
||||||
disabled: v.status == Some(0),
|
..Default::default()
|
||||||
..Default::default()
|
};
|
||||||
};
|
let peer = Arc::new(RwLock::new(peer));
|
||||||
let peer = Arc::new(RwLock::new(peer));
|
self.map.write().await.insert(id.to_owned(), peer.clone());
|
||||||
self.map.write().await.insert(id.to_owned(), peer.clone());
|
return Some(peer);
|
||||||
return Some(peer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ impl PeerMap {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn get_in_memory(&self, id: &str) -> Option<LockPeer> {
|
pub(crate) async fn get_in_memory(&self, id: &str) -> Option<LockPeer> {
|
||||||
self.map.read().await.get(id).map(|x| x.clone())
|
self.map.read().await.get(id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use hbb_common::{
|
|||||||
protobuf::Message as _,
|
protobuf::Message as _,
|
||||||
rendezvous_proto::*,
|
rendezvous_proto::*,
|
||||||
sleep,
|
sleep,
|
||||||
tcp::{new_listener, FramedStream},
|
tcp::{listen_any, FramedStream},
|
||||||
timeout,
|
timeout,
|
||||||
tokio::{
|
tokio::{
|
||||||
self,
|
self,
|
||||||
@@ -25,6 +25,7 @@ use std::{
|
|||||||
io::prelude::*,
|
io::prelude::*,
|
||||||
io::Error,
|
io::Error,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
type Usage = (usize, usize, usize, usize);
|
type Usage = (usize, usize, usize, usize);
|
||||||
@@ -36,13 +37,13 @@ lazy_static::lazy_static! {
|
|||||||
static ref BLOCKLIST: RwLock<HashSet<String>> = Default::default();
|
static ref BLOCKLIST: RwLock<HashSet<String>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
static mut DOWNGRADE_THRESHOLD: f64 = 0.66;
|
static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 0.66
|
||||||
static mut DOWNGRADE_START_CHECK: usize = 1800_000; // in ms
|
static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // in ms
|
||||||
static mut LIMIT_SPEED: usize = 4 * 1024 * 1024; // in bit/s
|
static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // in bit/s
|
||||||
static mut TOTAL_BANDWIDTH: usize = 1024 * 1024 * 1024; // in bit/s
|
static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024); // in bit/s
|
||||||
static mut SINGLE_BANDWIDTH: usize = 16 * 1024 * 1024; // in bit/s
|
static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024); // in bit/s
|
||||||
const BLACKLIST_FILE: &'static str = "blacklist.txt";
|
const BLACKLIST_FILE: &str = "blacklist.txt";
|
||||||
const BLOCKLIST_FILE: &'static str = "blocklist.txt";
|
const BLOCKLIST_FILE: &str = "blocklist.txt";
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
||||||
@@ -50,8 +51,8 @@ pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
|||||||
if let Ok(mut file) = std::fs::File::open(BLACKLIST_FILE) {
|
if let Ok(mut file) = std::fs::File::open(BLACKLIST_FILE) {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
if file.read_to_string(&mut contents).is_ok() {
|
if file.read_to_string(&mut contents).is_ok() {
|
||||||
for x in contents.split("\n") {
|
for x in contents.split('\n') {
|
||||||
if let Some(ip) = x.trim().split(' ').nth(0) {
|
if let Some(ip) = x.trim().split(' ').next() {
|
||||||
BLACKLIST.write().await.insert(ip.to_owned());
|
BLACKLIST.write().await.insert(ip.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,8 +66,8 @@ pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
|||||||
if let Ok(mut file) = std::fs::File::open(BLOCKLIST_FILE) {
|
if let Ok(mut file) = std::fs::File::open(BLOCKLIST_FILE) {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
if file.read_to_string(&mut contents).is_ok() {
|
if file.read_to_string(&mut contents).is_ok() {
|
||||||
for x in contents.split("\n") {
|
for x in contents.split('\n') {
|
||||||
if let Some(ip) = x.trim().split(' ').nth(0) {
|
if let Some(ip) = x.trim().split(' ').next() {
|
||||||
BLOCKLIST.write().await.insert(ip.to_owned());
|
BLOCKLIST.write().await.insert(ip.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,19 +78,21 @@ pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
|||||||
BLOCKLIST_FILE,
|
BLOCKLIST_FILE,
|
||||||
BLOCKLIST.read().await.len()
|
BLOCKLIST.read().await.len()
|
||||||
);
|
);
|
||||||
let addr = format!("0.0.0.0:{}", port);
|
let port: u16 = port.parse()?;
|
||||||
log::info!("Listening on tcp {}", addr);
|
log::info!("Listening on tcp :{}", port);
|
||||||
let addr2 = format!("0.0.0.0:{}", port.parse::<u16>().unwrap() + 2);
|
let port2 = port + 2;
|
||||||
log::info!("Listening on websocket {}", addr2);
|
log::info!("Listening on websocket :{}", port2);
|
||||||
loop {
|
let main_task = async move {
|
||||||
log::info!("Start");
|
loop {
|
||||||
io_loop(
|
log::info!("Start");
|
||||||
new_listener(&addr, false).await?,
|
io_loop(listen_any(port).await?, listen_any(port2).await?, &key).await;
|
||||||
new_listener(&addr2, false).await?,
|
}
|
||||||
&key,
|
};
|
||||||
)
|
let listen_signal = crate::common::listen_signal();
|
||||||
.await;
|
tokio::select!(
|
||||||
}
|
res = main_task => res,
|
||||||
|
res = listen_signal => res,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_params() {
|
fn check_params() {
|
||||||
@@ -97,62 +100,60 @@ fn check_params() {
|
|||||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||||
.unwrap_or(0.);
|
.unwrap_or(0.);
|
||||||
if tmp > 0. {
|
if tmp > 0. {
|
||||||
unsafe {
|
DOWNGRADE_THRESHOLD_100.store((tmp * 100.) as _, Ordering::SeqCst);
|
||||||
DOWNGRADE_THRESHOLD = tmp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unsafe { log::info!("DOWNGRADE_THRESHOLD: {}", DOWNGRADE_THRESHOLD) };
|
log::info!(
|
||||||
|
"DOWNGRADE_THRESHOLD: {}",
|
||||||
|
DOWNGRADE_THRESHOLD_100.load(Ordering::SeqCst) as f64 / 100.
|
||||||
|
);
|
||||||
let tmp = std::env::var("DOWNGRADE_START_CHECK")
|
let tmp = std::env::var("DOWNGRADE_START_CHECK")
|
||||||
.map(|x| x.parse::<usize>().unwrap_or(0))
|
.map(|x| x.parse::<usize>().unwrap_or(0))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
if tmp > 0 {
|
if tmp > 0 {
|
||||||
unsafe {
|
DOWNGRADE_START_CHECK.store(tmp * 1000, Ordering::SeqCst);
|
||||||
DOWNGRADE_START_CHECK = tmp * 1000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unsafe { log::info!("DOWNGRADE_START_CHECK: {}s", DOWNGRADE_START_CHECK / 1000) };
|
log::info!(
|
||||||
|
"DOWNGRADE_START_CHECK: {}s",
|
||||||
|
DOWNGRADE_START_CHECK.load(Ordering::SeqCst) / 1000
|
||||||
|
);
|
||||||
let tmp = std::env::var("LIMIT_SPEED")
|
let tmp = std::env::var("LIMIT_SPEED")
|
||||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||||
.unwrap_or(0.);
|
.unwrap_or(0.);
|
||||||
if tmp > 0. {
|
if tmp > 0. {
|
||||||
unsafe {
|
LIMIT_SPEED.store((tmp * 1024. * 1024.) as usize, Ordering::SeqCst);
|
||||||
LIMIT_SPEED = (tmp * 1024. * 1024.) as usize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unsafe { log::info!("LIMIT_SPEED: {}Mb/s", LIMIT_SPEED as f64 / 1024. / 1024.) };
|
log::info!(
|
||||||
|
"LIMIT_SPEED: {}Mb/s",
|
||||||
|
LIMIT_SPEED.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||||
|
);
|
||||||
let tmp = std::env::var("TOTAL_BANDWIDTH")
|
let tmp = std::env::var("TOTAL_BANDWIDTH")
|
||||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||||
.unwrap_or(0.);
|
.unwrap_or(0.);
|
||||||
if tmp > 0. {
|
if tmp > 0. {
|
||||||
unsafe {
|
TOTAL_BANDWIDTH.store((tmp * 1024. * 1024.) as usize, Ordering::SeqCst);
|
||||||
TOTAL_BANDWIDTH = (tmp * 1024. * 1024.) as usize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unsafe {
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"TOTAL_BANDWIDTH: {}Mb/s",
|
"TOTAL_BANDWIDTH: {}Mb/s",
|
||||||
TOTAL_BANDWIDTH as f64 / 1024. / 1024.
|
TOTAL_BANDWIDTH.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||||
)
|
);
|
||||||
};
|
|
||||||
let tmp = std::env::var("SINGLE_BANDWIDTH")
|
let tmp = std::env::var("SINGLE_BANDWIDTH")
|
||||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||||
.unwrap_or(0.);
|
.unwrap_or(0.);
|
||||||
if tmp > 0. {
|
if tmp > 0. {
|
||||||
unsafe {
|
SINGLE_BANDWIDTH.store((tmp * 1024. * 1024.) as usize, Ordering::SeqCst);
|
||||||
SINGLE_BANDWIDTH = (tmp * 1024. * 1024.) as usize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unsafe {
|
log::info!(
|
||||||
log::info!(
|
"SINGLE_BANDWIDTH: {}Mb/s",
|
||||||
"SINGLE_BANDWIDTH: {}Mb/s",
|
SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||||
SINGLE_BANDWIDTH as f64 / 1024. / 1024.
|
)
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
let mut res = "".to_owned();
|
let mut res = "".to_owned();
|
||||||
let mut fds = cmd.trim().split(" ");
|
let mut fds = cmd.trim().split(' ');
|
||||||
match fds.next() {
|
match fds.next() {
|
||||||
Some("h") => {
|
Some("h") => {
|
||||||
res = format!(
|
res = format!(
|
||||||
@@ -173,7 +174,7 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
}
|
}
|
||||||
Some("blacklist-add" | "ba") => {
|
Some("blacklist-add" | "ba") => {
|
||||||
if let Some(ip) = fds.next() {
|
if let Some(ip) = fds.next() {
|
||||||
for ip in ip.split("|") {
|
for ip in ip.split('|') {
|
||||||
BLACKLIST.write().await.insert(ip.to_owned());
|
BLACKLIST.write().await.insert(ip.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +184,7 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
if ip == "all" {
|
if ip == "all" {
|
||||||
BLACKLIST.write().await.clear();
|
BLACKLIST.write().await.clear();
|
||||||
} else {
|
} else {
|
||||||
for ip in ip.split("|") {
|
for ip in ip.split('|') {
|
||||||
BLACKLIST.write().await.remove(ip);
|
BLACKLIST.write().await.remove(ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,13 +195,13 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
res = format!("{}\n", BLACKLIST.read().await.get(ip).is_some());
|
res = format!("{}\n", BLACKLIST.read().await.get(ip).is_some());
|
||||||
} else {
|
} else {
|
||||||
for ip in BLACKLIST.read().await.clone().into_iter() {
|
for ip in BLACKLIST.read().await.clone().into_iter() {
|
||||||
res += &format!("{}\n", ip);
|
let _ = writeln!(res, "{ip}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("blocklist-add" | "Ba") => {
|
Some("blocklist-add" | "Ba") => {
|
||||||
if let Some(ip) = fds.next() {
|
if let Some(ip) = fds.next() {
|
||||||
for ip in ip.split("|") {
|
for ip in ip.split('|') {
|
||||||
BLOCKLIST.write().await.insert(ip.to_owned());
|
BLOCKLIST.write().await.insert(ip.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,7 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
if ip == "all" {
|
if ip == "all" {
|
||||||
BLOCKLIST.write().await.clear();
|
BLOCKLIST.write().await.clear();
|
||||||
} else {
|
} else {
|
||||||
for ip in ip.split("|") {
|
for ip in ip.split('|') {
|
||||||
BLOCKLIST.write().await.remove(ip);
|
BLOCKLIST.write().await.remove(ip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
res = format!("{}\n", BLOCKLIST.read().await.get(ip).is_some());
|
res = format!("{}\n", BLOCKLIST.read().await.get(ip).is_some());
|
||||||
} else {
|
} else {
|
||||||
for ip in BLOCKLIST.read().await.clone().into_iter() {
|
for ip in BLOCKLIST.read().await.clone().into_iter() {
|
||||||
res += &format!("{}\n", ip);
|
let _ = writeln!(res, "{ip}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,76 +230,68 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
if let Some(v) = fds.next() {
|
if let Some(v) = fds.next() {
|
||||||
if let Ok(v) = v.parse::<f64>() {
|
if let Ok(v) = v.parse::<f64>() {
|
||||||
if v > 0. {
|
if v > 0. {
|
||||||
unsafe {
|
DOWNGRADE_THRESHOLD_100.store((v * 100.) as _, Ordering::SeqCst);
|
||||||
DOWNGRADE_THRESHOLD = v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
res = format!(
|
||||||
res = format!("{}\n", DOWNGRADE_THRESHOLD);
|
"{}\n",
|
||||||
}
|
DOWNGRADE_THRESHOLD_100.load(Ordering::SeqCst) as f64 / 100.
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("downgrade-start-check" | "t") => {
|
Some("downgrade-start-check" | "t") => {
|
||||||
if let Some(v) = fds.next() {
|
if let Some(v) = fds.next() {
|
||||||
if let Ok(v) = v.parse::<usize>() {
|
if let Ok(v) = v.parse::<usize>() {
|
||||||
if v > 0 {
|
if v > 0 {
|
||||||
unsafe {
|
DOWNGRADE_START_CHECK.store(v * 1000, Ordering::SeqCst);
|
||||||
DOWNGRADE_START_CHECK = v * 1000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
res = format!("{}s\n", DOWNGRADE_START_CHECK.load(Ordering::SeqCst) / 1000);
|
||||||
res = format!("{}s\n", DOWNGRADE_START_CHECK / 1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("limit-speed" | "ls") => {
|
Some("limit-speed" | "ls") => {
|
||||||
if let Some(v) = fds.next() {
|
if let Some(v) = fds.next() {
|
||||||
if let Ok(v) = v.parse::<f64>() {
|
if let Ok(v) = v.parse::<f64>() {
|
||||||
if v > 0. {
|
if v > 0. {
|
||||||
unsafe {
|
LIMIT_SPEED.store((v * 1024. * 1024.) as _, Ordering::SeqCst);
|
||||||
LIMIT_SPEED = (v * 1024. * 1024.) as _;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
res = format!(
|
||||||
res = format!("{}Mb/s\n", LIMIT_SPEED as f64 / 1024. / 1024.);
|
"{}Mb/s\n",
|
||||||
}
|
LIMIT_SPEED.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("total-bandwidth" | "tb") => {
|
Some("total-bandwidth" | "tb") => {
|
||||||
if let Some(v) = fds.next() {
|
if let Some(v) = fds.next() {
|
||||||
if let Ok(v) = v.parse::<f64>() {
|
if let Ok(v) = v.parse::<f64>() {
|
||||||
if v > 0. {
|
if v > 0. {
|
||||||
unsafe {
|
TOTAL_BANDWIDTH.store((v * 1024. * 1024.) as _, Ordering::SeqCst);
|
||||||
TOTAL_BANDWIDTH = (v * 1024. * 1024.) as _;
|
limiter.set_speed_limit(TOTAL_BANDWIDTH.load(Ordering::SeqCst) as _);
|
||||||
limiter.set_speed_limit(TOTAL_BANDWIDTH as _);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
res = format!(
|
||||||
res = format!("{}Mb/s\n", TOTAL_BANDWIDTH as f64 / 1024. / 1024.);
|
"{}Mb/s\n",
|
||||||
}
|
TOTAL_BANDWIDTH.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("single-bandwidth" | "sb") => {
|
Some("single-bandwidth" | "sb") => {
|
||||||
if let Some(v) = fds.next() {
|
if let Some(v) = fds.next() {
|
||||||
if let Ok(v) = v.parse::<f64>() {
|
if let Ok(v) = v.parse::<f64>() {
|
||||||
if v > 0. {
|
if v > 0. {
|
||||||
unsafe {
|
SINGLE_BANDWIDTH.store((v * 1024. * 1024.) as _, Ordering::SeqCst);
|
||||||
SINGLE_BANDWIDTH = (v * 1024. * 1024.) as _;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
res = format!(
|
||||||
res = format!("{}Mb/s\n", SINGLE_BANDWIDTH as f64 / 1024. / 1024.);
|
"{}Mb/s\n",
|
||||||
}
|
SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("usage" | "u") => {
|
Some("usage" | "u") => {
|
||||||
@@ -306,15 +299,16 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| (x.0.clone(), x.1.clone()))
|
.map(|x| (x.0.clone(), *x.1))
|
||||||
.collect();
|
.collect();
|
||||||
tmp.sort_by(|a, b| ((b.1).1).partial_cmp(&(a.1).1).unwrap());
|
tmp.sort_by(|a, b| ((b.1).1).partial_cmp(&(a.1).1).unwrap());
|
||||||
for (ip, (elapsed, total, highest, speed)) in tmp {
|
for (ip, (elapsed, total, highest, speed)) in tmp {
|
||||||
if elapsed <= 0 {
|
if elapsed == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
res += &format!(
|
let _ = writeln!(
|
||||||
"{}: {}s {:.2}MB {}kb/s {}kb/s {}kb/s\n",
|
res,
|
||||||
|
"{}: {}s {:.2}MB {}kb/s {}kb/s {}kb/s",
|
||||||
ip,
|
ip,
|
||||||
elapsed / 1000,
|
elapsed / 1000,
|
||||||
total as f64 / 1024. / 1024. / 8.,
|
total as f64 / 1024. / 1024. / 8.,
|
||||||
@@ -331,7 +325,7 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
|||||||
|
|
||||||
async fn io_loop(listener: TcpListener, listener2: TcpListener, key: &str) {
|
async fn io_loop(listener: TcpListener, listener2: TcpListener, key: &str) {
|
||||||
check_params();
|
check_params();
|
||||||
let limiter = <Limiter>::new(unsafe { TOTAL_BANDWIDTH as _ });
|
let limiter = <Limiter>::new(TOTAL_BANDWIDTH.load(Ordering::SeqCst) as _);
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = listener.accept() => {
|
res = listener.accept() => {
|
||||||
@@ -369,12 +363,12 @@ async fn handle_connection(
|
|||||||
key: &str,
|
key: &str,
|
||||||
ws: bool,
|
ws: bool,
|
||||||
) {
|
) {
|
||||||
let ip = addr.ip().to_string();
|
let ip = hbb_common::try_into_v4(addr).ip();
|
||||||
if !ws && ip == "127.0.0.1" {
|
if !ws && ip.is_loopback() {
|
||||||
let limiter = limiter.clone();
|
let limiter = limiter.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stream = stream;
|
let mut stream = stream;
|
||||||
let mut buffer = [0; 64];
|
let mut buffer = [0; 1024];
|
||||||
if let Ok(Ok(n)) = timeout(1000, stream.read(&mut buffer[..])).await {
|
if let Ok(Ok(n)) = timeout(1000, stream.read(&mut buffer[..])).await {
|
||||||
if let Ok(data) = std::str::from_utf8(&buffer[..n]) {
|
if let Ok(data) = std::str::from_utf8(&buffer[..n]) {
|
||||||
let res = check_cmd(data, limiter).await;
|
let res = check_cmd(data, limiter).await;
|
||||||
@@ -384,6 +378,7 @@ async fn handle_connection(
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let ip = ip.to_string();
|
||||||
if BLOCKLIST.read().await.get(&ip).is_some() {
|
if BLOCKLIST.read().await.get(&ip).is_some() {
|
||||||
log::info!("{} blocked", ip);
|
log::info!("{} blocked", ip);
|
||||||
return;
|
return;
|
||||||
@@ -397,19 +392,30 @@ async fn handle_connection(
|
|||||||
|
|
||||||
async fn make_pair(
|
async fn make_pair(
|
||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
addr: SocketAddr,
|
mut addr: SocketAddr,
|
||||||
key: &str,
|
key: &str,
|
||||||
limiter: Limiter,
|
limiter: Limiter,
|
||||||
ws: bool,
|
ws: bool,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
if ws {
|
if ws {
|
||||||
make_pair_(
|
use tokio_tungstenite::tungstenite::handshake::server::{Request, Response};
|
||||||
tokio_tungstenite::accept_async(stream).await?,
|
let callback = |req: &Request, response: Response| {
|
||||||
addr,
|
let headers = req.headers();
|
||||||
key,
|
let real_ip = headers
|
||||||
limiter,
|
.get("X-Real-IP")
|
||||||
)
|
.or_else(|| headers.get("X-Forwarded-For"))
|
||||||
.await;
|
.and_then(|header_value| header_value.to_str().ok());
|
||||||
|
if let Some(ip) = real_ip {
|
||||||
|
if ip.contains('.') {
|
||||||
|
addr = format!("{ip}:0").parse().unwrap_or(addr);
|
||||||
|
} else {
|
||||||
|
addr = format!("[{ip}]:0").parse().unwrap_or(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(response)
|
||||||
|
};
|
||||||
|
let ws_stream = tokio_tungstenite::accept_hdr_async(stream, callback).await?;
|
||||||
|
make_pair_(ws_stream, addr, key, limiter).await;
|
||||||
} else {
|
} else {
|
||||||
make_pair_(FramedStream::from(stream, addr), addr, key, limiter).await;
|
make_pair_(FramedStream::from(stream, addr), addr, key, limiter).await;
|
||||||
}
|
}
|
||||||
@@ -422,6 +428,7 @@ async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limit
|
|||||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||||
if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union {
|
if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union {
|
||||||
if !key.is_empty() && rf.licence_key != key {
|
if !key.is_empty() && rf.licence_key != key {
|
||||||
|
log::warn!("Relay authentication failed from {} - invalid key", addr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !rf.uuid.is_empty() {
|
if !rf.uuid.is_empty() {
|
||||||
@@ -469,10 +476,11 @@ async fn relay(
|
|||||||
let mut highest_s = 0;
|
let mut highest_s = 0;
|
||||||
let mut downgrade: bool = false;
|
let mut downgrade: bool = false;
|
||||||
let mut blacked: bool = false;
|
let mut blacked: bool = false;
|
||||||
let limiter = <Limiter>::new(unsafe { SINGLE_BANDWIDTH as _ });
|
let sb = SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64;
|
||||||
let blacklist_limiter = <Limiter>::new(unsafe { LIMIT_SPEED as _ });
|
let limiter = <Limiter>::new(sb);
|
||||||
|
let blacklist_limiter = <Limiter>::new(LIMIT_SPEED.load(Ordering::SeqCst) as _);
|
||||||
let downgrade_threshold =
|
let downgrade_threshold =
|
||||||
(unsafe { SINGLE_BANDWIDTH as f64 * DOWNGRADE_THRESHOLD } / 1000.) as usize; // in bit/ms
|
(sb * DOWNGRADE_THRESHOLD_100.load(Ordering::SeqCst) as f64 / 100. / 1000.) as usize; // in bit/ms
|
||||||
let mut timer = interval(Duration::from_secs(3));
|
let mut timer = interval(Duration::from_secs(3));
|
||||||
let mut last_recv_time = std::time::Instant::now();
|
let mut last_recv_time = std::time::Instant::now();
|
||||||
loop {
|
loop {
|
||||||
@@ -489,7 +497,7 @@ async fn relay(
|
|||||||
total_limiter.consume(nb).await;
|
total_limiter.consume(nb).await;
|
||||||
total += nb;
|
total += nb;
|
||||||
total_s += nb;
|
total_s += nb;
|
||||||
if bytes.len() > 0 {
|
if !bytes.is_empty() {
|
||||||
stream.send_raw(bytes.into()).await?;
|
stream.send_raw(bytes.into()).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -508,7 +516,7 @@ async fn relay(
|
|||||||
total_limiter.consume(nb).await;
|
total_limiter.consume(nb).await;
|
||||||
total += nb;
|
total += nb;
|
||||||
total_s += nb;
|
total_s += nb;
|
||||||
if bytes.len() > 0 {
|
if !bytes.is_empty() {
|
||||||
peer.send_raw(bytes.into()).await?;
|
peer.send_raw(bytes.into()).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -530,7 +538,7 @@ async fn relay(
|
|||||||
}
|
}
|
||||||
blacked = BLACKLIST.read().await.get(&ip).is_some();
|
blacked = BLACKLIST.read().await.get(&ip).is_some();
|
||||||
tm = std::time::Instant::now();
|
tm = std::time::Instant::now();
|
||||||
let speed = total_s / (n as usize);
|
let speed = total_s / n;
|
||||||
if speed > highest_s {
|
if speed > highest_s {
|
||||||
highest_s = speed;
|
highest_s = speed;
|
||||||
}
|
}
|
||||||
@@ -540,16 +548,17 @@ async fn relay(
|
|||||||
(elapsed as _, total as _, highest_s as _, speed as _),
|
(elapsed as _, total as _, highest_s as _, speed as _),
|
||||||
);
|
);
|
||||||
total_s = 0;
|
total_s = 0;
|
||||||
if elapsed > unsafe { DOWNGRADE_START_CHECK } && !downgrade {
|
if elapsed > DOWNGRADE_START_CHECK.load(Ordering::SeqCst)
|
||||||
if total > elapsed * downgrade_threshold {
|
&& !downgrade
|
||||||
downgrade = true;
|
&& total > elapsed * downgrade_threshold
|
||||||
log::info!(
|
{
|
||||||
"Downgrade {}, exceed downgrade threshold {}bit/ms in {}ms",
|
downgrade = true;
|
||||||
id,
|
log::info!(
|
||||||
downgrade_threshold,
|
"Downgrade {}, exceed downgrade threshold {}bit/ms in {}ms",
|
||||||
elapsed
|
id,
|
||||||
);
|
downgrade_threshold,
|
||||||
}
|
elapsed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::peer::*;
|
use crate::peer::*;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err, bail,
|
||||||
bytes::{Bytes, BytesMut},
|
bytes::{Bytes, BytesMut},
|
||||||
bytes_codec::BytesCodec,
|
bytes_codec::BytesCodec,
|
||||||
|
config,
|
||||||
futures::future::join_all,
|
futures::future::join_all,
|
||||||
futures_util::{
|
futures_util::{
|
||||||
sink::SinkExt,
|
sink::SinkExt,
|
||||||
@@ -15,7 +16,7 @@ use hbb_common::{
|
|||||||
register_pk_response::Result::{TOO_FREQUENT, UUID_MISMATCH},
|
register_pk_response::Result::{TOO_FREQUENT, UUID_MISMATCH},
|
||||||
*,
|
*,
|
||||||
},
|
},
|
||||||
tcp::{new_listener, FramedStream},
|
tcp::{listen_any, FramedStream},
|
||||||
timeout,
|
timeout,
|
||||||
tokio::{
|
tokio::{
|
||||||
self,
|
self,
|
||||||
@@ -25,6 +26,7 @@ use hbb_common::{
|
|||||||
time::{interval, Duration},
|
time::{interval, Duration},
|
||||||
},
|
},
|
||||||
tokio_util::codec::Framed,
|
tokio_util::codec::Framed,
|
||||||
|
try_into_v4,
|
||||||
udp::FramedSocket,
|
udp::FramedSocket,
|
||||||
AddrMangle, ResultType,
|
AddrMangle, ResultType,
|
||||||
};
|
};
|
||||||
@@ -32,15 +34,15 @@ use ipnetwork::Ipv4Network;
|
|||||||
use sodiumoxide::crypto::sign;
|
use sodiumoxide::crypto::sign;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
|
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
const ADDR_127: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum Data {
|
enum Data {
|
||||||
Msg(RendezvousMessage, SocketAddr),
|
Msg(Box<RendezvousMessage>, SocketAddr),
|
||||||
RelayServers0(String),
|
RelayServers0(String),
|
||||||
RelayServers(RelayServers),
|
RelayServers(RelayServers),
|
||||||
}
|
}
|
||||||
@@ -54,10 +56,18 @@ enum Sink {
|
|||||||
}
|
}
|
||||||
type Sender = mpsc::UnboundedSender<Data>;
|
type Sender = mpsc::UnboundedSender<Data>;
|
||||||
type Receiver = mpsc::UnboundedReceiver<Data>;
|
type Receiver = mpsc::UnboundedReceiver<Data>;
|
||||||
static mut ROTATION_RELAY_SERVER: usize = 0;
|
static ROTATION_RELAY_SERVER: AtomicUsize = AtomicUsize::new(0);
|
||||||
type RelayServers = Vec<String>;
|
type RelayServers = Vec<String>;
|
||||||
static CHECK_RELAY_TIMEOUT: u64 = 3_000;
|
const CHECK_RELAY_TIMEOUT: u64 = 3_000;
|
||||||
static mut ALWAYS_USE_RELAY: bool = false;
|
static ALWAYS_USE_RELAY: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
// Store punch hole requests
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use tokio::sync::Mutex as TokioMutex; // differentiate if needed
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct PunchReqEntry { tm: Instant, from_ip: String, to_ip: String, to_id: String }
|
||||||
|
static PUNCH_REQS: Lazy<TokioMutex<Vec<PunchReqEntry>>> = Lazy::new(|| TokioMutex::new(Vec::new()));
|
||||||
|
const PUNCH_REQ_DEDUPE_SEC: u64 = 60;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
@@ -91,16 +101,15 @@ impl RendezvousServer {
|
|||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
pub async fn start(port: i32, serial: i32, key: &str, rmem: usize) -> ResultType<()> {
|
pub async fn start(port: i32, serial: i32, key: &str, rmem: usize) -> ResultType<()> {
|
||||||
let (key, sk) = Self::get_server_sk(key);
|
let (key, sk) = Self::get_server_sk(key);
|
||||||
let addr = format!("0.0.0.0:{}", port);
|
let nat_port = port - 1;
|
||||||
let addr2 = format!("0.0.0.0:{}", port - 1);
|
let ws_port = port + 2;
|
||||||
let addr3 = format!("0.0.0.0:{}", port + 2);
|
|
||||||
let pm = PeerMap::new().await?;
|
let pm = PeerMap::new().await?;
|
||||||
log::info!("serial={}", serial);
|
log::info!("serial={}", serial);
|
||||||
let rendezvous_servers = get_servers(&get_arg("rendezvous-servers"), "rendezvous-servers");
|
let rendezvous_servers = get_servers(&get_arg("rendezvous-servers"), "rendezvous-servers");
|
||||||
log::info!("Listening on tcp/udp {}", addr);
|
log::info!("Listening on tcp/udp :{}", port);
|
||||||
log::info!("Listening on tcp {}, extra port for NAT test", addr2);
|
log::info!("Listening on tcp :{}, extra port for NAT test", nat_port);
|
||||||
log::info!("Listening on websocket {}", addr3);
|
log::info!("Listening on websocket :{}", ws_port);
|
||||||
let mut socket = FramedSocket::new_with_buf_size(&addr, rmem).await?;
|
let mut socket = create_udp_listener(port, rmem).await?;
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
||||||
let software_url = get_arg("software-url");
|
let software_url = get_arg("software-url");
|
||||||
let version = hbb_common::get_version_from_url(&software_url);
|
let version = hbb_common::get_version_from_url(&software_url);
|
||||||
@@ -138,69 +147,85 @@ impl RendezvousServer {
|
|||||||
log::info!("local-ip: {:?}", rs.inner.local_ip);
|
log::info!("local-ip: {:?}", rs.inner.local_ip);
|
||||||
std::env::set_var("PORT_FOR_API", port.to_string());
|
std::env::set_var("PORT_FOR_API", port.to_string());
|
||||||
rs.parse_relay_servers(&get_arg("relay-servers"));
|
rs.parse_relay_servers(&get_arg("relay-servers"));
|
||||||
let mut listener = new_listener(&addr, false).await?;
|
let mut listener = create_tcp_listener(port).await?;
|
||||||
let mut listener2 = new_listener(&addr2, false).await?;
|
let mut listener2 = create_tcp_listener(nat_port).await?;
|
||||||
let mut listener3 = new_listener(&addr3, false).await?;
|
let mut listener3 = create_tcp_listener(ws_port).await?;
|
||||||
let test_addr = std::env::var("TEST_HBBS").unwrap_or_default();
|
let test_addr = std::env::var("TEST_HBBS").unwrap_or_default();
|
||||||
if std::env::var("ALWAYS_USE_RELAY")
|
if std::env::var("ALWAYS_USE_RELAY")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_uppercase()
|
.to_uppercase()
|
||||||
== "Y"
|
== "Y"
|
||||||
{
|
{
|
||||||
unsafe {
|
ALWAYS_USE_RELAY.store(true, Ordering::SeqCst);
|
||||||
ALWAYS_USE_RELAY = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log::info!(
|
log::info!(
|
||||||
"ALWAYS_USE_RELAY={}",
|
"ALWAYS_USE_RELAY={}",
|
||||||
if unsafe { ALWAYS_USE_RELAY } {
|
if ALWAYS_USE_RELAY.load(Ordering::SeqCst) {
|
||||||
"Y"
|
"Y"
|
||||||
} else {
|
} else {
|
||||||
"N"
|
"N"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if test_addr.to_lowercase() != "no" {
|
if test_addr.to_lowercase() != "no" {
|
||||||
let test_addr = (if test_addr.is_empty() {
|
let test_addr = if test_addr.is_empty() {
|
||||||
addr.replace("0.0.0.0", "127.0.0.1")
|
listener.local_addr()?
|
||||||
} else {
|
} else {
|
||||||
test_addr
|
test_addr.parse()?
|
||||||
})
|
};
|
||||||
.parse::<SocketAddr>()?;
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
allow_err!(test_hbbs(test_addr).await);
|
if let Err(err) = test_hbbs(test_addr).await {
|
||||||
|
if test_addr.is_ipv6() && test_addr.ip().is_unspecified() {
|
||||||
|
let mut test_addr = test_addr;
|
||||||
|
test_addr.set_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
||||||
|
if let Err(err) = test_hbbs(test_addr).await {
|
||||||
|
log::error!("Failed to run hbbs test with {test_addr}: {err}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to run hbbs test with {test_addr}: {err}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
loop {
|
let main_task = async move {
|
||||||
log::info!("Start");
|
loop {
|
||||||
match rs
|
log::info!("Start");
|
||||||
.io_loop(
|
match rs
|
||||||
&mut rx,
|
.io_loop(
|
||||||
&mut listener,
|
&mut rx,
|
||||||
&mut listener2,
|
&mut listener,
|
||||||
&mut listener3,
|
&mut listener2,
|
||||||
&mut socket,
|
&mut listener3,
|
||||||
&key,
|
&mut socket,
|
||||||
)
|
&key,
|
||||||
.await
|
)
|
||||||
{
|
.await
|
||||||
LoopFailure::UdpSocket => {
|
{
|
||||||
drop(socket);
|
LoopFailure::UdpSocket => {
|
||||||
socket = FramedSocket::new_with_buf_size(&addr, rmem).await?;
|
drop(socket);
|
||||||
}
|
socket = create_udp_listener(port, rmem).await?;
|
||||||
LoopFailure::Listener => {
|
}
|
||||||
drop(listener);
|
LoopFailure::Listener => {
|
||||||
listener = new_listener(&addr, false).await?;
|
drop(listener);
|
||||||
}
|
listener = create_tcp_listener(port).await?;
|
||||||
LoopFailure::Listener2 => {
|
}
|
||||||
drop(listener2);
|
LoopFailure::Listener2 => {
|
||||||
listener2 = new_listener(&addr2, false).await?;
|
drop(listener2);
|
||||||
}
|
listener2 = create_tcp_listener(nat_port).await?;
|
||||||
LoopFailure::Listener3 => {
|
}
|
||||||
drop(listener3);
|
LoopFailure::Listener3 => {
|
||||||
listener3 = new_listener(&addr3, false).await?;
|
drop(listener3);
|
||||||
|
listener3 = create_tcp_listener(ws_port).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
let listen_signal = listen_signal();
|
||||||
|
tokio::select!(
|
||||||
|
res = main_task => res,
|
||||||
|
res = listen_signal => res,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn io_loop(
|
async fn io_loop(
|
||||||
@@ -226,7 +251,7 @@ impl RendezvousServer {
|
|||||||
}
|
}
|
||||||
Some(data) = rx.recv() => {
|
Some(data) = rx.recv() => {
|
||||||
match data {
|
match data {
|
||||||
Data::Msg(msg, addr) => { allow_err!(socket.send(&msg, addr).await); }
|
Data::Msg(msg, addr) => { allow_err!(socket.send(msg.as_ref(), addr).await); }
|
||||||
Data::RelayServers0(rs) => { self.parse_relay_servers(&rs); }
|
Data::RelayServers0(rs) => { self.parse_relay_servers(&rs); }
|
||||||
Data::RelayServers(rs) => { self.relay_servers = Arc::new(rs); }
|
Data::RelayServers(rs) => { self.relay_servers = Arc::new(rs); }
|
||||||
}
|
}
|
||||||
@@ -296,11 +321,11 @@ impl RendezvousServer {
|
|||||||
socket: &mut FramedSocket,
|
socket: &mut FramedSocket,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(bytes) {
|
||||||
match msg_in.union {
|
match msg_in.union {
|
||||||
Some(rendezvous_message::Union::RegisterPeer(rp)) => {
|
Some(rendezvous_message::Union::RegisterPeer(rp)) => {
|
||||||
// B registered
|
// B registered
|
||||||
if rp.id.len() > 0 {
|
if !rp.id.is_empty() {
|
||||||
log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr);
|
log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr);
|
||||||
self.update_addr(rp.id, addr, socket).await?;
|
self.update_addr(rp.id, addr, socket).await?;
|
||||||
if self.inner.serial > rp.serial {
|
if self.inner.serial > rp.serial {
|
||||||
@@ -377,12 +402,10 @@ impl RendezvousServer {
|
|||||||
*tm = Instant::now();
|
*tm = Instant::now();
|
||||||
ips.clear();
|
ips.clear();
|
||||||
ips.insert(ip.clone(), 1);
|
ips.insert(ip.clone(), 1);
|
||||||
|
} else if let Some(v) = ips.get_mut(&ip) {
|
||||||
|
*v += 1;
|
||||||
} else {
|
} else {
|
||||||
if let Some(v) = ips.get_mut(&ip) {
|
ips.insert(ip.clone(), 1);
|
||||||
*v += 1;
|
|
||||||
} else {
|
|
||||||
ips.insert(ip.clone(), 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lock.insert(
|
lock.insert(
|
||||||
@@ -420,7 +443,7 @@ impl RendezvousServer {
|
|||||||
self.handle_local_addr(la, addr, Some(socket)).await?;
|
self.handle_local_addr(la, addr, Some(socket)).await?;
|
||||||
}
|
}
|
||||||
Some(rendezvous_message::Union::ConfigureUpdate(mut cu)) => {
|
Some(rendezvous_message::Union::ConfigureUpdate(mut cu)) => {
|
||||||
if addr.ip() == ADDR_127 && cu.serial > self.inner.serial {
|
if try_into_v4(addr).ip().is_loopback() && cu.serial > self.inner.serial {
|
||||||
let mut inner: Inner = (*self.inner).clone();
|
let mut inner: Inner = (*self.inner).clone();
|
||||||
inner.serial = cu.serial;
|
inner.serial = cu.serial;
|
||||||
self.inner = Arc::new(inner);
|
self.inner = Arc::new(inner);
|
||||||
@@ -465,27 +488,27 @@ impl RendezvousServer {
|
|||||||
key: &str,
|
key: &str,
|
||||||
ws: bool,
|
ws: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(bytes) {
|
||||||
match msg_in.union {
|
match msg_in.union {
|
||||||
Some(rendezvous_message::Union::PunchHoleRequest(ph)) => {
|
Some(rendezvous_message::Union::PunchHoleRequest(ph)) => {
|
||||||
// there maybe several attempt, so sink can be none
|
// there maybe several attempt, so sink can be none
|
||||||
if let Some(sink) = sink.take() {
|
if let Some(sink) = sink.take() {
|
||||||
self.tcp_punch.lock().await.insert(addr, sink);
|
self.tcp_punch.lock().await.insert(try_into_v4(addr), sink);
|
||||||
}
|
}
|
||||||
allow_err!(self.handle_tcp_punch_hole_request(addr, ph, &key, ws).await);
|
allow_err!(self.handle_tcp_punch_hole_request(addr, ph, key, ws).await);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Some(rendezvous_message::Union::RequestRelay(mut rf)) => {
|
Some(rendezvous_message::Union::RequestRelay(mut rf)) => {
|
||||||
// there maybe several attempt, so sink can be none
|
// there maybe several attempt, so sink can be none
|
||||||
if let Some(sink) = sink.take() {
|
if let Some(sink) = sink.take() {
|
||||||
self.tcp_punch.lock().await.insert(addr, sink);
|
self.tcp_punch.lock().await.insert(try_into_v4(addr), sink);
|
||||||
}
|
}
|
||||||
if let Some(peer) = self.pm.get_in_memory(&rf.id).await {
|
if let Some(peer) = self.pm.get_in_memory(&rf.id).await {
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
rf.socket_addr = AddrMangle::encode(addr).into();
|
rf.socket_addr = AddrMangle::encode(addr).into();
|
||||||
msg_out.set_request_relay(rf);
|
msg_out.set_request_relay(rf);
|
||||||
let peer_addr = peer.read().await.socket_addr;
|
let peer_addr = peer.read().await.socket_addr;
|
||||||
self.tx.send(Data::Msg(msg_out, peer_addr)).ok();
|
self.tx.send(Data::Msg(msg_out.into(), peer_addr)).ok();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -559,7 +582,7 @@ impl RendezvousServer {
|
|||||||
ip != old.socket_addr.ip()
|
ip != old.socket_addr.ip()
|
||||||
} else {
|
} else {
|
||||||
ip.to_string() != old.info.ip
|
ip.to_string() != old.info.ip
|
||||||
} && ip != ADDR_127;
|
} && !ip.is_loopback();
|
||||||
let request_pk = old.pk.is_empty() || ip_change;
|
let request_pk = old.pk.is_empty() || ip_change;
|
||||||
if !request_pk {
|
if !request_pk {
|
||||||
old.socket_addr = socket_addr;
|
old.socket_addr = socket_addr;
|
||||||
@@ -665,6 +688,7 @@ impl RendezvousServer {
|
|||||||
) -> ResultType<(RendezvousMessage, Option<SocketAddr>)> {
|
) -> ResultType<(RendezvousMessage, Option<SocketAddr>)> {
|
||||||
let mut ph = ph;
|
let mut ph = ph;
|
||||||
if !key.is_empty() && ph.licence_key != key {
|
if !key.is_empty() && ph.licence_key != key {
|
||||||
|
log::warn!("Authentication failed from {} for peer {} - invalid key", addr, ph.id);
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
msg_out.set_punch_hole_response(PunchHoleResponse {
|
msg_out.set_punch_hole_response(PunchHoleResponse {
|
||||||
failure: punch_hole_response::Failure::LICENSE_MISMATCH.into(),
|
failure: punch_hole_response::Failure::LICENSE_MISMATCH.into(),
|
||||||
@@ -691,28 +715,42 @@ impl RendezvousServer {
|
|||||||
});
|
});
|
||||||
return Ok((msg_out, None));
|
return Ok((msg_out, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record punch hole request (from addr -> peer id/peer_addr)
|
||||||
|
{
|
||||||
|
let from_ip = try_into_v4(addr).ip().to_string();
|
||||||
|
let to_ip = try_into_v4(peer_addr).ip().to_string();
|
||||||
|
let to_id_clone = id.clone();
|
||||||
|
let mut lock = PUNCH_REQS.lock().await;
|
||||||
|
let mut dup = false;
|
||||||
|
for e in lock.iter().rev().take(30) { // only check recent tail subset for speed
|
||||||
|
if e.from_ip == from_ip && e.to_id == to_id_clone {
|
||||||
|
if e.tm.elapsed().as_secs() < PUNCH_REQ_DEDUPE_SEC { dup = true; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !dup { lock.push(PunchReqEntry { tm: Instant::now(), from_ip, to_ip, to_id: to_id_clone }); }
|
||||||
|
}
|
||||||
|
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
let peer_is_lan = self.is_lan(peer_addr);
|
let peer_is_lan = self.is_lan(peer_addr);
|
||||||
let is_lan = self.is_lan(addr);
|
let is_lan = self.is_lan(addr);
|
||||||
let mut relay_server = self.get_relay_server(addr.ip(), peer_addr.ip());
|
let mut relay_server = self.get_relay_server(addr.ip(), peer_addr.ip());
|
||||||
if unsafe { ALWAYS_USE_RELAY } || (peer_is_lan ^ is_lan) {
|
if ALWAYS_USE_RELAY.load(Ordering::SeqCst) || (peer_is_lan ^ is_lan) {
|
||||||
if peer_is_lan {
|
if peer_is_lan {
|
||||||
// https://github.com/rustdesk/rustdesk-server/issues/24
|
// https://github.com/rustdesk/rustdesk-server/issues/24
|
||||||
relay_server = self.inner.local_ip.clone()
|
relay_server = self.inner.local_ip.clone()
|
||||||
}
|
}
|
||||||
ph.nat_type = NatType::SYMMETRIC.into(); // will force relay
|
ph.nat_type = NatType::SYMMETRIC.into(); // will force relay
|
||||||
}
|
}
|
||||||
let same_intranet = !ws
|
let same_intranet: bool = !ws
|
||||||
&& match peer_addr {
|
&& (peer_is_lan && is_lan || {
|
||||||
SocketAddr::V4(a) => match addr {
|
match (peer_addr, addr) {
|
||||||
SocketAddr::V4(b) => a.ip() == b.ip(),
|
(SocketAddr::V4(a), SocketAddr::V4(b)) => a.ip() == b.ip(),
|
||||||
|
(SocketAddr::V6(a), SocketAddr::V6(b)) => a.ip() == b.ip(),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
}
|
||||||
SocketAddr::V6(a) => match addr {
|
});
|
||||||
SocketAddr::V6(b) => a.ip() == b.ip(),
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let socket_addr = AddrMangle::encode(addr).into();
|
let socket_addr = AddrMangle::encode(addr).into();
|
||||||
if same_intranet {
|
if same_intranet {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
@@ -740,14 +778,14 @@ impl RendezvousServer {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Ok((msg_out, Some(peer_addr)));
|
Ok((msg_out, Some(peer_addr)))
|
||||||
} else {
|
} else {
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
msg_out.set_punch_hole_response(PunchHoleResponse {
|
msg_out.set_punch_hole_response(PunchHoleResponse {
|
||||||
failure: punch_hole_response::Failure::ID_NOT_EXIST.into(),
|
failure: punch_hole_response::Failure::ID_NOT_EXIST.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
return Ok((msg_out, None));
|
Ok((msg_out, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,8 +796,8 @@ impl RendezvousServer {
|
|||||||
peers: Vec<String>,
|
peers: Vec<String>,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let mut states = BytesMut::zeroed((peers.len() + 7) / 8);
|
let mut states = BytesMut::zeroed((peers.len() + 7) / 8);
|
||||||
for i in 0..peers.len() {
|
for (i, peer_id) in peers.iter().enumerate() {
|
||||||
if let Some(peer) = self.pm.get_in_memory(&peers[i]).await {
|
if let Some(peer) = self.pm.get_in_memory(peer_id).await {
|
||||||
let elapsed = peer.read().await.last_reg_time.elapsed().as_millis() as i32;
|
let elapsed = peer.read().await.last_reg_time.elapsed().as_millis() as i32;
|
||||||
// bytes index from left to right
|
// bytes index from left to right
|
||||||
let states_idx = i / 8;
|
let states_idx = i / 8;
|
||||||
@@ -782,7 +820,7 @@ impl RendezvousServer {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn send_to_tcp(&mut self, msg: RendezvousMessage, addr: SocketAddr) {
|
async fn send_to_tcp(&mut self, msg: RendezvousMessage, addr: SocketAddr) {
|
||||||
let mut tcp = self.tcp_punch.lock().await.remove(&addr);
|
let mut tcp = self.tcp_punch.lock().await.remove(&try_into_v4(addr));
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
Self::send_to_sink(&mut tcp, msg).await;
|
Self::send_to_sink(&mut tcp, msg).await;
|
||||||
});
|
});
|
||||||
@@ -810,7 +848,7 @@ impl RendezvousServer {
|
|||||||
msg: RendezvousMessage,
|
msg: RendezvousMessage,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let mut sink = self.tcp_punch.lock().await.remove(&addr);
|
let mut sink = self.tcp_punch.lock().await.remove(&try_into_v4(addr));
|
||||||
Self::send_to_sink(&mut sink, msg).await;
|
Self::send_to_sink(&mut sink, msg).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -825,7 +863,7 @@ impl RendezvousServer {
|
|||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let (msg, to_addr) = self.handle_punch_hole_request(addr, ph, key, ws).await?;
|
let (msg, to_addr) = self.handle_punch_hole_request(addr, ph, key, ws).await?;
|
||||||
if let Some(addr) = to_addr {
|
if let Some(addr) = to_addr {
|
||||||
self.tx.send(Data::Msg(msg, addr))?;
|
self.tx.send(Data::Msg(msg.into(), addr))?;
|
||||||
} else {
|
} else {
|
||||||
self.send_to_tcp_sync(msg, addr).await?;
|
self.send_to_tcp_sync(msg, addr).await?;
|
||||||
}
|
}
|
||||||
@@ -841,7 +879,7 @@ impl RendezvousServer {
|
|||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let (msg, to_addr) = self.handle_punch_hole_request(addr, ph, key, false).await?;
|
let (msg, to_addr) = self.handle_punch_hole_request(addr, ph, key, false).await?;
|
||||||
self.tx.send(Data::Msg(
|
self.tx.send(Data::Msg(
|
||||||
msg,
|
msg.into(),
|
||||||
match to_addr {
|
match to_addr {
|
||||||
Some(addr) => addr,
|
Some(addr) => addr,
|
||||||
None => addr,
|
None => addr,
|
||||||
@@ -892,24 +930,24 @@ impl RendezvousServer {
|
|||||||
} else if self.relay_servers.len() == 1 {
|
} else if self.relay_servers.len() == 1 {
|
||||||
return self.relay_servers[0].clone();
|
return self.relay_servers[0].clone();
|
||||||
}
|
}
|
||||||
let i = unsafe {
|
let i = ROTATION_RELAY_SERVER.fetch_add(1, Ordering::SeqCst) % self.relay_servers.len();
|
||||||
ROTATION_RELAY_SERVER += 1;
|
|
||||||
ROTATION_RELAY_SERVER % self.relay_servers.len()
|
|
||||||
};
|
|
||||||
self.relay_servers[i].clone()
|
self.relay_servers[i].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_cmd(&self, cmd: &str) -> String {
|
async fn check_cmd(&self, cmd: &str) -> String {
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
let mut res = "".to_owned();
|
let mut res = "".to_owned();
|
||||||
let mut fds = cmd.trim().split(" ");
|
let mut fds = cmd.trim().split(' ');
|
||||||
match fds.next() {
|
match fds.next() {
|
||||||
Some("h") => {
|
Some("h") => {
|
||||||
res = format!(
|
res = format!(
|
||||||
"{}\n{}\n{}\n{}\n{}\n{}\n",
|
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
|
||||||
"relay-servers(rs) <separated by ,>",
|
"relay-servers(rs) <separated by ,>",
|
||||||
"reload-geo(rg)",
|
"reload-geo(rg)",
|
||||||
"ip-blocker(ib) [<ip>|<number>] [-]",
|
"ip-blocker(ib) [<ip>|<number>] [-]",
|
||||||
"ip-changes(ic) [<id>|<number>] [-]",
|
"ip-changes(ic) [<id>|<number>] [-]",
|
||||||
|
"punch-requests(pr) [<number>] [-]",
|
||||||
"always-use-relay(aur)",
|
"always-use-relay(aur)",
|
||||||
"test-geo(tg) <ip1> <ip2>"
|
"test-geo(tg) <ip1> <ip2>"
|
||||||
)
|
)
|
||||||
@@ -919,7 +957,7 @@ impl RendezvousServer {
|
|||||||
self.tx.send(Data::RelayServers0(rs.to_owned())).ok();
|
self.tx.send(Data::RelayServers0(rs.to_owned())).ok();
|
||||||
} else {
|
} else {
|
||||||
for ip in self.relay_servers.iter() {
|
for ip in self.relay_servers.iter() {
|
||||||
res += &format!("{}\n", ip);
|
let _ = writeln!(res, "{ip}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -935,8 +973,9 @@ impl RendezvousServer {
|
|||||||
if start < 0 {
|
if start < 0 {
|
||||||
if let Some(ip) = ip {
|
if let Some(ip) = ip {
|
||||||
if let Some((a, b)) = lock.get(ip) {
|
if let Some((a, b)) = lock.get(ip) {
|
||||||
res += &format!(
|
let _ = writeln!(
|
||||||
"{}/{}s {}/{}s\n",
|
res,
|
||||||
|
"{}/{}s {}/{}s",
|
||||||
a.0,
|
a.0,
|
||||||
a.1.elapsed().as_secs(),
|
a.1.elapsed().as_secs(),
|
||||||
b.0.len(),
|
b.0.len(),
|
||||||
@@ -961,8 +1000,9 @@ impl RendezvousServer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some((ip, (a, b))) = x {
|
if let Some((ip, (a, b))) = x {
|
||||||
res += &format!(
|
let _ = writeln!(
|
||||||
"{}: {}/{}s {}/{}s\n",
|
res,
|
||||||
|
"{}: {}/{}s {}/{}s",
|
||||||
ip,
|
ip,
|
||||||
a.0,
|
a.0,
|
||||||
a.1.elapsed().as_secs(),
|
a.1.elapsed().as_secs(),
|
||||||
@@ -979,10 +1019,10 @@ impl RendezvousServer {
|
|||||||
res = format!("{}\n", lock.len());
|
res = format!("{}\n", lock.len());
|
||||||
let id = fds.next();
|
let id = fds.next();
|
||||||
let mut start = id.map(|x| x.parse::<i32>().unwrap_or(-1)).unwrap_or(-1);
|
let mut start = id.map(|x| x.parse::<i32>().unwrap_or(-1)).unwrap_or(-1);
|
||||||
if start < 0 || start > 10_000_000 {
|
if !(0..=10_000_000).contains(&start) {
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
if let Some((tm, ips)) = lock.get(id) {
|
if let Some((tm, ips)) = lock.get(id) {
|
||||||
res += &format!("{}s {:?}\n", tm.elapsed().as_secs(), ips);
|
let _ = writeln!(res, "{}s {:?}", tm.elapsed().as_secs(), ips);
|
||||||
}
|
}
|
||||||
if fds.next() == Some("-") {
|
if fds.next() == Some("-") {
|
||||||
lock.remove(id);
|
lock.remove(id);
|
||||||
@@ -1002,21 +1042,43 @@ impl RendezvousServer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some((id, (tm, ips))) = x {
|
if let Some((id, (tm, ips))) = x {
|
||||||
res += &format!("{}: {}s {:?}\n", id, tm.elapsed().as_secs(), ips,);
|
let _ = writeln!(res, "{}: {}s {:?}", id, tm.elapsed().as_secs(), ips,);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some("punch-requests" | "pr") => {
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let mut lock = PUNCH_REQS.lock().await;
|
||||||
|
let arg = fds.next();
|
||||||
|
if let Some("-") = arg { lock.clear(); }
|
||||||
|
else {
|
||||||
|
let mut start = arg.and_then(|x| x.parse::<usize>().ok()).unwrap_or(0);
|
||||||
|
let mut page_size = fds.next().and_then(|x| x.parse::<usize>().ok()).unwrap_or(10);
|
||||||
|
if page_size == 0 { page_size = 10; }
|
||||||
|
for (_, e) in lock.iter().enumerate().skip(start).take(page_size) {
|
||||||
|
let age = e.tm.elapsed();
|
||||||
|
let event_system = std::time::SystemTime::now() - age;
|
||||||
|
let event_iso = chrono::DateTime::<chrono::Utc>::from(event_system)
|
||||||
|
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||||
|
let _ = writeln!(res, "{} {} -> {}@{}", event_iso, e.from_ip, e.to_id, e.to_ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some("always-use-relay" | "aur") => {
|
Some("always-use-relay" | "aur") => {
|
||||||
if let Some(rs) = fds.next() {
|
if let Some(rs) = fds.next() {
|
||||||
if rs.to_uppercase() == "Y" {
|
if rs.to_uppercase() == "Y" {
|
||||||
unsafe { ALWAYS_USE_RELAY = true };
|
ALWAYS_USE_RELAY.store(true, Ordering::SeqCst);
|
||||||
} else {
|
} else {
|
||||||
unsafe { ALWAYS_USE_RELAY = false };
|
ALWAYS_USE_RELAY.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
self.tx.send(Data::RelayServers0(rs.to_owned())).ok();
|
self.tx.send(Data::RelayServers0(rs.to_owned())).ok();
|
||||||
} else {
|
} else {
|
||||||
res += &format!("ALWAYS_USE_RELAY: {:?}\n", unsafe { ALWAYS_USE_RELAY });
|
let _ = writeln!(
|
||||||
|
res,
|
||||||
|
"ALWAYS_USE_RELAY: {:?}",
|
||||||
|
ALWAYS_USE_RELAY.load(Ordering::SeqCst)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("test-geo" | "tg") => {
|
Some("test-geo" | "tg") => {
|
||||||
@@ -1039,10 +1101,11 @@ impl RendezvousServer {
|
|||||||
|
|
||||||
async fn handle_listener2(&self, stream: TcpStream, addr: SocketAddr) {
|
async fn handle_listener2(&self, stream: TcpStream, addr: SocketAddr) {
|
||||||
let mut rs = self.clone();
|
let mut rs = self.clone();
|
||||||
if addr.ip().to_string() == "127.0.0.1" {
|
let ip = try_into_v4(addr).ip();
|
||||||
|
if ip.is_loopback() {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stream = stream;
|
let mut stream = stream;
|
||||||
let mut buffer = [0; 64];
|
let mut buffer = [0; 1024];
|
||||||
if let Ok(Ok(n)) = timeout(1000, stream.read(&mut buffer[..])).await {
|
if let Ok(Ok(n)) = timeout(1000, stream.read(&mut buffer[..])).await {
|
||||||
if let Ok(data) = std::str::from_utf8(&buffer[..n]) {
|
if let Ok(data) = std::str::from_utf8(&buffer[..n]) {
|
||||||
let res = rs.check_cmd(data).await;
|
let res = rs.check_cmd(data).await;
|
||||||
@@ -1089,23 +1152,36 @@ impl RendezvousServer {
|
|||||||
async fn handle_listener_inner(
|
async fn handle_listener_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
addr: SocketAddr,
|
mut addr: SocketAddr,
|
||||||
key: &str,
|
key: &str,
|
||||||
ws: bool,
|
ws: bool,
|
||||||
) -> ResultType<()> {
|
) -> ResultType<()> {
|
||||||
let mut sink;
|
let mut sink;
|
||||||
if ws {
|
if ws {
|
||||||
let ws_stream = tokio_tungstenite::accept_async(stream).await?;
|
use tokio_tungstenite::tungstenite::handshake::server::{Request, Response};
|
||||||
|
let callback = |req: &Request, response: Response| {
|
||||||
|
let headers = req.headers();
|
||||||
|
let real_ip = headers
|
||||||
|
.get("X-Real-IP")
|
||||||
|
.or_else(|| headers.get("X-Forwarded-For"))
|
||||||
|
.and_then(|header_value| header_value.to_str().ok());
|
||||||
|
if let Some(ip) = real_ip {
|
||||||
|
if ip.contains('.') {
|
||||||
|
addr = format!("{ip}:0").parse().unwrap_or(addr);
|
||||||
|
} else {
|
||||||
|
addr = format!("[{ip}]:0").parse().unwrap_or(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(response)
|
||||||
|
};
|
||||||
|
let ws_stream = tokio_tungstenite::accept_hdr_async(stream, callback).await?;
|
||||||
let (a, mut b) = ws_stream.split();
|
let (a, mut b) = ws_stream.split();
|
||||||
sink = Some(Sink::Ws(a));
|
sink = Some(Sink::Ws(a));
|
||||||
while let Ok(Some(Ok(msg))) = timeout(30_000, b.next()).await {
|
while let Ok(Some(Ok(msg))) = timeout(30_000, b.next()).await {
|
||||||
match msg {
|
if let tungstenite::Message::Binary(bytes) = msg {
|
||||||
tungstenite::Message::Binary(bytes) => {
|
if !self.handle_tcp(&bytes, &mut sink, addr, key, ws).await {
|
||||||
if !self.handle_tcp(&bytes, &mut sink, addr, key, ws).await {
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1118,7 +1194,7 @@ impl RendezvousServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sink.is_none() {
|
if sink.is_none() {
|
||||||
self.tcp_punch.lock().await.remove(&addr);
|
self.tcp_punch.lock().await.remove(&try_into_v4(addr));
|
||||||
}
|
}
|
||||||
log::debug!("Tcp connection from {:?} closed", addr);
|
log::debug!("Tcp connection from {:?} closed", addr);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1131,7 +1207,7 @@ impl RendezvousServer {
|
|||||||
} else {
|
} else {
|
||||||
match self.pm.get(&id).await {
|
match self.pm.get(&id).await {
|
||||||
Some(peer) => {
|
Some(peer) => {
|
||||||
let pk = peer.read().await.pk.clone().into();
|
let pk = peer.read().await.pk.clone();
|
||||||
sign::sign(
|
sign::sign(
|
||||||
&hbb_common::message_proto::IdPk {
|
&hbb_common::message_proto::IdPk {
|
||||||
id,
|
id,
|
||||||
@@ -1140,7 +1216,7 @@ impl RendezvousServer {
|
|||||||
}
|
}
|
||||||
.write_to_bytes()
|
.write_to_bytes()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
&self.inner.sk.as_ref().unwrap(),
|
self.inner.sk.as_ref().unwrap(),
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
@@ -1168,14 +1244,11 @@ impl RendezvousServer {
|
|||||||
out_sk = sk;
|
out_sk = sk;
|
||||||
if !key.is_empty() {
|
if !key.is_empty() {
|
||||||
key = pk;
|
key = pk;
|
||||||
} else {
|
|
||||||
std::env::set_var("KEY_FOR_API", pk);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !key.is_empty() {
|
if !key.is_empty() {
|
||||||
log::info!("Key: {}", key);
|
log::info!("Key: {}", key);
|
||||||
std::env::set_var("KEY_FOR_API", key.clone());
|
|
||||||
}
|
}
|
||||||
(key, out_sk)
|
(key, out_sk)
|
||||||
}
|
}
|
||||||
@@ -1183,8 +1256,16 @@ impl RendezvousServer {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn is_lan(&self, addr: SocketAddr) -> bool {
|
fn is_lan(&self, addr: SocketAddr) -> bool {
|
||||||
if let Some(network) = &self.inner.mask {
|
if let Some(network) = &self.inner.mask {
|
||||||
if let SocketAddr::V4(addr) = addr {
|
match addr {
|
||||||
return network.contains(*addr.ip());
|
SocketAddr::V4(v4_socket_addr) => {
|
||||||
|
return network.contains(*v4_socket_addr.ip());
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddr::V6(v6_socket_addr) => {
|
||||||
|
if let Some(v4_addr) = v6_socket_addr.ip().to_ipv4() {
|
||||||
|
return network.contains(v4_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@@ -1196,13 +1277,13 @@ async fn check_relay_servers(rs0: Arc<RelayServers>, tx: Sender) {
|
|||||||
let rs = Arc::new(Mutex::new(Vec::new()));
|
let rs = Arc::new(Mutex::new(Vec::new()));
|
||||||
for x in rs0.iter() {
|
for x in rs0.iter() {
|
||||||
let mut host = x.to_owned();
|
let mut host = x.to_owned();
|
||||||
if !host.contains(":") {
|
if !host.contains(':') {
|
||||||
host = format!("{}:{}", host, hbb_common::config::RELAY_PORT);
|
host = format!("{}:{}", host, config::RELAY_PORT);
|
||||||
}
|
}
|
||||||
let rs = rs.clone();
|
let rs = rs.clone();
|
||||||
let x = x.clone();
|
let x = x.clone();
|
||||||
futs.push(tokio::spawn(async move {
|
futs.push(tokio::spawn(async move {
|
||||||
if FramedStream::new(&host, "0.0.0.0:0", CHECK_RELAY_TIMEOUT)
|
if FramedStream::new(&host, None, CHECK_RELAY_TIMEOUT)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
@@ -1212,7 +1293,7 @@ async fn check_relay_servers(rs0: Arc<RelayServers>, tx: Sender) {
|
|||||||
}
|
}
|
||||||
join_all(futs).await;
|
join_all(futs).await;
|
||||||
log::debug!("check_relay_servers");
|
log::debug!("check_relay_servers");
|
||||||
let rs = std::mem::replace(&mut *rs.lock().await, Default::default());
|
let rs = std::mem::take(&mut *rs.lock().await);
|
||||||
if !rs.is_empty() {
|
if !rs.is_empty() {
|
||||||
tx.send(Data::RelayServers(rs)).ok();
|
tx.send(Data::RelayServers(rs)).ok();
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1301,16 @@ async fn check_relay_servers(rs0: Arc<RelayServers>, tx: Sender) {
|
|||||||
|
|
||||||
// temp solution to solve udp socket failure
|
// temp solution to solve udp socket failure
|
||||||
async fn test_hbbs(addr: SocketAddr) -> ResultType<()> {
|
async fn test_hbbs(addr: SocketAddr) -> ResultType<()> {
|
||||||
let mut socket = FramedSocket::new("0.0.0.0:0").await?;
|
let mut addr = addr;
|
||||||
|
if addr.ip().is_unspecified() {
|
||||||
|
addr.set_ip(if addr.is_ipv4() {
|
||||||
|
IpAddr::V4(Ipv4Addr::LOCALHOST)
|
||||||
|
} else {
|
||||||
|
IpAddr::V6(Ipv6Addr::LOCALHOST)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut socket = FramedSocket::new(config::Config::get_any_listen_addr(addr.is_ipv4())).await?;
|
||||||
let mut msg_out = RendezvousMessage::new();
|
let mut msg_out = RendezvousMessage::new();
|
||||||
msg_out.set_register_peer(RegisterPeer {
|
msg_out.set_register_peer(RegisterPeer {
|
||||||
id: "(:test_hbbs:)".to_owned(),
|
id: "(:test_hbbs:)".to_owned(),
|
||||||
@@ -1233,8 +1323,7 @@ async fn test_hbbs(addr: SocketAddr) -> ResultType<()> {
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = timer.tick() => {
|
_ = timer.tick() => {
|
||||||
if last_time_recv.elapsed().as_secs() > 12 {
|
if last_time_recv.elapsed().as_secs() > 12 {
|
||||||
log::error!("Timeout of test_hbbs");
|
bail!("Timeout of test_hbbs");
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
socket.send(&msg_out, addr).await?;
|
socket.send(&msg_out, addr).await?;
|
||||||
}
|
}
|
||||||
@@ -1261,3 +1350,22 @@ async fn send_rk_res(
|
|||||||
});
|
});
|
||||||
socket.send(&msg_out, addr).await
|
socket.send(&msg_out, addr).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_udp_listener(port: i32, rmem: usize) -> ResultType<FramedSocket> {
|
||||||
|
let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port as _);
|
||||||
|
if let Ok(s) = FramedSocket::new_reuse(&addr, true, rmem).await {
|
||||||
|
log::debug!("listen on udp {:?}", s.local_addr());
|
||||||
|
return Ok(s);
|
||||||
|
}
|
||||||
|
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port as _);
|
||||||
|
let s = FramedSocket::new_reuse(&addr, true, rmem).await?;
|
||||||
|
log::debug!("listen on udp {:?}", s.local_addr());
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn create_tcp_listener(port: i32) -> ResultType<TcpListener> {
|
||||||
|
let s = listen_any(port as _).await?;
|
||||||
|
log::debug!("listen on tcp {:?}", s.local_addr());
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|||||||
43
src/utils.rs
@@ -10,7 +10,7 @@ use std::{
|
|||||||
fn print_help() {
|
fn print_help() {
|
||||||
println!(
|
println!(
|
||||||
"Usage:
|
"Usage:
|
||||||
rustdesk-util [command]\n
|
rustdesk-utils [command]\n
|
||||||
Available Commands:
|
Available Commands:
|
||||||
genkeypair Generate a new keypair
|
genkeypair Generate a new keypair
|
||||||
validatekeypair [public key] [secret key] Validate an existing keypair
|
validatekeypair [public key] [secret key] Validate an existing keypair
|
||||||
@@ -20,7 +20,7 @@ Available Commands:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn error_then_help(msg: &str) {
|
fn error_then_help(msg: &str) {
|
||||||
println!("ERROR: {}\n", msg);
|
println!("ERROR: {msg}\n");
|
||||||
print_help();
|
print_help();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ fn gen_keypair() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate_keypair(pk: &str, sk: &str) -> ResultType<()> {
|
fn validate_keypair(pk: &str, sk: &str) -> ResultType<()> {
|
||||||
let sk1 = base64::decode(&sk);
|
let sk1 = base64::decode(sk);
|
||||||
if sk1.is_err() {
|
if sk1.is_err() {
|
||||||
bail!("Invalid secret key");
|
bail!("Invalid secret key");
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ fn validate_keypair(pk: &str, sk: &str) -> ResultType<()> {
|
|||||||
}
|
}
|
||||||
let secret_key = secret_key.unwrap();
|
let secret_key = secret_key.unwrap();
|
||||||
|
|
||||||
let pk1 = base64::decode(&pk);
|
let pk1 = base64::decode(pk);
|
||||||
if pk1.is_err() {
|
if pk1.is_err() {
|
||||||
bail!("Invalid public key");
|
bail!("Invalid public key");
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ fn validate_keypair(pk: &str, sk: &str) -> ResultType<()> {
|
|||||||
|
|
||||||
fn doctor_tcp(address: std::net::IpAddr, port: &str, desc: &str) {
|
fn doctor_tcp(address: std::net::IpAddr, port: &str, desc: &str) {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let conn = format!("{}:{}", address, port);
|
let conn = format!("{address}:{port}");
|
||||||
if let Ok(_stream) = TcpStream::connect(conn.as_str()) {
|
if let Ok(_stream) = TcpStream::connect(conn.as_str()) {
|
||||||
let elapsed = std::time::Instant::now().duration_since(start);
|
let elapsed = std::time::Instant::now().duration_since(start);
|
||||||
println!(
|
println!(
|
||||||
@@ -84,26 +84,24 @@ fn doctor_tcp(address: std::net::IpAddr, port: &str, desc: &str) {
|
|||||||
elapsed.as_millis()
|
elapsed.as_millis()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("TCP Port {} ({}): ERROR", port, desc);
|
println!("TCP Port {port} ({desc}): ERROR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn doctor_ip(server_ip_address: std::net::IpAddr, server_address: Option<&str>) {
|
fn doctor_ip(server_ip_address: std::net::IpAddr, server_address: Option<&str>) {
|
||||||
println!("\nChecking IP address: {}", server_ip_address);
|
println!("\nChecking IP address: {server_ip_address}");
|
||||||
println!("Is IPV4: {}", server_ip_address.is_ipv4());
|
println!("Is IPV4: {}", server_ip_address.is_ipv4());
|
||||||
println!("Is IPV6: {}", server_ip_address.is_ipv6());
|
println!("Is IPV6: {}", server_ip_address.is_ipv6());
|
||||||
|
|
||||||
// reverse dns lookup
|
// reverse dns lookup
|
||||||
// TODO: (check) doesn't seem to do reverse lookup on OSX...
|
// TODO: (check) doesn't seem to do reverse lookup on OSX...
|
||||||
let reverse = lookup_addr(&server_ip_address).unwrap();
|
let reverse = lookup_addr(&server_ip_address).unwrap();
|
||||||
if server_address.is_some() {
|
if let Some(server_address) = server_address {
|
||||||
if reverse == server_address.unwrap() {
|
if reverse == server_address {
|
||||||
println!("Reverse DNS lookup: '{}' MATCHES server address", reverse);
|
println!("Reverse DNS lookup: '{reverse}' MATCHES server address");
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"Reverse DNS lookup: '{}' DOESN'T MATCH server address '{}'",
|
"Reverse DNS lookup: '{reverse}' DOESN'T MATCH server address '{server_address}'"
|
||||||
reverse,
|
|
||||||
server_address.unwrap()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,20 +123,19 @@ fn doctor(server_address_unclean: &str) {
|
|||||||
let server_address3 = server_address_unclean.trim();
|
let server_address3 = server_address_unclean.trim();
|
||||||
let server_address2 = server_address3.to_lowercase();
|
let server_address2 = server_address3.to_lowercase();
|
||||||
let server_address = server_address2.as_str();
|
let server_address = server_address2.as_str();
|
||||||
println!("Checking server: {}\n", server_address);
|
println!("Checking server: {server_address}\n");
|
||||||
let server_ipaddr = server_address.parse::<IpAddr>();
|
if let Ok(server_ipaddr) = server_address.parse::<IpAddr>() {
|
||||||
if server_ipaddr.is_err() {
|
// user requested an ip address
|
||||||
|
doctor_ip(server_ipaddr, None);
|
||||||
|
} else {
|
||||||
// the passed string is not an ip address
|
// the passed string is not an ip address
|
||||||
let ips: Vec<std::net::IpAddr> = lookup_host(server_address).unwrap();
|
let ips: Vec<std::net::IpAddr> = lookup_host(server_address).unwrap();
|
||||||
println!("Found {} IP addresses: ", ips.iter().count());
|
println!("Found {} IP addresses: ", ips.len());
|
||||||
|
|
||||||
ips.iter().for_each(|ip| println!(" - {ip}"));
|
ips.iter().for_each(|ip| println!(" - {ip}"));
|
||||||
|
|
||||||
ips.iter().for_each(|ip| doctor_ip(*ip, Some(server_address)));
|
ips.iter()
|
||||||
|
.for_each(|ip| doctor_ip(*ip, Some(server_address)));
|
||||||
} else {
|
|
||||||
// user requested an ip address
|
|
||||||
doctor_ip(server_ipaddr.unwrap(), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +154,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
let res = validate_keypair(args[2].as_str(), args[3].as_str());
|
let res = validate_keypair(args[2].as_str(), args[3].as_str());
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
println!("{}", e);
|
println!("{e}");
|
||||||
process::exit(0x0001);
|
process::exit(0x0001);
|
||||||
}
|
}
|
||||||
println!("Key pair is VALID");
|
println!("Key pair is VALID");
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
pub const VERSION: &str = "1.1.6";
|
|
||||||
@@ -10,6 +10,8 @@ WorkingDirectory=/var/lib/rustdesk-server/
|
|||||||
User=
|
User=
|
||||||
Group=
|
Group=
|
||||||
Restart=always
|
Restart=always
|
||||||
|
StandardOutput=append:/var/log/rustdesk-server/hbbr.log
|
||||||
|
StandardError=append:/var/log/rustdesk-server/hbbr.error
|
||||||
# Restart service after 10 seconds if node service crashes
|
# Restart service after 10 seconds if node service crashes
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ WorkingDirectory=/var/lib/rustdesk-server/
|
|||||||
User=
|
User=
|
||||||
Group=
|
Group=
|
||||||
Restart=always
|
Restart=always
|
||||||
|
StandardOutput=append:/var/log/rustdesk-server/hbbs.log
|
||||||
|
StandardError=append:/var/log/rustdesk-server/hbbs.error
|
||||||
# Restart service after 10 seconds if node service crashes
|
# Restart service after 10 seconds if node service crashes
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
|
|||||||
8
ui/.cargo/config.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||||
|
[target.i686-pc-windows-msvc]
|
||||||
|
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||||
|
[target.'cfg(target_os="macos")']
|
||||||
|
rustflags = [
|
||||||
|
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
|
||||||
|
]
|
||||||
4
ui/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
3853
ui/Cargo.lock
generated
Normal file
31
ui/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[package]
|
||||||
|
name = "rustdesk_server"
|
||||||
|
version = "0.1.2"
|
||||||
|
description = "rustdesk server gui"
|
||||||
|
authors = ["elilchen"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "1.2", features = [] }
|
||||||
|
winres = "0.1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-std = { version = "1.12", features = ["attributes", "unstable"] }
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
|
derive-new = "0.5"
|
||||||
|
notify = "5.1"
|
||||||
|
once_cell = "1.17"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tauri = { version = "1.2", features = ["fs-exists", "fs-read-dir", "fs-read-file", "fs-write-file", "path-all", "shell-open", "system-tray"] }
|
||||||
|
windows-service = "0.5.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# by default Tauri runs in production mode
|
||||||
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
default = ["custom-protocol"]
|
||||||
|
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||||
|
# DO NOT remove this
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
21
ui/build.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build();
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
let mut res = winres::WindowsResource::new();
|
||||||
|
res.set_icon("icons\\icon.ico");
|
||||||
|
res.set_manifest(
|
||||||
|
r#"
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
res.compile().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
ui/html/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
18
ui/html/index.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>RustDesk Server</title>
|
||||||
|
<link rel="icon" href="data:;base64,=">
|
||||||
|
<script>addEventListener('contextmenu', e => e.preventDefault());</script>
|
||||||
|
<script type="module" src="/main.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body style="visibility: hidden">
|
||||||
|
<textarea></textarea>
|
||||||
|
<form>
|
||||||
|
<label><input type="checkbox"> <p>Turn on auto scroll</p></label>
|
||||||
|
<label><p>Press ctrl + s to save</p></label>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
159
ui/html/main.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import './style.css';
|
||||||
|
import 'codemirror/mode/toml/toml.js';
|
||||||
|
import CodeMirror from 'codemirror';
|
||||||
|
|
||||||
|
const { event, fs, path, tauri } = window.__TAURI__;
|
||||||
|
|
||||||
|
class View {
|
||||||
|
constructor() {
|
||||||
|
Object.assign(this, {
|
||||||
|
content: '',
|
||||||
|
action_time: 0,
|
||||||
|
is_auto_scroll: true,
|
||||||
|
is_edit_mode: false,
|
||||||
|
is_file_changed: false,
|
||||||
|
is_form_changed: false,
|
||||||
|
is_content_changed: false
|
||||||
|
}, ...arguments);
|
||||||
|
addEventListener('DOMContentLoaded', this.init.bind(this));
|
||||||
|
}
|
||||||
|
async init() {
|
||||||
|
this.editor = this.renderEditor();
|
||||||
|
this.editor.on('scroll', this.editorScroll.bind(this));
|
||||||
|
this.editor.on('keypress', this.editorSave.bind(this));
|
||||||
|
this.form = this.renderForm();
|
||||||
|
this.form.addEventListener('change', this.formChange.bind(this));
|
||||||
|
event.listen('__update__', this.appAction.bind(this));
|
||||||
|
event.emit('__action__', '__init__');
|
||||||
|
while (true) {
|
||||||
|
let now = Date.now();
|
||||||
|
try {
|
||||||
|
await this.update();
|
||||||
|
this.render();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, Math.max(0, 33 - (Date.now() - now))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async update() {
|
||||||
|
if (this.is_file_changed) {
|
||||||
|
this.is_file_changed = false;
|
||||||
|
let now = Date.now(),
|
||||||
|
file = await path.resolveResource(this.file);
|
||||||
|
if (await fs.exists(file)) {
|
||||||
|
let content = await fs.readTextFile(file);
|
||||||
|
if (this.action_time < now) {
|
||||||
|
this.content = content;
|
||||||
|
this.is_content_changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (now >= this.action_time) {
|
||||||
|
if (this.is_edit_mode) {
|
||||||
|
this.content = `# https://github.com/rustdesk/rustdesk-server#env-variables
|
||||||
|
RUST_LOG=info
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
this.is_content_changed = true;
|
||||||
|
}
|
||||||
|
console.warn(`${this.file} file is missing`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async editorSave(editor, e) {
|
||||||
|
if (e.ctrlKey && e.keyCode === 19 && this.is_edit_mode && !this.locked) {
|
||||||
|
this.locked = true;
|
||||||
|
try {
|
||||||
|
let now = Date.now(),
|
||||||
|
content = this.editor.doc.getValue(),
|
||||||
|
file = await path.resolveResource(this.file);
|
||||||
|
await fs.writeTextFile(file, content);
|
||||||
|
event.emit('__action__', 'restart');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
this.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editorScroll(e) {
|
||||||
|
let info = this.editor.getScrollInfo(),
|
||||||
|
distance = info.height - info.top - info.clientHeight,
|
||||||
|
is_end = distance < 1;
|
||||||
|
if (this.is_auto_scroll !== is_end) {
|
||||||
|
this.is_auto_scroll = is_end;
|
||||||
|
this.is_form_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formChange(e) {
|
||||||
|
switch (e.target.tagName.toLowerCase()) {
|
||||||
|
case 'input':
|
||||||
|
this.is_auto_scroll = e.target.checked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appAction(e) {
|
||||||
|
let [action, data] = e.payload;
|
||||||
|
switch (action) {
|
||||||
|
case 'file':
|
||||||
|
if (data === '.env') {
|
||||||
|
this.is_edit_mode = true;
|
||||||
|
this.file = `bin/${data}`;
|
||||||
|
} else {
|
||||||
|
this.is_edit_mode = false;
|
||||||
|
this.file = `logs/${data}`;
|
||||||
|
}
|
||||||
|
this.action_time = Date.now();
|
||||||
|
this.is_file_changed = true;
|
||||||
|
this.is_form_changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (this.is_form_changed) {
|
||||||
|
this.is_form_changed = false;
|
||||||
|
this.renderForm();
|
||||||
|
}
|
||||||
|
if (this.is_content_changed) {
|
||||||
|
this.is_content_changed = false;
|
||||||
|
this.renderEditor();
|
||||||
|
}
|
||||||
|
if (this.is_auto_scroll && !this.is_edit_mode) {
|
||||||
|
this.renderScrollbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderForm() {
|
||||||
|
let form = this.form || document.querySelector('form'),
|
||||||
|
label = form.querySelectorAll('label'),
|
||||||
|
input = form.querySelector('input');
|
||||||
|
input.checked = this.is_auto_scroll;
|
||||||
|
if (this.is_edit_mode) {
|
||||||
|
label[0].style.display = 'none';
|
||||||
|
label[1].style.display = 'block';
|
||||||
|
} else {
|
||||||
|
label[0].style.display = 'block';
|
||||||
|
label[1].style.display = 'none';
|
||||||
|
}
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
renderEditor() {
|
||||||
|
let editor = this.editor || CodeMirror.fromTextArea(document.querySelector('textarea'), {
|
||||||
|
mode: { name: 'toml' },
|
||||||
|
lineNumbers: true,
|
||||||
|
autofocus: true
|
||||||
|
});
|
||||||
|
editor.setOption('readOnly', !this.is_edit_mode);
|
||||||
|
editor.doc.setValue(this.content);
|
||||||
|
editor.doc.clearHistory();
|
||||||
|
this.content = '';
|
||||||
|
editor.focus();
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
renderScrollbar() {
|
||||||
|
let info = this.editor.getScrollInfo();
|
||||||
|
this.editor.scrollTo(info.left, info.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new View();
|
||||||
17
ui/html/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "rustdesk_server",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.2",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^4.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"codemirror": "v5"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
ui/html/style.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
body {
|
||||||
|
visibility: visible !important;
|
||||||
|
margin: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
height: calc(100vh - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
height: 20px;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form>label {
|
||||||
|
display: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
form>label>input,
|
||||||
|
form>label>p {
|
||||||
|
height: 19px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
8
ui/html/vite.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
port: '5177',
|
||||||
|
strictPort: true
|
||||||
|
}
|
||||||
|
});
|
||||||
BIN
ui/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
ui/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
ui/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
ui/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
ui/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
ui/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
ui/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
ui/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
ui/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
ui/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
ui/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
ui/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
ui/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
ui/icons/icon.icns
Normal file
BIN
ui/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
ui/icons/icon.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
178
ui/setup.nsi
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
Unicode true
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Includes
|
||||||
|
|
||||||
|
!include nsDialogs.nsh
|
||||||
|
!include MUI2.nsh
|
||||||
|
!include x64.nsh
|
||||||
|
!include LogicLib.nsh
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# File Info
|
||||||
|
|
||||||
|
!define APP_NAME "RustDeskServer"
|
||||||
|
!define PRODUCT_NAME "rustdesk_server"
|
||||||
|
!define PRODUCT_DESCRIPTION "Installer for ${PRODUCT_NAME}"
|
||||||
|
!define COPYRIGHT "Copyright © 2021"
|
||||||
|
!define VERSION "1.1.15"
|
||||||
|
|
||||||
|
VIProductVersion "${VERSION}.0"
|
||||||
|
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
|
||||||
|
VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||||
|
VIAddVersionKey "FileDescription" "${PRODUCT_DESCRIPTION}"
|
||||||
|
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||||
|
VIAddVersionKey "FileVersion" "${VERSION}"
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Installer Attributes
|
||||||
|
|
||||||
|
Name "${APP_NAME}"
|
||||||
|
Outfile "${APP_NAME}.Setup.exe"
|
||||||
|
Caption "Setup - ${APP_NAME}"
|
||||||
|
BrandingText "${APP_NAME}"
|
||||||
|
|
||||||
|
ShowInstDetails show
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
SetOverwrite on
|
||||||
|
|
||||||
|
InstallDir "$PROGRAMFILES64\${APP_NAME}"
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Pages
|
||||||
|
|
||||||
|
!define MUI_ICON "icons\icon.ico"
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!define MUI_LANGDLL_ALLLANGUAGES
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME ""
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Startup Shortcut"
|
||||||
|
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateStartupShortcut
|
||||||
|
!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Language
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English" ; The first language is the default language
|
||||||
|
!insertmacro MUI_LANGUAGE "French"
|
||||||
|
!insertmacro MUI_LANGUAGE "German"
|
||||||
|
!insertmacro MUI_LANGUAGE "Spanish"
|
||||||
|
!insertmacro MUI_LANGUAGE "SpanishInternational"
|
||||||
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
|
!insertmacro MUI_LANGUAGE "TradChinese"
|
||||||
|
!insertmacro MUI_LANGUAGE "Japanese"
|
||||||
|
!insertmacro MUI_LANGUAGE "Korean"
|
||||||
|
!insertmacro MUI_LANGUAGE "Italian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Dutch"
|
||||||
|
!insertmacro MUI_LANGUAGE "Danish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Swedish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Norwegian"
|
||||||
|
!insertmacro MUI_LANGUAGE "NorwegianNynorsk"
|
||||||
|
!insertmacro MUI_LANGUAGE "Finnish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Greek"
|
||||||
|
!insertmacro MUI_LANGUAGE "Russian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Portuguese"
|
||||||
|
!insertmacro MUI_LANGUAGE "PortugueseBR"
|
||||||
|
!insertmacro MUI_LANGUAGE "Polish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Ukrainian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Czech"
|
||||||
|
!insertmacro MUI_LANGUAGE "Slovak"
|
||||||
|
!insertmacro MUI_LANGUAGE "Croatian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Bulgarian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Hungarian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Thai"
|
||||||
|
!insertmacro MUI_LANGUAGE "Romanian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Latvian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Macedonian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Estonian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Turkish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Lithuanian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Slovenian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Serbian"
|
||||||
|
!insertmacro MUI_LANGUAGE "SerbianLatin"
|
||||||
|
!insertmacro MUI_LANGUAGE "Arabic"
|
||||||
|
!insertmacro MUI_LANGUAGE "Farsi"
|
||||||
|
!insertmacro MUI_LANGUAGE "Hebrew"
|
||||||
|
!insertmacro MUI_LANGUAGE "Indonesian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Mongolian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Luxembourgish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Albanian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Breton"
|
||||||
|
!insertmacro MUI_LANGUAGE "Belarusian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Icelandic"
|
||||||
|
!insertmacro MUI_LANGUAGE "Malay"
|
||||||
|
!insertmacro MUI_LANGUAGE "Bosnian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Kurdish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Irish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Uzbek"
|
||||||
|
!insertmacro MUI_LANGUAGE "Galician"
|
||||||
|
!insertmacro MUI_LANGUAGE "Afrikaans"
|
||||||
|
!insertmacro MUI_LANGUAGE "Catalan"
|
||||||
|
!insertmacro MUI_LANGUAGE "Esperanto"
|
||||||
|
!insertmacro MUI_LANGUAGE "Asturian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Basque"
|
||||||
|
!insertmacro MUI_LANGUAGE "Pashto"
|
||||||
|
!insertmacro MUI_LANGUAGE "ScotsGaelic"
|
||||||
|
!insertmacro MUI_LANGUAGE "Georgian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Vietnamese"
|
||||||
|
!insertmacro MUI_LANGUAGE "Welsh"
|
||||||
|
!insertmacro MUI_LANGUAGE "Armenian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Corsican"
|
||||||
|
!insertmacro MUI_LANGUAGE "Tatar"
|
||||||
|
!insertmacro MUI_LANGUAGE "Hindi"
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Sections
|
||||||
|
|
||||||
|
Section "Install"
|
||||||
|
SetShellVarContext all
|
||||||
|
nsExec::Exec 'sc stop hbbr'
|
||||||
|
nsExec::Exec 'sc stop hbbs'
|
||||||
|
nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe'
|
||||||
|
Sleep 500
|
||||||
|
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
File /r "setup\*.*"
|
||||||
|
WriteUninstaller $INSTDIR\uninstall.exe
|
||||||
|
|
||||||
|
CreateDirectory "$SMPROGRAMS\${APP_NAME}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"
|
||||||
|
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=in action=allow program="$INSTDIR\bin\hbbs.exe" enable=yes'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=out action=allow program="$INSTDIR\bin\hbbs.exe" enable=yes'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=in action=allow program="$INSTDIR\bin\hbbr.exe" enable=yes'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=out action=allow program="$INSTDIR\bin\hbbr.exe" enable=yes'
|
||||||
|
ExecWait 'powershell.exe -NoProfile -windowstyle hidden try { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 } catch {}; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ($\'/silent$\', $\'/install$\') -Wait'
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
SetShellVarContext all
|
||||||
|
nsExec::Exec 'sc stop hbbr'
|
||||||
|
nsExec::Exec 'sc stop hbbs'
|
||||||
|
nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe'
|
||||||
|
Sleep 500
|
||||||
|
|
||||||
|
RMDir /r "$SMPROGRAMS\${APP_NAME}"
|
||||||
|
Delete "$SMSTARTUP\${APP_NAME}.lnk"
|
||||||
|
Delete "$DESKTOP\${APP_NAME}.lnk"
|
||||||
|
nsExec::Exec 'sc delete hbbr'
|
||||||
|
nsExec::Exec 'sc delete hbbs'
|
||||||
|
nsExec::Exec 'netsh advfirewall firewall delete rule name="${APP_NAME}"'
|
||||||
|
RMDir /r "$INSTDIR\bin"
|
||||||
|
RMDir /r "$INSTDIR\logs"
|
||||||
|
RMDir /r "$INSTDIR\service"
|
||||||
|
Delete "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
Function CreateStartupShortcut
|
||||||
|
CreateShortCut "$SMSTARTUP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||||
|
FunctionEnd
|
||||||
BIN
ui/setup/service/nssm.exe
Normal file
23
ui/setup/service/run.cmd
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
@echo off
|
||||||
|
%~d0
|
||||||
|
cd "%~dp0"
|
||||||
|
set nssm="%cd%\nssm"
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
%nssm% install %1 "%cd%\bin\%1.exe"
|
||||||
|
|
||||||
|
%nssm% set %1 DisplayName %1
|
||||||
|
%nssm% set %1 Description rustdesk %1 server
|
||||||
|
%nssm% set %1 Start SERVICE_AUTO_START
|
||||||
|
|
||||||
|
%nssm% set %1 ObjectName LocalSystem
|
||||||
|
%nssm% set %1 Type SERVICE_WIN32_OWN_PROCESS
|
||||||
|
|
||||||
|
%nssm% set %1 AppThrottle 1000
|
||||||
|
%nssm% set %1 AppExit Default Restart
|
||||||
|
%nssm% set %1 AppRestartDelay 0
|
||||||
|
|
||||||
|
%nssm% set %1 AppStdout "%cd%\logs\%1.out"
|
||||||
|
%nssm% set %1 AppStderr "%cd%\logs\%1.err"
|
||||||
|
|
||||||
|
%nssm% start %1
|
||||||
5
ui/src/adapter/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod view;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
|
pub use view::*;
|
||||||
|
pub use service::*;
|
||||||
3
ui/src/adapter/service/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod windows;
|
||||||
|
|
||||||
|
pub use windows::*;
|
||||||
130
ui/src/adapter/service/windows.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use std::{ffi::OsStr, process::Command};
|
||||||
|
|
||||||
|
use crate::{path, usecase::service::*};
|
||||||
|
use derive_new::new;
|
||||||
|
use windows_service::{
|
||||||
|
service::ServiceAccess,
|
||||||
|
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, new)]
|
||||||
|
pub struct WindowsDesktopService {
|
||||||
|
#[new(value = "DesktopServiceState::Stopped")]
|
||||||
|
pub state: DesktopServiceState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IDesktopService for WindowsDesktopService {
|
||||||
|
fn start(&mut self) {
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"echo.",
|
||||||
|
"%nssm% stop hbbr",
|
||||||
|
"%nssm% remove hbbr confirm",
|
||||||
|
"%nssm% stop hbbs",
|
||||||
|
"%nssm% remove hbbs confirm",
|
||||||
|
"mkdir logs",
|
||||||
|
"echo.",
|
||||||
|
"service\\run.cmd hbbs",
|
||||||
|
"echo.",
|
||||||
|
"service\\run.cmd hbbr",
|
||||||
|
"echo.",
|
||||||
|
"@ping 127.1 -n 3 >nul",
|
||||||
|
]
|
||||||
|
.join(" & "),
|
||||||
|
);
|
||||||
|
self.check();
|
||||||
|
}
|
||||||
|
fn stop(&mut self) {
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"echo.",
|
||||||
|
"%nssm% stop hbbr",
|
||||||
|
"%nssm% remove hbbr confirm",
|
||||||
|
"echo.",
|
||||||
|
"%nssm% stop hbbs",
|
||||||
|
"%nssm% remove hbbs confirm",
|
||||||
|
"echo.",
|
||||||
|
"@ping 127.1 -n 3 >nul",
|
||||||
|
]
|
||||||
|
.join(" & "),
|
||||||
|
);
|
||||||
|
self.check();
|
||||||
|
}
|
||||||
|
fn restart(&mut self) {
|
||||||
|
nssm(["restart", "hbbs"].map(|x| x.to_owned()));
|
||||||
|
nssm(["restart", "hbbr"].map(|x| x.to_owned()));
|
||||||
|
self.check();
|
||||||
|
}
|
||||||
|
fn pause(&mut self) {
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"echo.",
|
||||||
|
"%nssm% stop hbbr",
|
||||||
|
"echo.",
|
||||||
|
"%nssm% stop hbbs",
|
||||||
|
"echo.",
|
||||||
|
"@ping 127.1 -n 3 >nul",
|
||||||
|
]
|
||||||
|
.join(" & "),
|
||||||
|
);
|
||||||
|
self.check();
|
||||||
|
}
|
||||||
|
fn check(&mut self) -> DesktopServiceState {
|
||||||
|
self.state = match service_status("hbbs").as_str() {
|
||||||
|
"Running" => DesktopServiceState::Started,
|
||||||
|
// "Stopped" => DeskServerServiceState::Paused,
|
||||||
|
_ => DesktopServiceState::Stopped,
|
||||||
|
};
|
||||||
|
self.state.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(cmd: String) {
|
||||||
|
Command::new("cmd")
|
||||||
|
.current_dir(&path())
|
||||||
|
.env("nssm", "service\\nssm.exe")
|
||||||
|
.arg("/c")
|
||||||
|
.arg("start")
|
||||||
|
.arg("cmd")
|
||||||
|
.arg("/c")
|
||||||
|
.arg(cmd)
|
||||||
|
.output()
|
||||||
|
.expect("cmd exec error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec<I, S>(program: S, args: I) -> String
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
match Command::new(program).args(args).output() {
|
||||||
|
Ok(out) => String::from_utf8(out.stdout).unwrap_or("".to_owned()),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nssm<I>(args: I) -> String
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = String>,
|
||||||
|
{
|
||||||
|
exec(
|
||||||
|
format!("{}\\service\\nssm.exe", path().to_str().unwrap_or_default()),
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
.replace("\0", "")
|
||||||
|
.trim()
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn service_status(name: &str) -> String {
|
||||||
|
match ServiceManager::local_computer(None::<&OsStr>, ServiceManagerAccess::CONNECT) {
|
||||||
|
Ok(manager) => match manager.open_service(name, ServiceAccess::QUERY_STATUS) {
|
||||||
|
Ok(service) => match service.query_status() {
|
||||||
|
Ok(status) => format!("{:?}", status.current_state),
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
},
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
},
|
||||||
|
Err(e) => e.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
221
ui/src/adapter/view/desktop.rs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
use std::{
|
||||||
|
process::exit,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
path,
|
||||||
|
usecase::{view::Event, DesktopServiceState},
|
||||||
|
BUFFER,
|
||||||
|
};
|
||||||
|
use async_std::task::sleep;
|
||||||
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
use tauri::{
|
||||||
|
CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||||
|
SystemTrayMenuItem, WindowEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn run(sender: Sender<Event>, receiver: Receiver<Event>) {
|
||||||
|
let setup_sender = sender.clone();
|
||||||
|
let menu_sender = sender.clone();
|
||||||
|
let tray_sender = sender.clone();
|
||||||
|
let menu = Menu::new()
|
||||||
|
.add_submenu(Submenu::new(
|
||||||
|
"Service",
|
||||||
|
Menu::new()
|
||||||
|
.add_item(CustomMenuItem::new("restart", "Restart"))
|
||||||
|
.add_native_item(MenuItem::Separator)
|
||||||
|
.add_item(CustomMenuItem::new("start", "Start"))
|
||||||
|
.add_item(CustomMenuItem::new("stop", "Stop")),
|
||||||
|
))
|
||||||
|
.add_submenu(Submenu::new(
|
||||||
|
"Logs",
|
||||||
|
Menu::new()
|
||||||
|
.add_item(CustomMenuItem::new("hbbs.out", "hbbs.out"))
|
||||||
|
.add_item(CustomMenuItem::new("hbbs.err", "hbbs.err"))
|
||||||
|
.add_native_item(MenuItem::Separator)
|
||||||
|
.add_item(CustomMenuItem::new("hbbr.out", "hbbr.out"))
|
||||||
|
.add_item(CustomMenuItem::new("hbbr.err", "hbbr.err")),
|
||||||
|
))
|
||||||
|
.add_submenu(Submenu::new(
|
||||||
|
"Configuration",
|
||||||
|
Menu::new().add_item(CustomMenuItem::new(".env", ".env")),
|
||||||
|
));
|
||||||
|
let tray = SystemTray::new().with_menu(
|
||||||
|
SystemTrayMenu::new()
|
||||||
|
.add_item(CustomMenuItem::new("restart", "Restart"))
|
||||||
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
|
.add_item(CustomMenuItem::new("start", "Start"))
|
||||||
|
.add_item(CustomMenuItem::new("stop", "Stop"))
|
||||||
|
.add_native_item(SystemTrayMenuItem::Separator)
|
||||||
|
.add_item(CustomMenuItem::new("exit", "Exit GUI")),
|
||||||
|
);
|
||||||
|
let mut app = tauri::Builder::default()
|
||||||
|
.on_window_event(|event| match event.event() {
|
||||||
|
// WindowEvent::Resized(size) => {
|
||||||
|
// if size.width == 0 && size.height == 0 {
|
||||||
|
// event.window().hide().unwrap();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
WindowEvent::CloseRequested { api, .. } => {
|
||||||
|
api.prevent_close();
|
||||||
|
event.window().minimize().unwrap();
|
||||||
|
event.window().hide().unwrap();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.menu(menu)
|
||||||
|
.on_menu_event(move |event| {
|
||||||
|
// println!(
|
||||||
|
// "send {}: {}",
|
||||||
|
// std::time::SystemTime::now()
|
||||||
|
// .duration_since(std::time::UNIX_EPOCH)
|
||||||
|
// .unwrap_or_default()
|
||||||
|
// .as_millis(),
|
||||||
|
// event.menu_item_id()
|
||||||
|
// );
|
||||||
|
menu_sender
|
||||||
|
.send(Event::ViewAction(event.menu_item_id().to_owned()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.system_tray(tray)
|
||||||
|
.on_system_tray_event(move |app, event| match event {
|
||||||
|
SystemTrayEvent::LeftClick { .. } => {
|
||||||
|
let main = app.get_window("main").unwrap();
|
||||||
|
if main.is_visible().unwrap() {
|
||||||
|
main.hide().unwrap();
|
||||||
|
} else {
|
||||||
|
main.show().unwrap();
|
||||||
|
main.unminimize().unwrap();
|
||||||
|
main.set_focus().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemTrayEvent::MenuItemClick { id, .. } => {
|
||||||
|
tray_sender.send(Event::ViewAction(id)).unwrap_or_default();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.setup(move |app| {
|
||||||
|
setup_sender.send(Event::ViewInit).unwrap_or_default();
|
||||||
|
app.listen_global("__action__", move |msg| {
|
||||||
|
match msg.payload().unwrap_or_default() {
|
||||||
|
r#""__init__""# => setup_sender.send(Event::BrowserInit).unwrap_or_default(),
|
||||||
|
r#""restart""# => setup_sender
|
||||||
|
.send(Event::BrowserAction("restart".to_owned()))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.invoke_handler(tauri::generate_handler![root])
|
||||||
|
.build(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
let mut now = Instant::now();
|
||||||
|
let mut blink = false;
|
||||||
|
let mut span = 0;
|
||||||
|
let mut title = "".to_owned();
|
||||||
|
let product = "RustDesk Server";
|
||||||
|
let buffer = BUFFER.get().unwrap().to_owned();
|
||||||
|
loop {
|
||||||
|
for _ in 1..buffer {
|
||||||
|
match receiver.recv_timeout(Duration::from_nanos(1)) {
|
||||||
|
Ok(event) => {
|
||||||
|
let main = app.get_window("main").unwrap();
|
||||||
|
let menu = main.menu_handle();
|
||||||
|
let tray = app.tray_handle();
|
||||||
|
match event {
|
||||||
|
Event::BrowserUpdate((action, data)) => match action.as_str() {
|
||||||
|
"file" => {
|
||||||
|
let list = ["hbbs.out", "hbbs.err", "hbbr.out", "hbbr.err", ".env"];
|
||||||
|
let id = data.as_str();
|
||||||
|
if list.contains(&id) {
|
||||||
|
for file in list {
|
||||||
|
menu.get_item(file)
|
||||||
|
.set_selected(file == id)
|
||||||
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
|
// println!(
|
||||||
|
// "emit {}: {}",
|
||||||
|
// std::time::SystemTime::now()
|
||||||
|
// .duration_since(std::time::UNIX_EPOCH)
|
||||||
|
// .unwrap_or_default()
|
||||||
|
// .as_millis(),
|
||||||
|
// data
|
||||||
|
// );
|
||||||
|
app.emit_all("__update__", (action, data))
|
||||||
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Event::ViewRenderAppExit => exit(0),
|
||||||
|
Event::ViewRenderServiceState(state) => {
|
||||||
|
let enabled = |id, enabled| {
|
||||||
|
menu.get_item(id).set_enabled(enabled).unwrap_or_default();
|
||||||
|
tray.get_item(id).set_enabled(enabled).unwrap_or_default();
|
||||||
|
};
|
||||||
|
title = format!("{} {:?}", product, state);
|
||||||
|
main.set_title(title.as_str()).unwrap_or_default();
|
||||||
|
match state {
|
||||||
|
DesktopServiceState::Started => {
|
||||||
|
enabled("start", false);
|
||||||
|
enabled("stop", true);
|
||||||
|
enabled("restart", true);
|
||||||
|
blink = false;
|
||||||
|
}
|
||||||
|
DesktopServiceState::Stopped => {
|
||||||
|
enabled("start", true);
|
||||||
|
enabled("stop", false);
|
||||||
|
enabled("restart", false);
|
||||||
|
blink = true;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
enabled("start", false);
|
||||||
|
enabled("stop", false);
|
||||||
|
enabled("restart", false);
|
||||||
|
blink = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let elapsed = now.elapsed().as_micros();
|
||||||
|
if elapsed > 16666 {
|
||||||
|
now = Instant::now();
|
||||||
|
// println!("{}ms", elapsed as f64 * 0.001);
|
||||||
|
let iteration = app.run_iteration();
|
||||||
|
if iteration.window_count == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if blink {
|
||||||
|
if span > 1000000 {
|
||||||
|
span = 0;
|
||||||
|
app.get_window("main")
|
||||||
|
.unwrap()
|
||||||
|
.set_title(title.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
} else {
|
||||||
|
span += elapsed;
|
||||||
|
if span > 500000 {
|
||||||
|
app.get_window("main")
|
||||||
|
.unwrap()
|
||||||
|
.set_title(product)
|
||||||
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sleep(Duration::from_micros(999)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn root() -> String {
|
||||||
|
path().to_str().unwrap_or_default().to_owned()
|
||||||
|
}
|
||||||
3
ui/src/adapter/view/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod desktop;
|
||||||
|
|
||||||
|
pub use desktop::*;
|
||||||
17
ui/src/lib.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use std::{env::current_exe, path::PathBuf};
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
pub mod adapter;
|
||||||
|
pub mod usecase;
|
||||||
|
|
||||||
|
pub static BUFFER: OnceCell<usize> = OnceCell::new();
|
||||||
|
|
||||||
|
pub fn path() -> PathBuf {
|
||||||
|
current_exe()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_path()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
25
ui/src/main.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#![cfg_attr(
|
||||||
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
|
windows_subsystem = "windows"
|
||||||
|
)]
|
||||||
|
|
||||||
|
use async_std::{
|
||||||
|
prelude::FutureExt,
|
||||||
|
task::{spawn, spawn_local},
|
||||||
|
};
|
||||||
|
use crossbeam_channel::bounded;
|
||||||
|
use rustdesk_server::{
|
||||||
|
usecase::{presenter, view, watcher},
|
||||||
|
BUFFER,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() {
|
||||||
|
let buffer = BUFFER.get_or_init(|| 10).to_owned();
|
||||||
|
let (view_sender, presenter_receiver) = bounded(buffer);
|
||||||
|
let (presenter_sender, view_receiver) = bounded(buffer);
|
||||||
|
spawn_local(view::create(presenter_sender.clone(), presenter_receiver))
|
||||||
|
.join(spawn(presenter::create(view_sender, view_receiver)))
|
||||||
|
.join(spawn(watcher::create(presenter_sender)))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
9
ui/src/usecase/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
pub mod presenter;
|
||||||
|
pub mod service;
|
||||||
|
pub mod view;
|
||||||
|
pub mod watcher;
|
||||||
|
|
||||||
|
pub use presenter::*;
|
||||||
|
pub use service::*;
|
||||||
|
pub use view::*;
|
||||||
|
pub use watcher::*;
|
||||||
59
ui/src/usecase/presenter.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use super::{service, DesktopServiceState, Event};
|
||||||
|
use crate::BUFFER;
|
||||||
|
use async_std::task::sleep;
|
||||||
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
|
||||||
|
pub async fn create(sender: Sender<Event>, receiver: Receiver<Event>) {
|
||||||
|
let mut now = Instant::now();
|
||||||
|
let buffer = BUFFER.get().unwrap().to_owned();
|
||||||
|
let send = |event| sender.send(event).unwrap_or_default();
|
||||||
|
if let Some(mut service) = service::create() {
|
||||||
|
let mut service_state = DesktopServiceState::Unknown;
|
||||||
|
let mut file = "hbbs.out".to_owned();
|
||||||
|
send(Event::ViewRenderServiceState(service_state.to_owned()));
|
||||||
|
loop {
|
||||||
|
for _ in 1..buffer {
|
||||||
|
match receiver.recv_timeout(Duration::from_nanos(1)) {
|
||||||
|
Ok(event) => match event {
|
||||||
|
Event::BrowserInit => {
|
||||||
|
send(Event::BrowserUpdate(("file".to_owned(), file.to_owned())));
|
||||||
|
}
|
||||||
|
Event::BrowserAction(action) => match action.as_str() {
|
||||||
|
"restart" => service.restart(),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Event::FileChange(path) => {
|
||||||
|
if path == file {
|
||||||
|
send(Event::BrowserUpdate(("file".to_owned(), file.to_owned())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::ViewAction(action) => match action.as_str() {
|
||||||
|
"start" => service.start(),
|
||||||
|
"stop" => service.stop(),
|
||||||
|
"restart" => service.restart(),
|
||||||
|
"pause" => service.pause(),
|
||||||
|
"exit" => send(Event::ViewRenderAppExit),
|
||||||
|
_ => {
|
||||||
|
file = action;
|
||||||
|
send(Event::BrowserUpdate(("file".to_owned(), file.to_owned())));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(Duration::from_micros(999)).await;
|
||||||
|
if now.elapsed().as_millis() > 999 {
|
||||||
|
let state = service.check();
|
||||||
|
if state != service_state {
|
||||||
|
service_state = state.to_owned();
|
||||||
|
send(Event::ViewRenderServiceState(state));
|
||||||
|
}
|
||||||
|
now = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
ui/src/usecase/service.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use crate::adapter;
|
||||||
|
|
||||||
|
pub fn create() -> Option<Box<dyn IDesktopService + Send>> {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
return Some(Box::new(adapter::WindowsDesktopService::new()));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum DesktopServiceState {
|
||||||
|
Paused,
|
||||||
|
Started,
|
||||||
|
Stopped,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IDesktopService {
|
||||||
|
fn start(&mut self);
|
||||||
|
fn stop(&mut self);
|
||||||
|
fn restart(&mut self);
|
||||||
|
fn pause(&mut self);
|
||||||
|
fn check(&mut self) -> DesktopServiceState;
|
||||||
|
}
|
||||||
22
ui/src/usecase/view.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use super::DesktopServiceState;
|
||||||
|
use crate::adapter::desktop;
|
||||||
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
|
||||||
|
pub async fn create(sender: Sender<Event>, receiver: Receiver<Event>) {
|
||||||
|
desktop::run(sender, receiver).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Event {
|
||||||
|
BrowserAction(String),
|
||||||
|
BrowserInit,
|
||||||
|
BrowserUpdate((String, String)),
|
||||||
|
BrowserRender(String),
|
||||||
|
FileChange(String),
|
||||||
|
ViewAction(String),
|
||||||
|
ViewInit,
|
||||||
|
ViewUpdate(String),
|
||||||
|
ViewRender(String),
|
||||||
|
ViewRenderAppExit,
|
||||||
|
ViewRenderServiceState(DesktopServiceState),
|
||||||
|
}
|
||||||
46
ui/src/usecase/watcher.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use std::{path::Path, time::Duration};
|
||||||
|
|
||||||
|
use super::Event;
|
||||||
|
use crate::path;
|
||||||
|
use async_std::task::{sleep, spawn_blocking};
|
||||||
|
use crossbeam_channel::{bounded, Sender};
|
||||||
|
use notify::{Config, RecommendedWatcher, RecursiveMode, Result, Watcher};
|
||||||
|
|
||||||
|
pub async fn create(sender: Sender<Event>) {
|
||||||
|
loop {
|
||||||
|
let watch_sender = sender.clone();
|
||||||
|
match spawn_blocking(|| {
|
||||||
|
watch(
|
||||||
|
format!("{}/logs/", path().to_str().unwrap_or_default()),
|
||||||
|
watch_sender,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => println!("error: {e}"),
|
||||||
|
}
|
||||||
|
sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch<P: AsRef<Path>>(path: P, sender: Sender<Event>) -> Result<()> {
|
||||||
|
let (tx, rx) = bounded(10);
|
||||||
|
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
|
||||||
|
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
|
||||||
|
for res in rx {
|
||||||
|
let event = res?;
|
||||||
|
for p in event.paths {
|
||||||
|
let path = p
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_owned();
|
||||||
|
if path.len() > 0 {
|
||||||
|
sender.send(Event::FileChange(path)).unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
89
ui/tauri.conf.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"beforeBuildCommand": "npm run build",
|
||||||
|
"beforeDevCommand": "npm run dev",
|
||||||
|
"devPath": "http://127.0.0.1:5177/",
|
||||||
|
"distDir": "html/dist",
|
||||||
|
"withGlobalTauri": true
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"productName": "rustdesk_server",
|
||||||
|
"version": "0.1.2"
|
||||||
|
},
|
||||||
|
"tauri": {
|
||||||
|
"allowlist": {
|
||||||
|
"all": false,
|
||||||
|
"fs": {
|
||||||
|
"scope": [
|
||||||
|
"$RESOURCE/bin/.env",
|
||||||
|
"$RESOURCE/logs/*"
|
||||||
|
],
|
||||||
|
"all": false,
|
||||||
|
"exists": true,
|
||||||
|
"readDir": true,
|
||||||
|
"readFile": true,
|
||||||
|
"writeFile": true
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
|
"shell": {
|
||||||
|
"all": false,
|
||||||
|
"open": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"category": "DeveloperTool",
|
||||||
|
"copyright": "",
|
||||||
|
"deb": {
|
||||||
|
"depends": []
|
||||||
|
},
|
||||||
|
"externalBin": [],
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"identifier": "rustdesk.server",
|
||||||
|
"longDescription": "",
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": null,
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"frameworks": [],
|
||||||
|
"providerShortName": null,
|
||||||
|
"signingIdentity": null
|
||||||
|
},
|
||||||
|
"resources": [],
|
||||||
|
"shortDescription": "",
|
||||||
|
"targets": "all",
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
},
|
||||||
|
"systemTray": {
|
||||||
|
"iconPath": "icons/icon.ico",
|
||||||
|
"iconAsTemplate": true
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"center": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"height": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"title": "RustDesk Server",
|
||||||
|
"width": 980
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||