Compare commits
457 Commits
3.1.0
...
second_ada
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08c506a489 | ||
|
|
9d49c5fb77 | ||
|
|
b11c6fc6fe | ||
|
|
acfb021daa | ||
|
|
394390ef1e | ||
|
|
e25d153d33 | ||
|
|
ddb05d594e | ||
|
|
bfc88ff171 | ||
|
|
1cbb7e8c14 | ||
|
|
50d91526fb | ||
|
|
77a411f1d7 | ||
|
|
4216e50e6e | ||
|
|
b3b4adf152 | ||
|
|
b4694313a0 | ||
|
|
abb2cae1f8 | ||
|
|
b0004fd9dc | ||
|
|
19fe61ed29 | ||
|
|
72de38b4fb | ||
|
|
02c0f96e5e | ||
|
|
5e9f688000 | ||
|
|
6a7e346695 | ||
|
|
071738116e | ||
|
|
ae4ee6431d | ||
|
|
d1ccde2a4b | ||
|
|
4848091203 | ||
|
|
282f159311 | ||
|
|
4ef8c77a2d | ||
|
|
08c1cf2439 | ||
|
|
2fc33875bb | ||
|
|
9e92e4b5ff | ||
|
|
7f2cf70bf5 | ||
|
|
8164026891 | ||
|
|
0e23b3a1ac | ||
|
|
1739d4861e | ||
|
|
a6b6e7850d | ||
|
|
3e9dea6f07 | ||
|
|
1b37ca805f | ||
|
|
c772f56da7 | ||
|
|
bc183e39bb | ||
|
|
306d4f70a8 | ||
|
|
a386d39495 | ||
|
|
22b14dff5f | ||
|
|
e749cc7578 | ||
|
|
6a12cad1c9 | ||
|
|
c15665803d | ||
|
|
97090888d5 | ||
|
|
4642308fbb | ||
|
|
59bccb1188 | ||
|
|
cd8fc007ac | ||
|
|
7cfb38307e | ||
|
|
994aa32745 | ||
|
|
f0b872e86b | ||
|
|
0c33432436 | ||
|
|
0bb4dd9442 | ||
|
|
7a54dc15da | ||
|
|
e16a1100d8 | ||
|
|
99214e22e3 | ||
|
|
c77d35a2ed | ||
|
|
d98fdbdc5c | ||
|
|
4551cf0a21 | ||
|
|
023c3474d2 | ||
|
|
2a4cefb4bf | ||
|
|
09305724fa | ||
|
|
360fda1ba7 | ||
|
|
dadf0cf96e | ||
|
|
3d60ac751e | ||
|
|
32793eef8c | ||
|
|
da1cdfd6fa | ||
|
|
58ad7dc161 | ||
|
|
0a15f44193 | ||
|
|
e1dec3c1ba | ||
|
|
7834860245 | ||
|
|
2da1025f26 | ||
|
|
78c83b2e21 | ||
|
|
414a47e2f2 | ||
|
|
6c78b4ec8f | ||
|
|
a6949bd3ae | ||
|
|
f7bed04ab2 | ||
|
|
6ec773079c | ||
|
|
366e27a321 | ||
|
|
32c304dc1b | ||
|
|
338499247d | ||
|
|
79e1761c1f | ||
|
|
4ea1a19572 | ||
|
|
e2ae341ba9 | ||
|
|
de03435bac | ||
|
|
e16c425f87 | ||
|
|
c461e00c5c | ||
|
|
fcf6bb43b7 | ||
|
|
f5f72f87a6 | ||
|
|
3340451245 | ||
|
|
c14f1b5000 | ||
|
|
a46e55d5c2 | ||
|
|
4b64bfaec0 | ||
|
|
2f0c1eeecc | ||
|
|
546d4c1d3d | ||
|
|
160d88f002 | ||
|
|
a83cd29f72 | ||
|
|
94304b5777 | ||
|
|
61ddfe01a1 | ||
|
|
00d334f704 | ||
|
|
f4a4979997 | ||
|
|
03171e4743 | ||
|
|
5369e68267 | ||
|
|
9eb23e38bd | ||
|
|
2a0166bb26 | ||
|
|
2df612ec1f | ||
|
|
36ba3758db | ||
|
|
7cc0f39d3c | ||
|
|
9cf5590371 | ||
|
|
81f835458f | ||
|
|
e01b1db706 | ||
|
|
cdb18de305 | ||
|
|
8e0eef3316 | ||
|
|
221d45f564 | ||
|
|
2a4a01a4be | ||
|
|
501670bdd2 | ||
|
|
24637a1693 | ||
|
|
7bd1340190 | ||
|
|
cb5c09d967 | ||
|
|
a01ba5909c | ||
|
|
1c4678af95 | ||
|
|
5f5435c645 | ||
|
|
2f7dc2c46c | ||
|
|
9bc1c9dd03 | ||
|
|
4c81cdb4a2 | ||
|
|
3406ffa7a2 | ||
|
|
6d05b6845e | ||
|
|
29b4966119 | ||
|
|
d0f8358431 | ||
|
|
a75bd07cd8 | ||
|
|
8c1835950b | ||
|
|
c7cd8e4c80 | ||
|
|
f65e4066e3 | ||
|
|
37c18c5d3c | ||
|
|
8885f580b2 | ||
|
|
512ac74ee6 | ||
|
|
384ce9853b | ||
|
|
b6d2030041 | ||
|
|
3ac09181c6 | ||
|
|
ffc9e5823a | ||
|
|
f5448fed59 | ||
|
|
8163e51434 | ||
|
|
3836836c72 | ||
|
|
b78bf39767 | ||
|
|
7c8399ce88 | ||
|
|
9fe2a1dd41 | ||
|
|
846f554157 | ||
|
|
d1f66cbf4d | ||
|
|
a4624c7377 | ||
|
|
10435cea69 | ||
|
|
ce9a23e021 | ||
|
|
4b7c8f21c2 | ||
|
|
6ddebdbbd1 | ||
|
|
d92729d346 | ||
|
|
fa06dbbd29 | ||
|
|
5d59a1a10e | ||
|
|
222a251180 | ||
|
|
9d6559f0d7 | ||
|
|
da02f49850 | ||
|
|
992961c488 | ||
|
|
0ce30a4e81 | ||
|
|
bb2d794b6f | ||
|
|
45dc302de4 | ||
|
|
4a2706a9d9 | ||
|
|
00be3c3ccc | ||
|
|
cb7fe50d46 | ||
|
|
d364dbac2c | ||
|
|
042788bec3 | ||
|
|
def261f578 | ||
|
|
c08e23085e | ||
|
|
e01dd2bf57 | ||
|
|
61396ec82e | ||
|
|
357c283437 | ||
|
|
1b38cd6ca7 | ||
|
|
bdfa8bfe5b | ||
|
|
7f2ef65fe6 | ||
|
|
102d0472c7 | ||
|
|
445fc6efb1 | ||
|
|
135726f177 | ||
|
|
671ca0a66f | ||
|
|
aa4a79934a | ||
|
|
16fc0617e4 | ||
|
|
64a2f3f8bb | ||
|
|
b7a65343af | ||
|
|
5c121ea48d | ||
|
|
673f28ed64 | ||
|
|
3fb97d16bb | ||
|
|
079c9176ef | ||
|
|
9377a0b545 | ||
|
|
1357c4a309 | ||
|
|
d77be5a244 | ||
|
|
08863edb52 | ||
|
|
3a77705142 | ||
|
|
1eafa9a38a | ||
|
|
396b7aac18 | ||
|
|
08defbbbd8 | ||
|
|
79d371fb76 | ||
|
|
9df262d502 | ||
|
|
6f392ce126 | ||
|
|
a83ec10b61 | ||
|
|
a93f75fb5a | ||
|
|
2353cc4f2c | ||
|
|
30709c66ef | ||
|
|
70e6a3d303 | ||
|
|
cc89939d05 | ||
|
|
7d4a01c757 | ||
|
|
e2d61cb518 | ||
|
|
fb1a9c9867 | ||
|
|
776ae04cbe | ||
|
|
2447ab4305 | ||
|
|
52124b15e8 | ||
|
|
b1e9e8677b | ||
|
|
617e772cc1 | ||
|
|
27f770604b | ||
|
|
5bfc581ad2 | ||
|
|
2664a52007 | ||
|
|
6dbbf1fc89 | ||
|
|
c254f2fdc4 | ||
|
|
cf450fa4e4 | ||
|
|
304f29bfac | ||
|
|
50b8b3d649 | ||
|
|
4e6c1094f3 | ||
|
|
a6134ca10f | ||
|
|
3d999a503c | ||
|
|
39c2124a26 | ||
|
|
4e3955b39d | ||
|
|
784ae0da53 | ||
|
|
eaede032b4 | ||
|
|
4ed153373f | ||
|
|
07d7fac490 | ||
|
|
5535b6a6e3 | ||
|
|
b7fbb84a58 | ||
|
|
19bd94ed02 | ||
|
|
54b45a36e1 | ||
|
|
ed1afa7549 | ||
|
|
2986a18c8f | ||
|
|
b2072c06b7 | ||
|
|
762018883f | ||
|
|
0322c01c0e | ||
|
|
16ccfb8714 | ||
|
|
68095700a2 | ||
|
|
058f8b544e | ||
|
|
4cb871849b | ||
|
|
423305c35a | ||
|
|
b55313527e | ||
|
|
af53c456ea | ||
|
|
37024eb91d | ||
|
|
ee99565b63 | ||
|
|
1a8c08799f | ||
|
|
8b08a5bee0 | ||
|
|
51497d87e0 | ||
|
|
7ede1a8d83 | ||
|
|
b4df5c076e | ||
|
|
52400252dd | ||
|
|
49923c4214 | ||
|
|
81b77c9688 | ||
|
|
7284bb54bc | ||
|
|
6afdd8375d | ||
|
|
af23d9fd14 | ||
|
|
e7aead292c | ||
|
|
fd2678ce2f | ||
|
|
a6d660e708 | ||
|
|
de35a26285 | ||
|
|
18bb045e9a | ||
|
|
6fa9994366 | ||
|
|
665f2412f1 | ||
|
|
395099aa40 | ||
|
|
97a72a9ee2 | ||
|
|
3414202b7b | ||
|
|
52e5453d56 | ||
|
|
dd039a612f | ||
|
|
f5ab034aeb | ||
|
|
893ec2d61c | ||
|
|
ff41b26e94 | ||
|
|
d4d6fbab88 | ||
|
|
e38fe871b2 | ||
|
|
152d7bc3b3 | ||
|
|
9e7cf3ccd9 | ||
|
|
f7370a0280 | ||
|
|
814c574f26 | ||
|
|
fd9f9ee178 | ||
|
|
29e8f8f5fb | ||
|
|
279692afea | ||
|
|
fd09321f8e | ||
|
|
ad236baa86 | ||
|
|
8965b1fbba | ||
|
|
9b32411659 | ||
|
|
32dda9b904 | ||
|
|
c0cb5b96bf | ||
|
|
ff60030ffb | ||
|
|
f40bf2d9ba | ||
|
|
9eebee3ce3 | ||
|
|
8a3bdf136b | ||
|
|
f62076d3fd | ||
|
|
92f4d6b392 | ||
|
|
96ffd7e147 | ||
|
|
07c38e9b6c | ||
|
|
c0aca97083 | ||
|
|
2fd25f53cc | ||
|
|
2b3383a163 | ||
|
|
421a27ceae | ||
|
|
10022451b4 | ||
|
|
3c8d923299 | ||
|
|
154044e32a | ||
|
|
bfc8c10f3d | ||
|
|
d93b5a7b5c | ||
|
|
4ae608ed93 | ||
|
|
e2aef1fc1d | ||
|
|
16cadfeae8 | ||
|
|
3c9b42b9f7 | ||
|
|
9c0f27edb4 | ||
|
|
f81ee1b267 | ||
|
|
a964d955f4 | ||
|
|
f11c65c393 | ||
|
|
f8e5e9f675 | ||
|
|
72eb36f5b3 | ||
|
|
97c0fe1ece | ||
|
|
844b552bf3 | ||
|
|
6bb85deca6 | ||
|
|
551f7616f0 | ||
|
|
285c508329 | ||
|
|
f751657903 | ||
|
|
89096554e8 | ||
|
|
4bf6cce4ba | ||
|
|
85eae0b74a | ||
|
|
0a5657738e | ||
|
|
3aa0adbf39 | ||
|
|
7dc21ce8a7 | ||
|
|
6a6b200861 | ||
|
|
1c7868312d | ||
|
|
90d1c16783 | ||
|
|
3cfca046ba | ||
|
|
85414eb65f | ||
|
|
fdff57da7c | ||
|
|
7c223feef5 | ||
|
|
3740cb2c30 | ||
|
|
b5dd48ad7b | ||
|
|
e46025739a | ||
|
|
66a3538d05 | ||
|
|
e1fa24c251 | ||
|
|
a76e22c021 | ||
|
|
c166327835 | ||
|
|
4ab006f065 | ||
|
|
7eaaef6e75 | ||
|
|
c4f94efe24 | ||
|
|
2eb729d712 | ||
|
|
0343b6cf98 | ||
|
|
7fc4ea0c68 | ||
|
|
195a3ab170 | ||
|
|
a96f485e3c | ||
|
|
1b3a32f83f | ||
|
|
cacf74af3c | ||
|
|
4e9f68acff | ||
|
|
b58295d1d6 | ||
|
|
cbcf187814 | ||
|
|
4baa003c0d | ||
|
|
8cf8c3c122 | ||
|
|
e3e2c0ab6a | ||
|
|
e8862a3811 | ||
|
|
1c1a82696b | ||
|
|
a2893bac7e | ||
|
|
0eda42f29f | ||
|
|
810da0db61 | ||
|
|
8f6aa950cd | ||
|
|
36a2482165 | ||
|
|
639c18395b | ||
|
|
fe08fd3f0a | ||
|
|
29bef052c7 | ||
|
|
a74736b100 | ||
|
|
3f7e7f2601 | ||
|
|
259eff3fea | ||
|
|
c271235d16 | ||
|
|
7539afa91e | ||
|
|
4c79905f5b | ||
|
|
23ad006187 | ||
|
|
f7926847ac | ||
|
|
420c616e9d | ||
|
|
822009ec5f | ||
|
|
b5e1c78461 | ||
|
|
0060f57b63 | ||
|
|
ddaa5b784d | ||
|
|
a40f365a54 | ||
|
|
3964bffce4 | ||
|
|
a8deb3593b | ||
|
|
e0d6e0117e | ||
|
|
e157160337 | ||
|
|
14fa0b4fd3 | ||
|
|
c1c68cf72d | ||
|
|
591d98d8b6 | ||
|
|
e0d93eaa9f | ||
|
|
0c40a954fa | ||
|
|
784f99a900 | ||
|
|
90ae0b3e44 | ||
|
|
2c429fd406 | ||
|
|
ebcca0c3b8 | ||
|
|
925fd9f268 | ||
|
|
0058edc24e | ||
|
|
aa66133813 | ||
|
|
66f9a82f31 | ||
|
|
1092abe776 | ||
|
|
0411792ca5 | ||
|
|
b9a13d3a32 | ||
|
|
0a1359ed16 | ||
|
|
5b8a0881b7 | ||
|
|
5d677a9115 | ||
|
|
75489c00c2 | ||
|
|
3aaa7b62ef | ||
|
|
c13b9754eb | ||
|
|
a97417fd38 | ||
|
|
43261f8469 | ||
|
|
b32935dd97 | ||
|
|
35660ff5e7 | ||
|
|
9b07909ed8 | ||
|
|
b4eb317b00 | ||
|
|
464d77dfb5 | ||
|
|
d0c9c1043c | ||
|
|
795405c47d | ||
|
|
2ef53c6df9 | ||
|
|
249be451f7 | ||
|
|
3a264e6baf | ||
|
|
4224e8314b | ||
|
|
7b14ad9616 | ||
|
|
be7386f0d7 | ||
|
|
cd3263db50 | ||
|
|
3034019d5a | ||
|
|
1fd48a1cf8 | ||
|
|
c3f39ad24d | ||
|
|
68d9394d9f | ||
|
|
80fca589af | ||
|
|
420c33d3ba | ||
|
|
de0cd976de | ||
|
|
1e180489a4 | ||
|
|
e00656d757 | ||
|
|
ca6b7fbeb2 | ||
|
|
0479113949 | ||
|
|
acca85b99a | ||
|
|
03a0e2084a | ||
|
|
116fa6777b | ||
|
|
35d4222c7a | ||
|
|
dd0de7e8be | ||
|
|
e3e7503a7c | ||
|
|
b66f4bf2be | ||
|
|
1c8dbae359 | ||
|
|
4f36349630 | ||
|
|
68b27451f2 | ||
|
|
c7acd63ea7 | ||
|
|
cfc17cf290 | ||
|
|
904e173037 | ||
|
|
a9ebf534c6 | ||
|
|
87f01007cc | ||
|
|
3d63d6c0f2 | ||
|
|
905a3a30f3 | ||
|
|
ec96c1b534 | ||
|
|
c74c5e0c6d | ||
|
|
167d57408d | ||
|
|
8e61d77497 |
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Log files**
|
||||
Attach log files to help explain your problem.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 10]
|
||||
- Version [e.g. 2.1.2]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 2.1.2]
|
||||
|
||||
**Server (please complete the following information):**
|
||||
- OS: [e.g. Ubuntu 22.04]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
27
.github/workflows/deploy.yml
vendored
@@ -1,7 +1,12 @@
|
||||
name: 'Deploy workflow'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
on: [push]
|
||||
env:
|
||||
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/ # https://download.qt.io/static/mirrorlist/
|
||||
|
||||
jobs:
|
||||
Build-Linux-Ubuntu:
|
||||
@@ -25,7 +30,7 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v3
|
||||
@@ -89,7 +94,7 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
@@ -119,15 +124,14 @@ jobs:
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-IOS:
|
||||
name: 'Build-IOS'
|
||||
Build-iOS:
|
||||
name: 'Build-iOS'
|
||||
runs-on: macos-12
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.5.2
|
||||
|
||||
steps:
|
||||
# Just select XCode
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
@@ -143,6 +147,7 @@ jobs:
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install iOS Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -154,7 +159,7 @@ jobs:
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v3
|
||||
@@ -174,7 +179,7 @@ jobs:
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: Install dependencies
|
||||
- name: 'Install dependencies'
|
||||
run: pip install jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
@@ -232,7 +237,7 @@ jobs:
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v3
|
||||
@@ -296,7 +301,7 @@ jobs:
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -309,7 +314,7 @@ jobs:
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Grant execute permission for qt-cmake'
|
||||
shell: bash
|
||||
|
||||
64
.github/workflows/tag-upload.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
name: upload
|
||||
steps:
|
||||
- name: Checkout CMakeLists.txt
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
run: |
|
||||
GIT_TAG=${{ github.ref_name }}
|
||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||
|
||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
||||
else
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
||||
out-file-path: ${{ github.ref_name }}
|
||||
|
||||
- name: Upload beta version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'dev')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: beta/${{ github.ref_name }}
|
||||
|
||||
- name: Upload stable version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'master')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: stable/${{ github.ref_name }}
|
||||
1
.gitignore
vendored
@@ -8,6 +8,7 @@ deploy/build/*
|
||||
deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
|
||||
|
||||
# Qt-es
|
||||
|
||||
6
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "client/3rd/wireguard-apple"]
|
||||
path = client/3rd/wireguard-apple
|
||||
url = https://github.com/WireGuard/wireguard-apple
|
||||
[submodule "client/3rd/OpenVPNAdapter"]
|
||||
path = client/3rd/OpenVPNAdapter
|
||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
|
||||
@@ -25,3 +22,6 @@
|
||||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
[submodule "client/3rd/awg-apple"]
|
||||
path = client/3rd/awg-apple
|
||||
url = https://github.com/amnezia-vpn/awg-apple
|
||||
|
||||
@@ -2,11 +2,14 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 3.1.0.0
|
||||
project(${PROJECT} VERSION 4.1.0.1
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
set(RELEASE_DATE "2023-08-28")
|
||||
|
||||
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
|
||||
1
client/3rd/awg-apple
vendored
Submodule
2
client/3rd/qtkeychain
vendored
1
client/3rd/wireguard-apple
vendored
@@ -10,30 +10,34 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
|
||||
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen")
|
||||
|
||||
set(PACKAGES
|
||||
Widgets Core Gui Network Xml
|
||||
Core Gui Network Xml
|
||||
RemoteObjects Quick Svg QuickControls2
|
||||
Core5Compat Concurrent LinguistTools
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(PACKAGES
|
||||
${PACKAGES}
|
||||
Multimedia
|
||||
)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
Qt6::Widgets Qt6::Core Qt6::Gui
|
||||
Qt6::Core Qt6::Gui
|
||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(LIBS
|
||||
${LIBS}
|
||||
Qt6::Multimedia
|
||||
)
|
||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
@@ -46,12 +50,30 @@ endif()
|
||||
|
||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||
|
||||
qt6_add_translations(${PROJECT} TS_FILES
|
||||
# -- i18n begin
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||
|
||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||
|
||||
set(QM_FILE_LIST "")
|
||||
foreach(FILE ${AMNEZIAVPN_QM_FILES})
|
||||
get_filename_component(QM_FILE_NAME ${FILE} NAME)
|
||||
list(APPEND QM_FILE_LIST "<file>${QM_FILE_NAME}</file>")
|
||||
endforeach()
|
||||
string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST})
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
# -- i18n end
|
||||
|
||||
if(IOS)
|
||||
#execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh)
|
||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
endif()
|
||||
@@ -91,7 +113,6 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
@@ -128,7 +149,6 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
@@ -162,20 +182,33 @@ file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/
|
||||
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
|
||||
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp)
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
|
||||
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${COMMON_FILES_H}
|
||||
${PAGE_LOGIC_H}
|
||||
${CONFIGURATORS_H}
|
||||
${UI_MODELS_H}
|
||||
${UI_CONTROLLERS_H}
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${COMMON_FILES_CPP}
|
||||
${PAGE_LOGIC_CPP}
|
||||
${CONFIGURATORS_CPP}
|
||||
${UI_MODELS_CPP}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
@@ -248,6 +281,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
@@ -258,6 +292,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -307,16 +342,5 @@ if(NOT IOS AND NOT ANDROID)
|
||||
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy,true>
|
||||
$<TARGET_FILE_DIR:${PROJECT}>/../service/wireguard-service/wireguard-service.exe
|
||||
$<TARGET_FILE_DIR:${PROJECT}>/wireguard/wireguard-service.exe
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC})
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
qt_finalize_target(${PROJECT})
|
||||
|
||||
@@ -3,55 +3,35 @@
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QMimeData>
|
||||
#include <QQuickStyle>
|
||||
#include <QResource>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
#include <QQuickItem>
|
||||
|
||||
#include "core/servercontroller.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
#include <QQuickStyle>
|
||||
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
|
||||
#include "ui/pages.h"
|
||||
|
||||
#include "ui/pages_logic/AppSettingsLogic.h"
|
||||
#include "ui/pages_logic/GeneralSettingsLogic.h"
|
||||
#include "ui/pages_logic/NetworkSettingsLogic.h"
|
||||
#include "ui/pages_logic/NewServerProtocolsLogic.h"
|
||||
#include "ui/pages_logic/QrDecoderLogic.h"
|
||||
#include "ui/pages_logic/ServerConfiguringProgressLogic.h"
|
||||
#include "ui/pages_logic/ServerContainersLogic.h"
|
||||
#include "ui/pages_logic/ServerListLogic.h"
|
||||
#include "ui/pages_logic/ServerSettingsLogic.h"
|
||||
#include "ui/pages_logic/ServerContainersLogic.h"
|
||||
#include "ui/pages_logic/ShareConnectionLogic.h"
|
||||
#include "ui/pages_logic/SitesLogic.h"
|
||||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
#include "ui/pages_logic/VpnLogic.h"
|
||||
#include "ui/pages_logic/WizardLogic.h"
|
||||
|
||||
#include "ui/pages_logic/protocols/CloakLogic.h"
|
||||
#include "ui/pages_logic/protocols/OpenVpnLogic.h"
|
||||
#include "ui/pages_logic/protocols/ShadowSocksLogic.h"
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
#include "protocols/qml_register_protocols.h"
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/QtAppDelegate-C-Interface.h"
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]):
|
||||
AMNEZIA_BASE_CLASS(argc, argv)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
#else
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary,
|
||||
SingleApplication::Options options, int timeout, const QString &userData):
|
||||
SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
|
||||
int timeout, const QString &userData)
|
||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
||||
#endif
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
@@ -73,49 +53,97 @@
|
||||
#endif
|
||||
|
||||
m_settings = std::shared_ptr<Settings>(new Settings);
|
||||
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
|
||||
}
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
m_vpnConnectionThread.quit();
|
||||
m_vpnConnectionThread.wait(3000);
|
||||
|
||||
if (m_engine) {
|
||||
QObject::disconnect(m_engine, 0,0,0);
|
||||
QObject::disconnect(m_engine, 0, 0, 0);
|
||||
delete m_engine;
|
||||
}
|
||||
if (m_uiLogic) {
|
||||
QObject::disconnect(m_uiLogic, 0,0,0);
|
||||
delete m_uiLogic;
|
||||
}
|
||||
|
||||
if (m_protocolProps) delete m_protocolProps;
|
||||
if (m_containerProps) delete m_containerProps;
|
||||
}
|
||||
|
||||
void AmneziaApplication::init()
|
||||
{
|
||||
m_engine = new QQmlApplicationEngine;
|
||||
m_uiLogic = new UiLogic(m_settings, m_configurator);
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main.qml"));
|
||||
QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated,
|
||||
this, [url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
}, Qt::QueuedConnection);
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
QObject::connect(
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||
m_uiLogic->registerPagesLogic();
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
setStartPageLogic(m_uiLogic->pageLogic<StartPageLogic>());
|
||||
IosController::Instance()->initialize();
|
||||
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
|
||||
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
|
||||
initModels();
|
||||
loadTranslator();
|
||||
initControllers();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::initialized, this,
|
||||
[this](bool status, bool connected, const QDateTime &connectionDate) {
|
||||
if (connected) {
|
||||
m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected);
|
||||
if (m_vpnConnection)
|
||||
m_vpnConnection->restoreConnection();
|
||||
}
|
||||
});
|
||||
if (!AndroidController::instance()->initialize()) {
|
||||
qCritical() << QString("Init failed");
|
||||
if (m_vpnConnection)
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
|
||||
m_pageController->replaceStartPage();
|
||||
m_importController->extractConfigFromData(data);
|
||||
m_pageController->goToPageViewConfig();
|
||||
});
|
||||
#endif
|
||||
|
||||
m_engine->load(url);
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
|
||||
m_pageController->replaceStartPage();
|
||||
m_importController->extractConfigFromData(data);
|
||||
m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
if (m_engine->rootObjects().size() > 0) {
|
||||
m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0));
|
||||
}
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
|
||||
m_pageController->replaceStartPage();
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
#endif
|
||||
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
&NotificationHandler::setConnectionState);
|
||||
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
|
||||
&PageController::raiseMainWindow);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||
&ConnectionController::openConnection);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
|
||||
&NotificationHandler::onTranslationsUpdated);
|
||||
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
|
||||
if (m_settings->isSaveLogs()) {
|
||||
if (!Logger::init()) {
|
||||
@@ -124,19 +152,20 @@ void AmneziaApplication::init()
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_parser.isSet("a")) m_uiLogic->showOnStartup();
|
||||
else emit m_uiLogic->show();
|
||||
if (m_parser.isSet("a"))
|
||||
m_pageController->showOnStartup();
|
||||
else
|
||||
emit m_pageController->raiseMainWindow();
|
||||
#else
|
||||
m_uiLogic->showOnStartup();
|
||||
m_pageController->showOnStartup();
|
||||
#endif
|
||||
|
||||
// TODO - fix
|
||||
// TODO - fix
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (isPrimary()) {
|
||||
QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){
|
||||
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
|
||||
qDebug() << "Secondary instance started, showing this window instead";
|
||||
emit m_uiLogic->show();
|
||||
emit m_uiLogic->raise();
|
||||
emit m_pageController->raiseMainWindow();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
@@ -144,7 +173,7 @@ void AmneziaApplication::init()
|
||||
// Android TextField clipboard workaround
|
||||
// https://bugreports.qt.io/browse/QTBUG-113461
|
||||
#ifdef Q_OS_ANDROID
|
||||
QObject::connect(qApp, &QApplication::applicationStateChanged, [](Qt::ApplicationState state) {
|
||||
QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) {
|
||||
if (state == Qt::ApplicationActive) {
|
||||
if (qApp->clipboard()->mimeData()->formats().contains("text/html")) {
|
||||
QTextDocument doc;
|
||||
@@ -158,55 +187,64 @@ void AmneziaApplication::init()
|
||||
|
||||
void AmneziaApplication::registerTypes()
|
||||
{
|
||||
qRegisterMetaType<VpnProtocol::VpnConnectionState>("VpnProtocol::VpnConnectionState");
|
||||
qRegisterMetaType<ServerCredentials>("ServerCredentials");
|
||||
|
||||
qRegisterMetaType<DockerContainer>("DockerContainer");
|
||||
qRegisterMetaType<TransportProto>("TransportProto");
|
||||
qRegisterMetaType<Proto>("Proto");
|
||||
qRegisterMetaType<ServiceType>("ServiceType");
|
||||
qRegisterMetaType<Page>("Page");
|
||||
qRegisterMetaType<VpnProtocol::VpnConnectionState>("ConnectionState");
|
||||
|
||||
qRegisterMetaType<PageProtocolLogicBase *>("PageProtocolLogicBase *");
|
||||
|
||||
|
||||
declareQmlPageEnum();
|
||||
declareQmlProtocolEnum();
|
||||
declareQmlContainerEnum();
|
||||
|
||||
qmlRegisterType<PageType>("PageType", 1, 0, "PageType");
|
||||
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
|
||||
|
||||
m_containerProps = new ContainerProps;
|
||||
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps);
|
||||
m_containerProps.reset(new ContainerProps());
|
||||
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
|
||||
|
||||
m_protocolProps = new ProtocolProps;
|
||||
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps);
|
||||
m_protocolProps.reset(new ProtocolProps());
|
||||
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
|
||||
|
||||
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
||||
"ContainersModelFilters");
|
||||
|
||||
//
|
||||
Vpn::declareQmlVpnConnectionStateEnum();
|
||||
PageLoader::declareQmlPageEnum();
|
||||
}
|
||||
|
||||
void AmneziaApplication::loadFonts()
|
||||
{
|
||||
QQuickStyle::setStyle("Basic");
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Bold.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-BoldItalic.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Italic.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Light.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-LightItalic.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
||||
}
|
||||
|
||||
void AmneziaApplication::loadTranslator()
|
||||
{
|
||||
m_translator = new QTranslator;
|
||||
if (m_translator->load(QLocale(), QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/translations"))) {
|
||||
installTranslator(m_translator);
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
}
|
||||
|
||||
void AmneziaApplication::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
}
|
||||
|
||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
||||
if (m_translator->load(strFileName)) {
|
||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
}
|
||||
} else {
|
||||
m_settings->setAppLanguage(QLocale::English);
|
||||
}
|
||||
|
||||
m_engine->retranslate();
|
||||
|
||||
emit translationsUpdated();
|
||||
}
|
||||
|
||||
bool AmneziaApplication::parseCommands()
|
||||
@@ -215,19 +253,17 @@ bool AmneziaApplication::parseCommands()
|
||||
m_parser.addHelpOption();
|
||||
m_parser.addVersionOption();
|
||||
|
||||
QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"};
|
||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
||||
m_parser.addOption(c_autostart);
|
||||
|
||||
QCommandLineOption c_cleanup {{"c", "cleanup"}, "Cleanup logs"};
|
||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
||||
m_parser.addOption(c_cleanup);
|
||||
|
||||
m_parser.process(*this);
|
||||
|
||||
if (m_parser.isSet(c_cleanup)) {
|
||||
Logger::cleanUp();
|
||||
QTimer::singleShot(100, this, [this]{
|
||||
quit();
|
||||
});
|
||||
QTimer::singleShot(100, this, [this] { quit(); });
|
||||
exec();
|
||||
return false;
|
||||
}
|
||||
@@ -239,3 +275,92 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
return m_engine;
|
||||
}
|
||||
|
||||
void AmneziaApplication::initModels()
|
||||
{
|
||||
m_containersModel.reset(new ContainersModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||
connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(),
|
||||
&ContainersModel::updateContainersConfig);
|
||||
|
||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||
connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(),
|
||||
&ContainersModel::setCurrentlyProcessedServerIndex);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(),
|
||||
&ContainersModel::setCurrentlyProcessedServerIndex);
|
||||
connect(m_containersModel.get(), &ContainersModel::containersModelUpdated, m_serversModel.get(),
|
||||
&ServersModel::updateContainersConfig);
|
||||
|
||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
||||
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
|
||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||
|
||||
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
||||
|
||||
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
||||
|
||||
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
||||
|
||||
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
||||
|
||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||
#endif
|
||||
|
||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
}
|
||||
|
||||
void AmneziaApplication::initControllers()
|
||||
{
|
||||
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||
&PageController::showPassphraseRequestDrawer);
|
||||
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
||||
&InstallController::setEncryptedPassphrase);
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator));
|
||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||
|
||||
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||
if (m_settingsController->isAutoStartEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||
}
|
||||
|
||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
|
||||
m_systemController.reset(new SystemController(m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||
}
|
||||
|
||||
@@ -1,27 +1,53 @@
|
||||
#ifndef AMNEZIA_APPLICATION_H
|
||||
#define AMNEZIA_APPLICATION_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "settings.h"
|
||||
#include "vpnconnection.h"
|
||||
|
||||
#include "ui/uilogic.h"
|
||||
#include "configurators/vpn_configurator.h"
|
||||
|
||||
#include "ui/controllers/connectionController.h"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
#include "ui/controllers/installController.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
#include "ui/notificationhandler.h"
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#include "singleapplication.h"
|
||||
#define AMNEZIA_BASE_CLASS SingleApplication
|
||||
#define QAPPLICATION_CLASS QApplication
|
||||
#include "singleapplication.h"
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
@@ -32,7 +58,8 @@ public:
|
||||
AmneziaApplication(int &argc, char *argv[]);
|
||||
#else
|
||||
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
|
||||
SingleApplication::Options options = SingleApplication::User, int timeout = 1000, const QString &userData = {} );
|
||||
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
|
||||
const QString &userData = {});
|
||||
#endif
|
||||
virtual ~AmneziaApplication();
|
||||
|
||||
@@ -40,22 +67,57 @@ public:
|
||||
void registerTypes();
|
||||
void loadFonts();
|
||||
void loadTranslator();
|
||||
void updateTranslator(const QLocale &locale);
|
||||
bool parseCommands();
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
|
||||
private:
|
||||
void initModels();
|
||||
void initControllers();
|
||||
|
||||
QQmlApplicationEngine *m_engine {};
|
||||
UiLogic *m_uiLogic {};
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
std::shared_ptr<VpnConfigurator> m_configurator;
|
||||
|
||||
ContainerProps* m_containerProps {};
|
||||
ProtocolProps* m_protocolProps {};
|
||||
QSharedPointer<ContainerProps> m_containerProps;
|
||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||
|
||||
QTranslator* m_translator;
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
|
||||
#endif
|
||||
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
|
||||
QScopedPointer<ConnectionController> m_connectionController;
|
||||
QScopedPointer<PageController> m_pageController;
|
||||
QScopedPointer<InstallController> m_installController;
|
||||
QScopedPointer<ImportController> m_importController;
|
||||
QScopedPointer<ExportController> m_exportController;
|
||||
QScopedPointer<SettingsController> m_settingsController;
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowNativeHeapPointerTagging="false"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||
android:icon="@drawable/icon">
|
||||
android:icon="@drawable/icon"
|
||||
android:roundIcon="@drawable/icon_round">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
@@ -44,6 +45,7 @@
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:screenOrientation="unspecified"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
|
||||
<!-- android:theme="@style/splashScreenTheme"-->
|
||||
|
||||
@@ -138,8 +138,8 @@ android {
|
||||
resConfig "en"
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 34
|
||||
versionCode 32 // Change to a higher number
|
||||
versionName "3.0.9" // Change to a higher number
|
||||
versionCode 39 // Change to a higher number
|
||||
versionName "4.1.0" // Change to a higher number
|
||||
|
||||
javaCompileOptions.annotationProcessorOptions.arguments = [
|
||||
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
|
||||
|
||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 3.3 KiB |
BIN
client/android/res/drawable-hdpi/icon_round.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
client/android/res/drawable-ldpi/icon_round.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
client/android/res/drawable-mdpi/icon_round.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.7 KiB |
BIN
client/android/res/drawable-xhdpi/icon_round.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
BIN
client/android/res/drawable-xxhdpi/icon_round.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
BIN
client/android/res/drawable-xxxhdpi/icon_round.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -70,6 +70,15 @@ public class BadConfigException extends Exception {
|
||||
EXCLUDED_APPLICATIONS("ExcludedApplications"),
|
||||
INCLUDED_APPLICATIONS("IncludedApplications"),
|
||||
LISTEN_PORT("ListenPort"),
|
||||
JC("Jc"),
|
||||
JMIN("Jmin"),
|
||||
JMAX("Jmax"),
|
||||
S1("S1"),
|
||||
S2("S2"),
|
||||
H1("H1"),
|
||||
H2("H2"),
|
||||
H3("H3"),
|
||||
H4("H4"),
|
||||
MTU("MTU"),
|
||||
PERSISTENT_KEEPALIVE("PersistentKeepalive"),
|
||||
PRE_SHARED_KEY("PresharedKey"),
|
||||
|
||||
509
client/android/src/com/wireguard/config/IPRange.java
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Class that represents a range of IP addresses. This range could be a proper subnet, but that's
|
||||
* not necessarily the case (see {@code getPrefix} and {@code toSubnets}).
|
||||
*/
|
||||
public class IPRange implements Comparable<IPRange>
|
||||
{
|
||||
private final byte[] mBitmask = { (byte)0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
|
||||
private byte[] mFrom;
|
||||
private byte[] mTo;
|
||||
private Integer mPrefix;
|
||||
|
||||
/**
|
||||
* Determine if the range is a proper subnet and, if so, what the network prefix is.
|
||||
*/
|
||||
private void determinePrefix()
|
||||
{
|
||||
boolean matching = true;
|
||||
|
||||
mPrefix = mFrom.length * 8;
|
||||
for (int i = 0; i < mFrom.length; i++)
|
||||
{
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
if (matching)
|
||||
{
|
||||
if ((mFrom[i] & mBitmask[bit]) != (mTo[i] & mBitmask[bit]))
|
||||
{
|
||||
mPrefix = (i * 8) + bit;
|
||||
matching = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((mFrom[i] & mBitmask[bit]) != 0 || (mTo[i] & mBitmask[bit]) == 0)
|
||||
{
|
||||
mPrefix = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IPRange(byte[] from, byte[] to)
|
||||
{
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
determinePrefix();
|
||||
}
|
||||
|
||||
public IPRange(String from, String to) throws UnknownHostException
|
||||
{
|
||||
this(Utils.parseInetAddress(from), Utils.parseInetAddress(to));
|
||||
}
|
||||
|
||||
public IPRange(InetAddress from, InetAddress to)
|
||||
{
|
||||
initializeFromRange(from, to);
|
||||
}
|
||||
|
||||
private void initializeFromRange(InetAddress from, InetAddress to)
|
||||
{
|
||||
byte[] fa = from.getAddress(), ta = to.getAddress();
|
||||
if (fa.length != ta.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid range");
|
||||
}
|
||||
if (compareAddr(fa, ta) < 0)
|
||||
{
|
||||
mFrom = fa;
|
||||
mTo = ta;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTo = fa;
|
||||
mFrom = ta;
|
||||
}
|
||||
determinePrefix();
|
||||
}
|
||||
|
||||
public IPRange(String base, int prefix) throws UnknownHostException
|
||||
{
|
||||
this(Utils.parseInetAddress(base), prefix);
|
||||
}
|
||||
|
||||
public IPRange(InetAddress base, int prefix)
|
||||
{
|
||||
this(base.getAddress(), prefix);
|
||||
}
|
||||
|
||||
private IPRange(byte[] from, int prefix)
|
||||
{
|
||||
initializeFromCIDR(from, prefix);
|
||||
}
|
||||
|
||||
private void initializeFromCIDR(byte[] from, int prefix)
|
||||
{
|
||||
if (from.length != 4 && from.length != 16)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid address");
|
||||
}
|
||||
if (prefix < 0 || prefix > from.length * 8)
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid prefix");
|
||||
}
|
||||
byte[] to = from.clone();
|
||||
byte mask = (byte)(0xff << (8 - prefix % 8));
|
||||
int i = prefix / 8;
|
||||
|
||||
if (i < from.length)
|
||||
{
|
||||
from[i] = (byte)(from[i] & mask);
|
||||
to[i] = (byte)(to[i] | ~mask);
|
||||
Arrays.fill(from, i+1, from.length, (byte)0);
|
||||
Arrays.fill(to, i+1, to.length, (byte)0xff);
|
||||
}
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
mPrefix = prefix;
|
||||
}
|
||||
|
||||
public IPRange(String cidr) throws UnknownHostException
|
||||
{
|
||||
/* only verify the basic structure */
|
||||
if (!cidr.matches("(?i)^(([0-9.]+)|([0-9a-f:]+))(-(([0-9.]+)|([0-9a-f:]+))|(/\\d+))?$"))
|
||||
{
|
||||
throw new IllegalArgumentException("Invalid CIDR or range notation");
|
||||
}
|
||||
if (cidr.contains("-"))
|
||||
{
|
||||
String[] parts = cidr.split("-");
|
||||
InetAddress from = InetAddress.getByName(parts[0]);
|
||||
InetAddress to = InetAddress.getByName(parts[1]);
|
||||
initializeFromRange(from, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
String[] parts = cidr.split("/");
|
||||
InetAddress addr = InetAddress.getByName(parts[0]);
|
||||
byte[] base = addr.getAddress();
|
||||
int prefix = base.length * 8;
|
||||
if (parts.length > 1)
|
||||
{
|
||||
prefix = Integer.parseInt(parts[1]);
|
||||
}
|
||||
initializeFromCIDR(base, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first address of the range. The network ID in case this is a proper subnet.
|
||||
*/
|
||||
public InetAddress getFrom()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress(mFrom);
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last address of the range.
|
||||
*/
|
||||
public InetAddress getTo()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress(mTo);
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this range is a proper subnet returns its prefix, otherwise returns null.
|
||||
*/
|
||||
public Integer getPrefix()
|
||||
{
|
||||
return mPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull IPRange other)
|
||||
{
|
||||
int cmp = compareAddr(mFrom, other.mFrom);
|
||||
if (cmp == 0)
|
||||
{ /* smaller ranges first */
|
||||
cmp = compareAddr(mTo, other.mTo);
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o == null || !(o instanceof IPRange))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this == o || compareTo((IPRange)o) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mPrefix != null)
|
||||
{
|
||||
return InetAddress.getByAddress(mFrom).getHostAddress() + "/" + mPrefix;
|
||||
}
|
||||
return InetAddress.getByAddress(mFrom).getHostAddress() + "-" +
|
||||
InetAddress.getByAddress(mTo).getHostAddress();
|
||||
}
|
||||
catch (UnknownHostException ignored)
|
||||
{
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private int compareAddr(byte a[], byte b[])
|
||||
{
|
||||
if (a.length != b.length)
|
||||
{
|
||||
return (a.length < b.length) ? -1 : 1;
|
||||
}
|
||||
for (int i = 0; i < a.length; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
if (((int)a[i] & 0xff) < ((int)b[i] & 0xff))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this range fully contains the given range.
|
||||
*/
|
||||
public boolean contains(IPRange range)
|
||||
{
|
||||
return compareAddr(mFrom, range.mFrom) <= 0 && compareAddr(range.mTo, mTo) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this and the given range overlap.
|
||||
*/
|
||||
public boolean overlaps(IPRange range)
|
||||
{
|
||||
return !(compareAddr(mTo, range.mFrom) < 0 || compareAddr(range.mTo, mFrom) < 0);
|
||||
}
|
||||
|
||||
private byte[] dec(byte[] addr)
|
||||
{
|
||||
for (int i = addr.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (--addr[i] != (byte)0xff)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
private byte[] inc(byte[] addr)
|
||||
{
|
||||
for (int i = addr.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (++addr[i] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given range from the current range. Returns a list of resulting ranges (these are
|
||||
* not proper subnets). At most two ranges are returned, in case the given range is contained in
|
||||
* this but does not equal it, which would result in an empty list (which is also the case if
|
||||
* this range is fully contained in the given range).
|
||||
*/
|
||||
public List<IPRange> remove(IPRange range)
|
||||
{
|
||||
ArrayList<IPRange> list = new ArrayList<>();
|
||||
if (!overlaps(range))
|
||||
{ /* | this | or | this |
|
||||
* | range | | range | */
|
||||
list.add(this);
|
||||
}
|
||||
else if (!range.contains(this))
|
||||
{ /* we are not completely removed, so none of these cases applies:
|
||||
* | this | or | this | or | this |
|
||||
* | range | | range | | range | */
|
||||
if (compareAddr(mFrom, range.mFrom) < 0 && compareAddr(range.mTo, mTo) < 0)
|
||||
{ /* the removed range is completely within our boundaries:
|
||||
* | this |
|
||||
* | range | */
|
||||
list.add(new IPRange(mFrom, dec(range.mFrom.clone())));
|
||||
list.add(new IPRange(inc(range.mTo.clone()), mTo));
|
||||
}
|
||||
else
|
||||
{ /* one end is within our boundaries the other at or outside it:
|
||||
* | this | or | this | or | this | or | this |
|
||||
* | range | | range | | range | | range | */
|
||||
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : inc(range.mTo.clone());
|
||||
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : dec(range.mFrom.clone());
|
||||
list.add(new IPRange(from, to));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean adjacent(IPRange range)
|
||||
{
|
||||
if (compareAddr(mTo, range.mFrom) < 0)
|
||||
{
|
||||
byte[] to = inc(mTo.clone());
|
||||
return compareAddr(to, range.mFrom) == 0;
|
||||
}
|
||||
byte[] from = dec(mFrom.clone());
|
||||
return compareAddr(from, range.mTo) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two adjacent or overlapping ranges, returns null if it's not possible to merge them.
|
||||
*/
|
||||
public IPRange merge(IPRange range)
|
||||
{
|
||||
if (overlaps(range))
|
||||
{
|
||||
if (contains(range))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
else if (range.contains(this))
|
||||
{
|
||||
return range;
|
||||
}
|
||||
}
|
||||
else if (!adjacent(range))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] from = compareAddr(mFrom, range.mFrom) < 0 ? mFrom : range.mFrom;
|
||||
byte[] to = compareAddr(mTo, range.mTo) > 0 ? mTo : range.mTo;
|
||||
return new IPRange(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given range into a sorted list of proper subnets.
|
||||
*/
|
||||
public List<IPRange> toSubnets()
|
||||
{
|
||||
ArrayList<IPRange> list = new ArrayList<>();
|
||||
if (mPrefix != null)
|
||||
{
|
||||
list.add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0, bit = 0, prefix, netmask, common_byte, common_bit;
|
||||
int from_cur, from_prev = 0, to_cur, to_prev = 1;
|
||||
boolean from_full = true, to_full = true;
|
||||
|
||||
byte[] from = mFrom.clone();
|
||||
byte[] to = mTo.clone();
|
||||
|
||||
/* find a common prefix */
|
||||
while (i < from.length && (from[i] & mBitmask[bit]) == (to[i] & mBitmask[bit]))
|
||||
{
|
||||
if (++bit == 8)
|
||||
{
|
||||
bit = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
prefix = i * 8 + bit;
|
||||
|
||||
/* at this point we know that the addresses are either equal, or that the
|
||||
* current bits in the 'from' and 'to' addresses are 0 and 1, respectively.
|
||||
* we now look at the rest of the bits as two binary trees (0=left, 1=right)
|
||||
* where 'from' and 'to' are both leaf nodes. all leaf nodes between these
|
||||
* nodes are addresses contained in the range. to collect them as subnets
|
||||
* we follow the trees from both leaf nodes to their root node and record
|
||||
* all complete subtrees (right for from, left for to) we come across as
|
||||
* subnets. in that process host bits are zeroed out. if both addresses
|
||||
* are equal we won't enter the loop below.
|
||||
* 0_____|_____1 for the 'from' address we assume we start on a
|
||||
* 0__|__ 1 0__|__1 left subtree (0) and follow the left edges until
|
||||
* _|_ _|_ _|_ _|_ we reach the root of this subtree, which is
|
||||
* | | | | | | | | either the root of this whole 'from'-subtree
|
||||
* 0 1 0 1 0 1 0 1 (causing us to leave the loop) or the root node
|
||||
* of the right subtree (1) of another node (which actually could be the
|
||||
* leaf node we start from). that whole subtree gets recorded as subnet.
|
||||
* next we follow the right edges to the root of that subtree which again is
|
||||
* either the 'from'-root or the root node in the left subtree (0) of
|
||||
* another node. the complete right subtree of that node is the next subnet
|
||||
* we record. from there we assume that we are in that right subtree and
|
||||
* recursively follow right edges to its root. for the 'to' address the
|
||||
* procedure is exactly the same but with left and right reversed.
|
||||
*/
|
||||
if (++bit == 8)
|
||||
{
|
||||
bit = 0;
|
||||
i++;
|
||||
}
|
||||
common_byte = i;
|
||||
common_bit = bit;
|
||||
netmask = from.length * 8;
|
||||
for (i = from.length - 1; i >= common_byte; i--)
|
||||
{
|
||||
int bit_min = (i == common_byte) ? common_bit : 0;
|
||||
for (bit = 7; bit >= bit_min; bit--)
|
||||
{
|
||||
byte mask = mBitmask[bit];
|
||||
|
||||
from_cur = from[i] & mask;
|
||||
if (from_prev == 0 && from_cur != 0)
|
||||
{ /* 0 -> 1: subnet is the whole current (right) subtree */
|
||||
list.add(new IPRange(from.clone(), netmask));
|
||||
from_full = false;
|
||||
}
|
||||
else if (from_prev != 0 && from_cur == 0)
|
||||
{ /* 1 -> 0: invert bit to switch to right subtree and add it */
|
||||
from[i] ^= mask;
|
||||
list.add(new IPRange(from.clone(), netmask));
|
||||
from_cur = 1;
|
||||
}
|
||||
/* clear the current bit */
|
||||
from[i] &= ~mask;
|
||||
from_prev = from_cur;
|
||||
|
||||
to_cur = to[i] & mask;
|
||||
if (to_prev != 0 && to_cur == 0)
|
||||
{ /* 1 -> 0: subnet is the whole current (left) subtree */
|
||||
list.add(new IPRange(to.clone(), netmask));
|
||||
to_full = false;
|
||||
}
|
||||
else if (to_prev == 0 && to_cur != 0)
|
||||
{ /* 0 -> 1: invert bit to switch to left subtree and add it */
|
||||
to[i] ^= mask;
|
||||
list.add(new IPRange(to.clone(), netmask));
|
||||
to_cur = 0;
|
||||
}
|
||||
/* clear the current bit */
|
||||
to[i] &= ~mask;
|
||||
to_prev = to_cur;
|
||||
netmask--;
|
||||
}
|
||||
}
|
||||
|
||||
if (from_full && to_full)
|
||||
{ /* full subnet (from=to or from=0.. and to=1.. after common prefix) - not reachable
|
||||
* due to the shortcut at the top */
|
||||
list.add(new IPRange(from.clone(), prefix));
|
||||
}
|
||||
else if (from_full)
|
||||
{ /* full from subnet (from=0.. after prefix) */
|
||||
list.add(new IPRange(from.clone(), prefix + 1));
|
||||
}
|
||||
else if (to_full)
|
||||
{ /* full to subnet (to=1.. after prefix) */
|
||||
list.add(new IPRange(to.clone(), prefix + 1));
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
223
client/android/src/com/wireguard/config/IPRangeSet.java
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Class that represents a set of IP address ranges (not necessarily proper subnets) and allows
|
||||
* modifying the set and enumerating the resulting subnets.
|
||||
*/
|
||||
public class IPRangeSet implements Iterable<IPRange>
|
||||
{
|
||||
private TreeSet<IPRange> mRanges = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* Parse the given string (space separated ranges in CIDR or range notation) and return the
|
||||
* resulting set or {@code null} if the string was invalid. An empty set is returned if the given string
|
||||
* is {@code null}.
|
||||
*/
|
||||
public static IPRangeSet fromString(String ranges)
|
||||
{
|
||||
IPRangeSet set = new IPRangeSet();
|
||||
if (ranges != null)
|
||||
{
|
||||
for (String range : ranges.split("\\s+"))
|
||||
{
|
||||
try
|
||||
{
|
||||
set.add(new IPRange(range));
|
||||
}
|
||||
catch (Exception unused)
|
||||
{ /* besides due to invalid strings exceptions might get thrown if the string
|
||||
* contains a hostname (NetworkOnMainThreadException) */
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a range to this set. Automatically gets merged with existing ranges.
|
||||
*/
|
||||
public void add(IPRange range)
|
||||
{
|
||||
if (mRanges.contains(range))
|
||||
{
|
||||
return;
|
||||
}
|
||||
reinsert:
|
||||
while (true)
|
||||
{
|
||||
Iterator<IPRange> iterator = mRanges.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
IPRange existing = iterator.next();
|
||||
IPRange replacement = existing.merge(range);
|
||||
if (replacement != null)
|
||||
{
|
||||
iterator.remove();
|
||||
range = replacement;
|
||||
continue reinsert;
|
||||
}
|
||||
}
|
||||
mRanges.add(range);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all ranges from the given set.
|
||||
*/
|
||||
public void add(IPRangeSet ranges)
|
||||
{
|
||||
if (ranges == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (IPRange range : ranges.mRanges)
|
||||
{
|
||||
add(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all ranges from the given collection to this set.
|
||||
*/
|
||||
public void addAll(Collection<? extends IPRange> coll)
|
||||
{
|
||||
for (IPRange range : coll)
|
||||
{
|
||||
add(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given range from this set. Existing ranges are automatically adjusted.
|
||||
*/
|
||||
public void remove(IPRange range)
|
||||
{
|
||||
ArrayList <IPRange> additions = new ArrayList<>();
|
||||
Iterator<IPRange> iterator = mRanges.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
IPRange existing = iterator.next();
|
||||
List<IPRange> result = existing.remove(range);
|
||||
if (result.size() == 0)
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
else if (!result.get(0).equals(existing))
|
||||
{
|
||||
iterator.remove();
|
||||
additions.addAll(result);
|
||||
}
|
||||
}
|
||||
mRanges.addAll(additions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given ranges from ranges in this set.
|
||||
*/
|
||||
public void remove(IPRangeSet ranges)
|
||||
{
|
||||
if (ranges == this)
|
||||
{
|
||||
mRanges.clear();
|
||||
return;
|
||||
}
|
||||
for (IPRange range : ranges.mRanges)
|
||||
{
|
||||
remove(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the subnets derived from all the ranges in this set.
|
||||
*/
|
||||
public Iterable<IPRange> subnets()
|
||||
{
|
||||
return new Iterable<IPRange>()
|
||||
{
|
||||
@Override
|
||||
public Iterator<IPRange> iterator()
|
||||
{
|
||||
return new Iterator<IPRange>()
|
||||
{
|
||||
private Iterator<IPRange> mIterator = mRanges.iterator();
|
||||
private List<IPRange> mSubnets;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return (mSubnets != null && mSubnets.size() > 0) || mIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPRange next()
|
||||
{
|
||||
if (mSubnets == null || mSubnets.size() == 0)
|
||||
{
|
||||
IPRange range = mIterator.next();
|
||||
mSubnets = range.toSubnets();
|
||||
}
|
||||
return mSubnets.remove(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<IPRange> iterator()
|
||||
{
|
||||
return mRanges.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of ranges, not subnets.
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return mRanges.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{ /* we could use TextUtils, but that causes the unit tests to fail */
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (IPRange range : mRanges)
|
||||
{
|
||||
if (sb.length() > 0)
|
||||
{
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(range.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,15 @@ public final class Interface {
|
||||
private final KeyPair keyPair;
|
||||
private final Optional<Integer> listenPort;
|
||||
private final Optional<Integer> mtu;
|
||||
private final Optional<Integer> jc;
|
||||
private final Optional<Integer> jmin;
|
||||
private final Optional<Integer> jmax;
|
||||
private final Optional<Integer> s1;
|
||||
private final Optional<Integer> s2;
|
||||
private final Optional<Long> h1;
|
||||
private final Optional<Long> h2;
|
||||
private final Optional<Long> h3;
|
||||
private final Optional<Long> h4;
|
||||
|
||||
private Interface(final Builder builder) {
|
||||
// Defensively copy to ensure immutability even if the Builder is reused.
|
||||
@@ -56,6 +65,15 @@ public final class Interface {
|
||||
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
|
||||
listenPort = builder.listenPort;
|
||||
mtu = builder.mtu;
|
||||
jc = builder.jc;
|
||||
jmax = builder.jmax;
|
||||
jmin = builder.jmin;
|
||||
s1 = builder.s1;
|
||||
s2 = builder.s2;
|
||||
h1 = builder.h1;
|
||||
h2 = builder.h2;
|
||||
h3 = builder.h3;
|
||||
h4 = builder.h4;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +113,33 @@ public final class Interface {
|
||||
case "privatekey":
|
||||
builder.parsePrivateKey(attribute.getValue());
|
||||
break;
|
||||
case "jc":
|
||||
builder.parseJc(attribute.getValue());
|
||||
break;
|
||||
case "jmin":
|
||||
builder.parseJmin(attribute.getValue());
|
||||
break;
|
||||
case "jmax":
|
||||
builder.parseJmax(attribute.getValue());
|
||||
break;
|
||||
case "s1":
|
||||
builder.parseS1(attribute.getValue());
|
||||
break;
|
||||
case "s2":
|
||||
builder.parseS2(attribute.getValue());
|
||||
break;
|
||||
case "h1":
|
||||
builder.parseH1(attribute.getValue());
|
||||
break;
|
||||
case "h2":
|
||||
builder.parseH2(attribute.getValue());
|
||||
break;
|
||||
case "h3":
|
||||
builder.parseH3(attribute.getValue());
|
||||
break;
|
||||
case "h4":
|
||||
builder.parseH4(attribute.getValue());
|
||||
break;
|
||||
default:
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
|
||||
@@ -111,7 +156,9 @@ public final class Interface {
|
||||
return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers)
|
||||
&& excludedApplications.equals(other.excludedApplications)
|
||||
&& includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair)
|
||||
&& listenPort.equals(other.listenPort) && mtu.equals(other.mtu);
|
||||
&& listenPort.equals(other.listenPort) && mtu.equals(other.mtu) && jc.equals(other.jc) && jmin.equals(other.jmin)
|
||||
&& jmax.equals(other.jmax) && s1.equals(other.s1) && s2.equals(other.s2) && h1.equals(other.h1) && h2.equals(other.h2)
|
||||
&& h3.equals(other.h3) && h4.equals(other.h4);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,6 +227,42 @@ public final class Interface {
|
||||
public Optional<Integer> getMtu() {
|
||||
return mtu;
|
||||
}
|
||||
|
||||
public Optional<Integer> getJc() {
|
||||
return jc;
|
||||
}
|
||||
|
||||
public Optional<Integer> getJmin() {
|
||||
return jmin;
|
||||
}
|
||||
|
||||
public Optional<Integer> getJmax() {
|
||||
return jmax;
|
||||
}
|
||||
|
||||
public Optional<Integer> getS1() {
|
||||
return s1;
|
||||
}
|
||||
|
||||
public Optional<Integer> getS2() {
|
||||
return s2;
|
||||
}
|
||||
|
||||
public Optional<Long> getH1() {
|
||||
return h1;
|
||||
}
|
||||
|
||||
public Optional<Long> getH2() {
|
||||
return h2;
|
||||
}
|
||||
|
||||
public Optional<Long> getH3() {
|
||||
return h3;
|
||||
}
|
||||
|
||||
public Optional<Long> getH4() {
|
||||
return h4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
@@ -191,6 +274,15 @@ public final class Interface {
|
||||
hash = 31 * hash + keyPair.hashCode();
|
||||
hash = 31 * hash + listenPort.hashCode();
|
||||
hash = 31 * hash + mtu.hashCode();
|
||||
hash = 31 * hash + jc.hashCode();
|
||||
hash = 31 * hash + jmin.hashCode();
|
||||
hash = 31 * hash + jmax.hashCode();
|
||||
hash = 31 * hash + s1.hashCode();
|
||||
hash = 31 * hash + s2.hashCode();
|
||||
hash = 31 * hash + h1.hashCode();
|
||||
hash = 31 * hash + h2.hashCode();
|
||||
hash = 31 * hash + h3.hashCode();
|
||||
hash = 31 * hash + h4.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -234,6 +326,19 @@ public final class Interface {
|
||||
.append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
|
||||
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
|
||||
|
||||
jc.ifPresent(t_jc -> sb.append("Jc = ").append(t_jc).append('\n'));
|
||||
jmin.ifPresent(t_jmin -> sb.append("Jmin = ").append(t_jmin).append('\n'));
|
||||
jmax.ifPresent(t_jmax -> sb.append("Jmax = ").append(t_jmax).append('\n'));
|
||||
|
||||
s1.ifPresent(t_s1 -> sb.append("S1 = ").append(t_s1).append('\n'));
|
||||
s2.ifPresent(t_s2 -> sb.append("S2 = ").append(t_s2).append('\n'));
|
||||
|
||||
h1.ifPresent(t_h1 -> sb.append("H1 = ").append(t_h1).append('\n'));
|
||||
h2.ifPresent(t_h2 -> sb.append("H2 = ").append(t_h2).append('\n'));
|
||||
h3.ifPresent(t_h3 -> sb.append("H3 = ").append(t_h3).append('\n'));
|
||||
h4.ifPresent(t_h4 -> sb.append("H4 = ").append(t_h4).append('\n'));
|
||||
|
||||
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -248,6 +353,18 @@ public final class Interface {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n');
|
||||
listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n'));
|
||||
|
||||
jc.ifPresent(t_jc -> sb.append("jc=").append(t_jc).append('\n'));
|
||||
jmin.ifPresent(t_jmin -> sb.append("jmin=").append(t_jmin).append('\n'));
|
||||
jmax.ifPresent(t_jmax -> sb.append("jmax=").append(t_jmax).append('\n'));
|
||||
|
||||
s1.ifPresent(t_s1 -> sb.append("s1=").append(t_s1).append('\n'));
|
||||
s2.ifPresent(t_s2 -> sb.append("s2=").append(t_s2).append('\n'));
|
||||
|
||||
h1.ifPresent(t_h1 -> sb.append("h1=").append(t_h1).append('\n'));
|
||||
h2.ifPresent(t_h2 -> sb.append("h2=").append(t_h2).append('\n'));
|
||||
h3.ifPresent(t_h3 -> sb.append("h3=").append(t_h3).append('\n'));
|
||||
h4.ifPresent(t_h4 -> sb.append("h4=").append(t_h4).append('\n'));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -267,6 +384,17 @@ public final class Interface {
|
||||
private Optional<Integer> listenPort = Optional.empty();
|
||||
// Defaults to not present.
|
||||
private Optional<Integer> mtu = Optional.empty();
|
||||
private Optional<Integer> jc = Optional.empty();
|
||||
private Optional<Integer> jmin = Optional.empty();
|
||||
private Optional<Integer> jmax = Optional.empty();
|
||||
|
||||
private Optional<Integer> s1 = Optional.empty();
|
||||
private Optional<Integer> s2 = Optional.empty();
|
||||
|
||||
private Optional<Long> h1 = Optional.empty();
|
||||
private Optional<Long> h2 = Optional.empty();
|
||||
private Optional<Long> h3 = Optional.empty();
|
||||
private Optional<Long> h4 = Optional.empty();
|
||||
|
||||
public Builder addAddress(final InetNetwork address) {
|
||||
addresses.add(address);
|
||||
@@ -362,6 +490,78 @@ public final class Interface {
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJc(final String jc) throws BadConfigException {
|
||||
try {
|
||||
return setJc(Integer.parseInt(jc));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JC, jc, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJmax(final String jmax) throws BadConfigException {
|
||||
try {
|
||||
return setJmax(Integer.parseInt(jmax));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JMAX, jmax, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseJmin(final String jmin) throws BadConfigException {
|
||||
try {
|
||||
return setJmin(Integer.parseInt(jmin));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.JMIN, jmin, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseS1(final String s1) throws BadConfigException {
|
||||
try {
|
||||
return setS1(Integer.parseInt(s1));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.S1, s1, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseS2(final String s2) throws BadConfigException {
|
||||
try {
|
||||
return setS2(Integer.parseInt(s2));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.S2, s2, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseH1(final String h1) throws BadConfigException {
|
||||
try {
|
||||
return setH1(Long.parseLong(h1));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.H1, h1, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseH2(final String h2) throws BadConfigException {
|
||||
try {
|
||||
return setH2(Long.parseLong(h2));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.H2, h2, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseH3(final String h3) throws BadConfigException {
|
||||
try {
|
||||
return setH3(Long.parseLong(h3));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.H3, h3, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parseH4(final String h4) throws BadConfigException {
|
||||
try {
|
||||
return setH4(Long.parseLong(h4));
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new BadConfigException(Section.INTERFACE, Location.H4, h4, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
|
||||
try {
|
||||
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
|
||||
@@ -386,9 +586,81 @@ public final class Interface {
|
||||
public Builder setMtu(final int mtu) throws BadConfigException {
|
||||
if (mtu < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE, String.valueOf(mtu));
|
||||
Section.INTERFACE, Location.MTU, Reason.INVALID_VALUE, String.valueOf(mtu));
|
||||
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJc(final int jc) throws BadConfigException {
|
||||
if (jc < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.JC, Reason.INVALID_VALUE, String.valueOf(jc));
|
||||
this.jc = Optional.of(jc);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJmin(final int jmin) throws BadConfigException {
|
||||
if (jmin < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.JMIN, Reason.INVALID_VALUE, String.valueOf(jmin));
|
||||
this.jmin = Optional.of(jmin);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setJmax(final int jmax) throws BadConfigException {
|
||||
if (jmax < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.JMAX, Reason.INVALID_VALUE, String.valueOf(jmax));
|
||||
this.jmax = Optional.of(jmax);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setS1(final int s1) throws BadConfigException {
|
||||
if (s1 < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.S1, Reason.INVALID_VALUE, String.valueOf(s1));
|
||||
this.s1 = Optional.of(s1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setS2(final int s2) throws BadConfigException {
|
||||
if (s2 < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.S2, Reason.INVALID_VALUE, String.valueOf(s2));
|
||||
this.s2 = Optional.of(s2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setH1(final long h1) throws BadConfigException {
|
||||
if (h1 < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.H1, Reason.INVALID_VALUE, String.valueOf(h1));
|
||||
this.h1 = Optional.of(h1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setH2(final long h2) throws BadConfigException {
|
||||
if (h2 < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.H2, Reason.INVALID_VALUE, String.valueOf(h2));
|
||||
this.h2 = Optional.of(h2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setH3(final long h3) throws BadConfigException {
|
||||
if (h3 < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.H3, Reason.INVALID_VALUE, String.valueOf(h3));
|
||||
this.h3 = Optional.of(h3);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setH4(final long h4) throws BadConfigException {
|
||||
if (h4 < 0)
|
||||
throw new BadConfigException(
|
||||
Section.INTERFACE, Location.H4, Reason.INVALID_VALUE, String.valueOf(h4));
|
||||
this.h4 = Optional.of(h4);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
client/android/src/com/wireguard/config/Utils.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2019 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
package com.wireguard.config;
|
||||
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class Utils
|
||||
{
|
||||
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
|
||||
|
||||
/**
|
||||
* Converts the given byte array to a hexadecimal string encoding.
|
||||
*
|
||||
* @param bytes byte array to convert
|
||||
* @return hex string
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
char[] hex = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
int value = bytes[i];
|
||||
hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4];
|
||||
hex[i*2+1] = HEXDIGITS[ value & 0x0f];
|
||||
}
|
||||
return new String(hex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given proposal string
|
||||
*
|
||||
* @param ike true for IKE, false for ESP
|
||||
* @param proposal proposal string
|
||||
* @return true if valid
|
||||
*/
|
||||
public native static boolean isProposalValid(boolean ike, String proposal);
|
||||
|
||||
/**
|
||||
* Parse an IP address without doing a name lookup
|
||||
*
|
||||
* @param address IP address string
|
||||
* @return address bytes if valid
|
||||
*/
|
||||
private native static byte[] parseInetAddressBytes(String address);
|
||||
|
||||
/**
|
||||
* Parse an IP address without doing a name lookup (as compared to InetAddress.fromName())
|
||||
*
|
||||
* @param address IP address string
|
||||
* @return address if valid
|
||||
* @throws UnknownHostException if address is invalid
|
||||
*/
|
||||
public static InetAddress parseInetAddress(String address) throws UnknownHostException
|
||||
{
|
||||
byte[] bytes = parseInetAddressBytes(address);
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new UnknownHostException();
|
||||
}
|
||||
return InetAddress.getByAddress(bytes);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import com.wireguard.crypto.Key
|
||||
import org.json.JSONObject
|
||||
import java.util.Base64
|
||||
|
||||
import com.wireguard.config.*
|
||||
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import net.openvpn.ovpn3.ClientAPI_EvalConfig
|
||||
import net.openvpn.ovpn3.ClientAPI_Event
|
||||
@@ -72,6 +74,8 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
|
||||
val jsonVpnConfig = mService.getVpnConfig()
|
||||
val ovpnConfig = jsonVpnConfig.getJSONObject("openvpn_config_data").getString("config")
|
||||
val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType")
|
||||
val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites")
|
||||
|
||||
val resultingConfig = StringBuilder()
|
||||
resultingConfig.append(ovpnConfig)
|
||||
@@ -115,6 +119,7 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
eval_config(config)
|
||||
|
||||
val status = connect()
|
||||
|
||||
if (status.getError()) {
|
||||
Log.i(tag, "connect() error: " + status.getError() + ": " + status.getMessage())
|
||||
}
|
||||
@@ -139,6 +144,31 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
|
||||
|
||||
override fun tun_builder_establish(): Int {
|
||||
Log.v(tag, "tun_builder_establish")
|
||||
val jsonVpnConfig = mService.getVpnConfig()
|
||||
|
||||
val splitTunnelType = jsonVpnConfig.getInt("splitTunnelType")
|
||||
val splitTunnelSites = jsonVpnConfig.getJSONArray("splitTunnelSites")
|
||||
if (splitTunnelType == 1) {
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
val ipRange = IPRange(site)
|
||||
mService.addRoute(ipRange.getFrom().getHostAddress(), ipRange.getPrefix())
|
||||
}
|
||||
}
|
||||
if (splitTunnelType == 2) {
|
||||
val ipRangeSet = IPRangeSet.fromString("0.0.0.0/0")
|
||||
ipRangeSet.remove(IPRange("127.0.0.0/8"))
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
ipRangeSet.remove(IPRange(site))
|
||||
}
|
||||
ipRangeSet.subnets().forEach {
|
||||
mService.addRoute(it.getFrom().getHostAddress(), it.getPrefix())
|
||||
Thread.sleep(10)
|
||||
}
|
||||
mService.addRoute("2000::", 3)
|
||||
}
|
||||
|
||||
return mService.establish()!!.detachFd()
|
||||
}
|
||||
|
||||
|
||||
@@ -380,7 +380,10 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
mNetworkState.bindNetworkListener()
|
||||
}
|
||||
"wireguard" -> {
|
||||
startWireGuard()
|
||||
startWireGuard("wireguard")
|
||||
}
|
||||
"awg" -> {
|
||||
startWireGuard("awg")
|
||||
}
|
||||
"shadowsocks" -> {
|
||||
startShadowsocks()
|
||||
@@ -457,7 +460,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
fun turnOff() {
|
||||
Log.v(tag, "Aman: turnOff....................")
|
||||
when (mProtocol) {
|
||||
"wireguard" -> {
|
||||
"wireguard",
|
||||
"awg" -> {
|
||||
GoBackend.wgTurnOff(currentTunnelHandle)
|
||||
}
|
||||
"cloak",
|
||||
@@ -559,37 +563,76 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
}
|
||||
return parseData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a Wireguard [Config] from a [json] string -
|
||||
* The [json] will be created in AndroidVpnProtocol.cpp
|
||||
*/
|
||||
private fun buildWireguardConfig(obj: JSONObject): Config {
|
||||
private fun buildWireguardConfig(obj: JSONObject, type: String): Config {
|
||||
val confBuilder = Config.Builder()
|
||||
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
|
||||
val wireguardConfigData = obj.getJSONObject(type)
|
||||
val splitTunnelType = obj.getInt("splitTunnelType")
|
||||
val splitTunnelSites = obj.getJSONArray("splitTunnelSites")
|
||||
|
||||
val config = parseConfigData(wireguardConfigData.getString("config"))
|
||||
val peerBuilder = Peer.Builder()
|
||||
val peerConfig = config["Peer"]!!
|
||||
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
|
||||
peerConfig["PresharedKey"]?.let {
|
||||
peerBuilder.setPreSharedKey(Key.fromBase64(it))
|
||||
peerConfig["PresharedKey"]?.let { peerBuilder.setPreSharedKey(Key.fromBase64(it)) }
|
||||
|
||||
val allIpString = peerConfig["AllowedIPs"]
|
||||
|
||||
var allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
|
||||
|
||||
/* default value in template */
|
||||
if (allIpString == "0.0.0.0/0, ::/0") {
|
||||
allowedIPList = emptyList()
|
||||
}
|
||||
val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
|
||||
if (allowedIPList.isEmpty()) {
|
||||
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internet)
|
||||
|
||||
if (allowedIPList.isEmpty() && (splitTunnelType == 0)) {
|
||||
/* AllowedIP is empty and splitTunnel is turnoff */
|
||||
/* use VPN for whole Internet */
|
||||
val internetV4 = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internetV4)
|
||||
val internetV6 = InetNetwork.parse("::/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internetV6)
|
||||
} else {
|
||||
allowedIPList.forEach {
|
||||
val network = InetNetwork.parse(it.trim())
|
||||
peerBuilder.addAllowedIp(network)
|
||||
if (!allowedIPList.isEmpty()) {
|
||||
/* We have predefined AllowedIP in WG config */
|
||||
/* It's have higher priority than system SplitTunnel */
|
||||
allowedIPList.forEach {
|
||||
val network = InetNetwork.parse(it.trim())
|
||||
peerBuilder.addAllowedIp(network)
|
||||
}
|
||||
} else {
|
||||
if (splitTunnelType == 1) {
|
||||
/* Use system SplitTunnel */
|
||||
/* VPN connection used only for defined IPs */
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
val internet = InetNetwork.parse(site)
|
||||
peerBuilder.addAllowedIp(internet)
|
||||
}
|
||||
}
|
||||
if (splitTunnelType == 2) {
|
||||
/* Use system SplitTunnel */
|
||||
/* VPN connection used for all Internet exclude defined IPs */
|
||||
val ipRangeSet = IPRangeSet.fromString("0.0.0.0/0")
|
||||
ipRangeSet.remove(IPRange("127.0.0.0/8"))
|
||||
for (i in 0 until splitTunnelSites.length()) {
|
||||
val site = splitTunnelSites.getString(i)
|
||||
ipRangeSet.remove(IPRange(site))
|
||||
}
|
||||
val allowedIps = ipRangeSet.subnets().joinToString(", ") + ", 2000::/3"
|
||||
peerBuilder.parseAllowedIPs(allowedIps)
|
||||
}
|
||||
}
|
||||
}
|
||||
val endpointConfig = peerConfig["Endpoint"]
|
||||
val endpoint = InetEndpoint.parse(endpointConfig)
|
||||
peerBuilder.setEndpoint(endpoint)
|
||||
peerConfig["PersistentKeepalive"]?.let {
|
||||
peerBuilder.setPersistentKeepalive(it.toInt())
|
||||
}
|
||||
peerConfig["PersistentKeepalive"]?.let { peerBuilder.setPersistentKeepalive(it.toInt()) }
|
||||
confBuilder.addPeer(peerBuilder.build())
|
||||
|
||||
val ifaceBuilder = Interface.Builder()
|
||||
@@ -599,11 +642,34 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
ifaceConfig["DNS"]!!.split(",").forEach {
|
||||
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
|
||||
}
|
||||
|
||||
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
|
||||
if (type == "awg_config_data") {
|
||||
ifaceBuilder.parseJc(ifaceConfig["Jc"])
|
||||
ifaceBuilder.parseJmin(ifaceConfig["Jmin"])
|
||||
ifaceBuilder.parseJmax(ifaceConfig["Jmax"])
|
||||
ifaceBuilder.parseS1(ifaceConfig["S1"])
|
||||
ifaceBuilder.parseS2(ifaceConfig["S2"])
|
||||
ifaceBuilder.parseH1(ifaceConfig["H1"])
|
||||
ifaceBuilder.parseH2(ifaceConfig["H2"])
|
||||
ifaceBuilder.parseH3(ifaceConfig["H3"])
|
||||
ifaceBuilder.parseH4(ifaceConfig["H4"])
|
||||
} else {
|
||||
ifaceBuilder.parseJc("0")
|
||||
ifaceBuilder.parseJmin("0")
|
||||
ifaceBuilder.parseJmax("0")
|
||||
ifaceBuilder.parseS1("0")
|
||||
ifaceBuilder.parseS2("0")
|
||||
ifaceBuilder.parseH1("0")
|
||||
ifaceBuilder.parseH2("0")
|
||||
ifaceBuilder.parseH3("0")
|
||||
ifaceBuilder.parseH4("0")
|
||||
}
|
||||
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
|
||||
(0 until jExcludedApplication.length()).toList().forEach {
|
||||
(0 until jExcludedApplication.length()).toList().forEach {
|
||||
val appName = jExcludedApplication.get(it).toString()
|
||||
ifaceBuilder.excludeApplication(appName)
|
||||
}*/
|
||||
}*/
|
||||
confBuilder.setInterface(ifaceBuilder.build())
|
||||
|
||||
return confBuilder.build()
|
||||
@@ -716,21 +782,27 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
||||
}).start()
|
||||
}
|
||||
|
||||
private fun startWireGuard() {
|
||||
val wireguard_conf = buildWireguardConfig(mConfig!!)
|
||||
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
|
||||
private fun startWireGuard(type: String) {
|
||||
val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data")
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.e(tag, "Tunnel already up")
|
||||
// Turn the tunnel down because this might be a switch
|
||||
GoBackend.wgTurnOff(currentTunnelHandle)
|
||||
}
|
||||
val wgConfig: String = wireguard_conf.toWgUserspaceString()
|
||||
|
||||
val builder = Builder()
|
||||
setupBuilder(wireguard_conf, builder)
|
||||
builder.setSession("Amnezia")
|
||||
|
||||
|
||||
builder.establish().use { tun ->
|
||||
if (tun == null) return
|
||||
currentTunnelHandle = GoBackend.wgTurnOn("Amnezia", tun.detachFd(), wgConfig)
|
||||
if (tun == null) return
|
||||
if (type == "awg"){
|
||||
currentTunnelHandle = GoBackend.wgTurnOn("awg0", tun.detachFd(), wgConfig)
|
||||
} else {
|
||||
currentTunnelHandle = GoBackend.wgTurnOn("amn0", tun.detachFd(), wgConfig)
|
||||
}
|
||||
}
|
||||
if (currentTunnelHandle < 0) {
|
||||
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
|
||||
|
||||
@@ -10,6 +10,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||
)
|
||||
|
||||
@@ -18,6 +19,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE
|
||||
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||
)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources)
|
||||
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift
|
||||
|
||||
47
client/configurators/awg_configurator.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "awg_configurator.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "core/servercontroller.h"
|
||||
|
||||
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: WireguardConfigurator(settings, true, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
||||
|
||||
QMap<QString, QString> configMap;
|
||||
auto configLines = awgConfig.split("\n");
|
||||
for (auto &line : configLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount);
|
||||
jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize);
|
||||
jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize);
|
||||
jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize);
|
||||
jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize);
|
||||
jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader);
|
||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||
|
||||
return QJsonDocument(jsonConfig).toJson();
|
||||
}
|
||||
18
client/configurators/awg_configurator.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef AWGCONFIGURATOR_H
|
||||
#define AWGCONFIGURATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "wireguard_configurator.h"
|
||||
|
||||
class AwgConfigurator : public WireguardConfigurator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
};
|
||||
|
||||
#endif // AWGCONFIGURATOR_H
|
||||
@@ -1,28 +1,26 @@
|
||||
#include "ikev2_configurator.h"
|
||||
#include <QApplication>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QDebug>
|
||||
#include <QTemporaryFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "utilities.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "core/servercontroller.h"
|
||||
#include "utilities.h"
|
||||
|
||||
|
||||
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent):
|
||||
ConfiguratorBase(settings, parent)
|
||||
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: ConfiguratorBase(settings, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
|
||||
DockerContainer container, ErrorCode *errorCode)
|
||||
DockerContainer container, ErrorCode *errorCode)
|
||||
{
|
||||
Ikev2Configurator::ConnectionData connData;
|
||||
connData.host = credentials.hostName;
|
||||
@@ -32,26 +30,27 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
||||
|
||||
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
|
||||
|
||||
QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\
|
||||
"-S -c \"IKEv2 VPN CA\" -n \"%1\" "\
|
||||
"-s \"O=IKEv2 VPN,CN=%1\" "\
|
||||
"-k rsa -g 3072 -v 120 "\
|
||||
"-d sql:/etc/ipsec.d -t \",,\" "\
|
||||
"--keyUsage digitalSignature,keyEncipherment "\
|
||||
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
||||
.arg(connData.clientId);
|
||||
QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "
|
||||
"-S -c \"IKEv2 VPN CA\" -n \"%1\" "
|
||||
"-s \"O=IKEv2 VPN,CN=%1\" "
|
||||
"-k rsa -g 3072 -v 120 "
|
||||
"-d sql:/etc/ipsec.d -t \",,\" "
|
||||
"--keyUsage digitalSignature,keyEncipherment "
|
||||
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
||||
.arg(connData.clientId);
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
|
||||
|
||||
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
|
||||
.arg(connData.password)
|
||||
.arg(connData.clientId)
|
||||
.arg(certFileName);
|
||||
.arg(connData.password)
|
||||
.arg(connData.clientId)
|
||||
.arg(certFileName);
|
||||
e = serverController.runContainerScript(credentials, container, scriptExportCert);
|
||||
|
||||
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e);
|
||||
connData.caCert = serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e);
|
||||
connData.caCert =
|
||||
serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e);
|
||||
|
||||
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
|
||||
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
|
||||
@@ -59,8 +58,8 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
Q_UNUSED(containerConfig)
|
||||
|
||||
@@ -120,4 +119,3 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData)
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,82 +1,94 @@
|
||||
#include "openvpn_configurator.h"
|
||||
#include <QApplication>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QDebug>
|
||||
#include <QTemporaryFile>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "core/servercontroller.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "utilities.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
||||
ConfiguratorBase(settings, parent)
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: ConfiguratorBase(settings, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, ErrorCode *errorCode)
|
||||
DockerContainer container,
|
||||
ErrorCode *errorCode)
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||
connData.host = credentials.hostName;
|
||||
|
||||
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
|
||||
if (errorCode) *errorCode = ErrorCode::OpenSslFailed;
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::OpenSslFailed;
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString reqFileName = QString("%1/%2.req").
|
||||
arg(amnezia::protocols::openvpn::clientsDirPath).
|
||||
arg(connData.clientId);
|
||||
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
e = signCert(container, credentials, connData.clientId);
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.caCert = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e);
|
||||
connData.clientCert = serverController.getTextFileFromContainer(container, credentials,
|
||||
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
|
||||
connData.caCert = serverController.getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::openvpn::caCertPath, &e);
|
||||
connData.clientCert = serverController.getTextFileFromContainer(
|
||||
container, credentials,
|
||||
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.taKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e);
|
||||
connData.taKey = serverController.getTextFileFromContainer(container, credentials,
|
||||
amnezia::protocols::openvpn::taKeyPath, &e);
|
||||
|
||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||
if (errorCode) *errorCode = ErrorCode::SshSftpFailureError;
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::SshSftpFailureError;
|
||||
}
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
ServerController serverController(m_settings);
|
||||
QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
||||
QString config =
|
||||
serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
||||
if (errorCode && *errorCode) {
|
||||
@@ -89,8 +101,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
|
||||
|
||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
config.replace("<tls-auth>", "");
|
||||
config.replace("</tls-auth>", "");
|
||||
}
|
||||
@@ -120,10 +131,13 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
|
||||
config.append("block-ipv6\n");
|
||||
}
|
||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||
|
||||
// no redirect-gateway
|
||||
}
|
||||
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#ifndef Q_OS_ANDROID
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
#endif
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
config.append("block-ipv6\n");
|
||||
@@ -133,12 +147,11 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
|
||||
config.replace("block-outside-dns", "");
|
||||
#endif
|
||||
|
||||
#if (defined (MZ_MACOS) || defined(MZ_LINUX))
|
||||
QString dnsConf = QString(
|
||||
"\nscript-security 2\n"
|
||||
"up %1/update-resolv-conf.sh\n"
|
||||
"down %1/update-resolv-conf.sh\n").
|
||||
arg(qApp->applicationDirPath());
|
||||
#if (defined(MZ_MACOS) || defined(MZ_LINUX))
|
||||
QString dnsConf = QString("\nscript-security 2\n"
|
||||
"up %1/update-resolv-conf.sh\n"
|
||||
"down %1/update-resolv-conf.sh\n")
|
||||
.arg(qApp->applicationDirPath());
|
||||
|
||||
config.append(dnsConf);
|
||||
#endif
|
||||
@@ -168,23 +181,23 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig)
|
||||
return QJsonDocument(json).toJson();
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container,
|
||||
const ServerCredentials &credentials, QString clientId)
|
||||
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId)
|
||||
{
|
||||
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
|
||||
"easyrsa import-req %2/%3.req %3\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(amnezia::protocols::openvpn::clientsDirPath)
|
||||
.arg(clientId);
|
||||
"easyrsa import-req %2/%3.req %3\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(amnezia::protocols::openvpn::clientsDirPath)
|
||||
.arg(clientId);
|
||||
|
||||
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
|
||||
"easyrsa sign-req client %2\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(clientId);
|
||||
"easyrsa sign-req client %2\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(clientId);
|
||||
|
||||
ServerController serverController(m_settings);
|
||||
QStringList scriptList {script_import, script_sign};
|
||||
QString script = serverController.replaceVars(scriptList.join("\n"), serverController.genVarsForScript(credentials, container));
|
||||
QStringList scriptList { script_import, script_sign };
|
||||
QString script = serverController.replaceVars(scriptList.join("\n"),
|
||||
serverController.genVarsForScript(credentials, container));
|
||||
|
||||
return serverController.runScript(credentials, script);
|
||||
}
|
||||
@@ -194,18 +207,17 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
ConnectionData connData;
|
||||
connData.clientId = Utils::getRandomString(32);
|
||||
|
||||
int ret = 0;
|
||||
int nVersion = 1;
|
||||
int ret = 0;
|
||||
int nVersion = 1;
|
||||
|
||||
QByteArray clientIdUtf8 = connData.clientId.toUtf8();
|
||||
|
||||
EVP_PKEY * pKey = EVP_PKEY_new();
|
||||
EVP_PKEY *pKey = EVP_PKEY_new();
|
||||
q_check_ptr(pKey);
|
||||
RSA * rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr);
|
||||
RSA *rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr);
|
||||
q_check_ptr(rsa);
|
||||
EVP_PKEY_assign_RSA(pKey, rsa);
|
||||
|
||||
|
||||
// 2. set version of x509 req
|
||||
X509_REQ *x509_req = X509_REQ_new();
|
||||
ret = X509_REQ_set_version(x509_req, nVersion);
|
||||
@@ -219,16 +231,14 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
// 3. set subject of x509 req
|
||||
X509_NAME *x509_name = X509_REQ_get_subject_name(x509_req);
|
||||
|
||||
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC,
|
||||
(unsigned char *)"ORG", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC,
|
||||
(unsigned char *)"", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC,
|
||||
reinterpret_cast<unsigned char const *>(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0);
|
||||
|
||||
// 4. set public key of x509 req
|
||||
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
||||
if (ret != 1){
|
||||
if (ret != 1) {
|
||||
qWarning() << "Could not set pubkey!";
|
||||
X509_REQ_free(x509_req);
|
||||
EVP_PKEY_free(pKey);
|
||||
@@ -236,8 +246,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
}
|
||||
|
||||
// 5. set sign key of x509 req
|
||||
ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length
|
||||
if (ret <= 0){
|
||||
ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length
|
||||
if (ret <= 0) {
|
||||
qWarning() << "Could not sign request!";
|
||||
X509_REQ_free(x509_req);
|
||||
EVP_PKEY_free(pKey);
|
||||
@@ -245,10 +255,9 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
}
|
||||
|
||||
// save private key
|
||||
BIO * bp_private = BIO_new(BIO_s_mem());
|
||||
BIO *bp_private = BIO_new(BIO_s_mem());
|
||||
q_check_ptr(bp_private);
|
||||
if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1)
|
||||
{
|
||||
if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) {
|
||||
qFatal("PEM_write_bio_PrivateKey");
|
||||
EVP_PKEY_free(pKey);
|
||||
BIO_free_all(bp_private);
|
||||
@@ -256,7 +265,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
return connData;
|
||||
}
|
||||
|
||||
const char * buffer = nullptr;
|
||||
const char *buffer = nullptr;
|
||||
size_t size = BIO_get_mem_data(bp_private, &buffer);
|
||||
q_check_ptr(buffer);
|
||||
connData.privKey = QByteArray(buffer, size);
|
||||
@@ -270,7 +279,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
BIO_free_all(bp_private);
|
||||
|
||||
// save req
|
||||
BIO * bio_req = BIO_new(BIO_s_mem());
|
||||
BIO *bio_req = BIO_new(BIO_s_mem());
|
||||
PEM_write_bio_X509_REQ(bio_req, x509_req);
|
||||
|
||||
BUF_MEM *bio_buf;
|
||||
@@ -278,7 +287,6 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||
connData.request = QByteArray(bio_buf->data, bio_buf->length);
|
||||
BIO_free(bio_req);
|
||||
|
||||
|
||||
EVP_PKEY_free(pKey); // this will also free the rsa key
|
||||
|
||||
return connData;
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
#include "ssh_configurator.h"
|
||||
#include <QApplication>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QDebug>
|
||||
#include <QTemporaryFile>
|
||||
#include <QThread>
|
||||
#include <QObject>
|
||||
#include <QTextEdit>
|
||||
#include <QPlainTextEdit>
|
||||
#include <qtimer.h>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "core/server_defs.h"
|
||||
#include "utilities.h"
|
||||
|
||||
|
||||
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
||||
ConfiguratorBase(settings, parent)
|
||||
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: ConfiguratorBase(settings, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
@@ -28,23 +29,30 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
QTemporaryFile tmp;
|
||||
#ifdef QT_DEBUG
|
||||
#ifdef QT_DEBUG
|
||||
tmp.setAutoRemove(false);
|
||||
#endif
|
||||
#endif
|
||||
tmp.open();
|
||||
tmp.write(key.toUtf8());
|
||||
tmp.close();
|
||||
|
||||
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN
|
||||
p.setProcessEnvironment(prepareEnv());
|
||||
p.setProgram("cmd.exe");
|
||||
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
|
||||
#else
|
||||
#else
|
||||
p.setProgram("ssh-keygen");
|
||||
p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName());
|
||||
#endif
|
||||
p.setArguments(QStringList() << "-p"
|
||||
<< "-P"
|
||||
<< ""
|
||||
<< "-N"
|
||||
<< ""
|
||||
<< "-m"
|
||||
<< "pem"
|
||||
<< "-f" << tmp.fileName());
|
||||
#endif
|
||||
|
||||
p.start();
|
||||
p.waitForFinished();
|
||||
@@ -65,22 +73,21 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
||||
QProcess *p = new QProcess();
|
||||
p->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN
|
||||
p->setProcessEnvironment(prepareEnv());
|
||||
p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe");
|
||||
|
||||
if (credentials.password.contains("PRIVATE KEY")) {
|
||||
if (credentials.secretData.contains("PRIVATE KEY")) {
|
||||
// todo: connect by key
|
||||
// p->setNativeArguments(QString("%1@%2")
|
||||
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password));
|
||||
// p->setNativeArguments(QString("%1@%2")
|
||||
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||
} else {
|
||||
p->setNativeArguments(
|
||||
QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||
}
|
||||
else {
|
||||
p->setNativeArguments(QString("%1@%2 -pw %3")
|
||||
.arg(credentials.userName).arg(credentials.hostName).arg(credentials.password));
|
||||
}
|
||||
#else
|
||||
#else
|
||||
p->setProgram("/bin/bash");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
p->startDetached();
|
||||
#endif
|
||||
@@ -95,11 +102,11 @@ QProcessEnvironment SshConfigurator::prepareEnv()
|
||||
pathEnvVar.clear();
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||
#else
|
||||
#elif defined(Q_OS_MACX)
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
|
||||
env.insert("PATH", pathEnvVar);
|
||||
//qDebug().noquote() << "ENV PATH" << pathEnvVar;
|
||||
// qDebug().noquote() << "ENV PATH" << pathEnvVar;
|
||||
return env;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
#include "vpn_configurator.h"
|
||||
#include "openvpn_configurator.h"
|
||||
#include "cloak_configurator.h"
|
||||
#include "shadowsocks_configurator.h"
|
||||
#include "wireguard_configurator.h"
|
||||
#include "ikev2_configurator.h"
|
||||
#include "openvpn_configurator.h"
|
||||
#include "shadowsocks_configurator.h"
|
||||
#include "ssh_configurator.h"
|
||||
#include "wireguard_configurator.h"
|
||||
#include "awg_configurator.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "utilities.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
||||
ConfiguratorBase(settings, parent)
|
||||
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
||||
: ConfiguratorBase(settings, parent)
|
||||
{
|
||||
openVpnConfigurator = std::shared_ptr<OpenVpnConfigurator>(new OpenVpnConfigurator(settings, this));
|
||||
shadowSocksConfigurator = std::shared_ptr<ShadowSocksConfigurator>(new ShadowSocksConfigurator(settings, this));
|
||||
cloakConfigurator = std::shared_ptr<CloakConfigurator>(new CloakConfigurator(settings, this));
|
||||
wireguardConfigurator = std::shared_ptr<WireguardConfigurator>(new WireguardConfigurator(settings, this));
|
||||
wireguardConfigurator = std::shared_ptr<WireguardConfigurator>(new WireguardConfigurator(settings, false, this));
|
||||
ikev2Configurator = std::shared_ptr<Ikev2Configurator>(new Ikev2Configurator(settings, this));
|
||||
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
|
||||
awgConfigurator = std::shared_ptr<AwgConfigurator>(new AwgConfigurator(settings, this));
|
||||
}
|
||||
|
||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
|
||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
|
||||
{
|
||||
switch (proto) {
|
||||
case Proto::OpenVpn:
|
||||
@@ -35,17 +37,17 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
|
||||
case Proto::ShadowSocks:
|
||||
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
case Proto::Cloak:
|
||||
return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
|
||||
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
case Proto::WireGuard:
|
||||
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
case Proto::Ikev2:
|
||||
return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
|
||||
case Proto::Awg:
|
||||
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
default:
|
||||
return "";
|
||||
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
|
||||
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +64,8 @@ QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
|
||||
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
|
||||
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
|
||||
dns.first = protocols::dns::amneziaDnsIp;
|
||||
}
|
||||
else dns.first = m_settings->primaryDns();
|
||||
} else
|
||||
dns.first = m_settings->primaryDns();
|
||||
}
|
||||
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
|
||||
dns.second = m_settings->secondaryDns();
|
||||
@@ -73,8 +75,8 @@ QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
|
||||
return dns;
|
||||
}
|
||||
|
||||
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container,
|
||||
Proto proto, QString &config)
|
||||
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto,
|
||||
QString &config)
|
||||
{
|
||||
auto dns = getDnsForConfig(serverIndex);
|
||||
|
||||
@@ -84,8 +86,8 @@ QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerCo
|
||||
return config;
|
||||
}
|
||||
|
||||
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container,
|
||||
Proto proto, QString &config)
|
||||
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto,
|
||||
QString &config)
|
||||
{
|
||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
||||
|
||||
@@ -95,8 +97,8 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker
|
||||
return config;
|
||||
}
|
||||
|
||||
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container,
|
||||
Proto proto, QString &config)
|
||||
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto,
|
||||
QString &config)
|
||||
{
|
||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
||||
|
||||
@@ -107,7 +109,7 @@ QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, Docke
|
||||
}
|
||||
|
||||
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
|
||||
const QString &stdOut)
|
||||
const QString &stdOut)
|
||||
{
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
|
||||
|
||||
@@ -13,13 +13,14 @@ class CloakConfigurator;
|
||||
class WireguardConfigurator;
|
||||
class Ikev2Configurator;
|
||||
class SshConfigurator;
|
||||
class AwgConfigurator;
|
||||
|
||||
// Retrieve connection settings from server
|
||||
class VpnConfigurator : ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
|
||||
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr);
|
||||
@@ -40,6 +41,7 @@ public:
|
||||
std::shared_ptr<WireguardConfigurator> wireguardConfigurator;
|
||||
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
|
||||
std::shared_ptr<SshConfigurator> sshConfigurator;
|
||||
std::shared_ptr<AwgConfigurator> awgConfigurator;
|
||||
};
|
||||
|
||||
#endif // VPN_CONFIGURATOR_H
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
#include "wireguard_configurator.h"
|
||||
#include <QApplication>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QDebug>
|
||||
#include <QTemporaryFile>
|
||||
#include <QJsonDocument>
|
||||
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "utilities.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "core/servercontroller.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
||||
ConfiguratorBase(settings, parent)
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent)
|
||||
: ConfiguratorBase(settings, parent), m_isAwg(isAwg)
|
||||
{
|
||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath
|
||||
: amnezia::protocols::wireguard::serverConfigPath;
|
||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath
|
||||
: amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath
|
||||
: amnezia::protocols::wireguard::serverPskKeyPath;
|
||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template
|
||||
: ProtocolScriptType::wireguard_template;
|
||||
|
||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
@@ -36,37 +44,40 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
|
||||
unsigned char buff[EDDSA_KEY_LENGTH];
|
||||
int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH);
|
||||
if (ret <=0) return connData;
|
||||
if (ret <= 0)
|
||||
return connData;
|
||||
|
||||
EVP_PKEY * pKey = EVP_PKEY_new();
|
||||
EVP_PKEY *pKey = EVP_PKEY_new();
|
||||
q_check_ptr(pKey);
|
||||
pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH);
|
||||
|
||||
|
||||
size_t keySize = EDDSA_KEY_LENGTH;
|
||||
|
||||
// save private key
|
||||
unsigned char priv[EDDSA_KEY_LENGTH];
|
||||
EVP_PKEY_get_raw_private_key(pKey, priv, &keySize);
|
||||
connData.clientPrivKey = QByteArray::fromRawData((char*)priv, keySize).toBase64();
|
||||
connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64();
|
||||
|
||||
// save public key
|
||||
unsigned char pub[EDDSA_KEY_LENGTH];
|
||||
EVP_PKEY_get_raw_public_key(pKey, pub, &keySize);
|
||||
connData.clientPubKey = QByteArray::fromRawData((char*)pub, keySize).toBase64();
|
||||
connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64();
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig,
|
||||
ErrorCode *errorCode)
|
||||
{
|
||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||
connData.host = credentials.hostName;
|
||||
connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
|
||||
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
|
||||
|
||||
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
||||
if (errorCode) *errorCode = ErrorCode::InternalError;
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::InternalError;
|
||||
return connData;
|
||||
}
|
||||
|
||||
@@ -76,7 +87,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
// Get list of already created clients (only IP addresses)
|
||||
QString nextIpNumber;
|
||||
{
|
||||
QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath);
|
||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
@@ -96,22 +107,24 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
// Calc next IP address
|
||||
if (ips.isEmpty()) {
|
||||
nextIpNumber = "2";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
int next = ips.last().split(".").last().toInt() + 1;
|
||||
if (next > 254) {
|
||||
if (errorCode) *errorCode = ErrorCode::AddressPoolError;
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
nextIpNumber = QString::number(next);
|
||||
}
|
||||
}
|
||||
|
||||
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
QString subnetIp =
|
||||
containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
{
|
||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||
if (l.isEmpty()) {
|
||||
if (errorCode) *errorCode = ErrorCode::AddressPoolError;
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
l.removeLast();
|
||||
@@ -121,52 +134,55 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
}
|
||||
|
||||
// Get keys
|
||||
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e);
|
||||
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e);
|
||||
connData.serverPubKey.replace("\n", "");
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e);
|
||||
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e);
|
||||
connData.pskKey.replace("\n", "");
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
// Add client to config
|
||||
QString configPart = QString(
|
||||
"[Peer]\n"
|
||||
"PublicKey = %1\n"
|
||||
"PresharedKey = %2\n"
|
||||
"AllowedIPs = %3/32\n\n").
|
||||
arg(connData.clientPubKey).
|
||||
arg(connData.pskKey).
|
||||
arg(connData.clientIP);
|
||||
QString configPart = QString("[Peer]\n"
|
||||
"PublicKey = %1\n"
|
||||
"PresharedKey = %2\n"
|
||||
"AllowedIPs = %3/32\n\n")
|
||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||
|
||||
e = serverController.uploadTextFileToContainer(container, credentials, configPart,
|
||||
protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting);
|
||||
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||
libssh::SftpOverwriteMode::SftpAppendToExisting);
|
||||
|
||||
if (e) {
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
return connData;
|
||||
}
|
||||
|
||||
e = serverController.runScript(credentials,
|
||||
serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'",
|
||||
serverController.genVarsForScript(credentials, container)));
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
||||
.arg(m_serverConfigPath);
|
||||
|
||||
e = serverController.runScript(
|
||||
credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
||||
{
|
||||
ServerController serverController(m_settings);
|
||||
QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container),
|
||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config = serverController.replaceVars(
|
||||
scriptData, serverController.genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
if (errorCode && *errorCode) {
|
||||
|
||||
@@ -6,35 +6,44 @@
|
||||
|
||||
#include "configurator_base.h"
|
||||
#include "core/defs.h"
|
||||
#include "core/scripts_registry.h"
|
||||
|
||||
class WireguardConfigurator : ConfiguratorBase
|
||||
class WireguardConfigurator : public ConfiguratorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData {
|
||||
struct ConnectionData
|
||||
{
|
||||
QString clientPrivKey; // client private key
|
||||
QString clientPubKey; // client public key
|
||||
QString clientIP; // internal client IP address
|
||||
QString serverPubKey; // tls-auth key
|
||||
QString pskKey; // preshared key
|
||||
QString host; // host ip
|
||||
QString clientPubKey; // client public key
|
||||
QString clientIP; // internal client IP address
|
||||
QString serverPubKey; // tls-auth key
|
||||
QString pskKey; // preshared key
|
||||
QString host; // host ip
|
||||
QString port;
|
||||
};
|
||||
|
||||
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
|
||||
QString processConfigWithLocalSettings(QString config);
|
||||
QString processConfigWithExportSettings(QString config);
|
||||
|
||||
|
||||
private:
|
||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
||||
|
||||
ConnectionData genClientKeys();
|
||||
|
||||
bool m_isAwg;
|
||||
QString m_serverConfigPath;
|
||||
QString m_serverPublicKeyPath;
|
||||
QString m_serverPskKeyPath;
|
||||
amnezia::ProtocolScriptType m_configTemplate;
|
||||
QString m_protocolName;
|
||||
QString m_defaultPort;
|
||||
};
|
||||
|
||||
#endif // WIREGUARD_CONFIGURATOR_H
|
||||
|
||||
@@ -8,18 +8,23 @@ QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
||||
return debug;
|
||||
}
|
||||
|
||||
amnezia::DockerContainer ContainerProps::containerFromString(const QString &container){
|
||||
amnezia::DockerContainer ContainerProps::containerFromString(const QString &container)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
for (int i = 0; i < metaEnum.keyCount(); ++i) {
|
||||
DockerContainer c = static_cast<DockerContainer>(i);
|
||||
if (container == containerToString(c)) return c;
|
||||
if (container == containerToString(c))
|
||||
return c;
|
||||
}
|
||||
return DockerContainer::None;
|
||||
}
|
||||
|
||||
QString ContainerProps::containerToString(amnezia::DockerContainer c){
|
||||
if (c == DockerContainer::None) return "none";
|
||||
if (c == DockerContainer::Cloak) return "amnezia-openvpn-cloak";
|
||||
QString ContainerProps::containerToString(amnezia::DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||
@@ -27,9 +32,12 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c){
|
||||
return "amnezia-" + containerKey.toLower();
|
||||
}
|
||||
|
||||
QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){
|
||||
if (c == DockerContainer::None) return "none";
|
||||
if (c == DockerContainer::Ipsec) return "ikev2";
|
||||
QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Ipsec)
|
||||
return "ikev2";
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||
@@ -40,29 +48,21 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){
|
||||
QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::None:
|
||||
return { };
|
||||
case DockerContainer::None: return {};
|
||||
|
||||
case DockerContainer::OpenVpn:
|
||||
return { Proto::OpenVpn };
|
||||
case DockerContainer::OpenVpn: return { Proto::OpenVpn };
|
||||
|
||||
case DockerContainer::ShadowSocks:
|
||||
return { Proto::OpenVpn, Proto::ShadowSocks };
|
||||
case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks };
|
||||
|
||||
case DockerContainer::Cloak:
|
||||
return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak };
|
||||
case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak };
|
||||
|
||||
case DockerContainer::Ipsec:
|
||||
return { Proto::Ikev2 /*, Protocol::L2tp */};
|
||||
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
||||
|
||||
case DockerContainer::Dns:
|
||||
return { };
|
||||
case DockerContainer::Dns: return {};
|
||||
|
||||
case DockerContainer::Sftp:
|
||||
return { Proto::Sftp};
|
||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||
|
||||
default:
|
||||
return { defaultProtocol(container) };
|
||||
default: return { defaultProtocol(container) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,70 +79,164 @@ QList<DockerContainer> ContainerProps::allContainers()
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||
{
|
||||
return {
|
||||
{DockerContainer::None, "Not installed"},
|
||||
{DockerContainer::OpenVpn, "OpenVPN"},
|
||||
{DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"},
|
||||
{DockerContainer::Cloak, "OpenVpn over Cloak"},
|
||||
{DockerContainer::WireGuard, "WireGuard"},
|
||||
{DockerContainer::Ipsec, QObject::tr("IPsec")},
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "ShadowSocks" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||
|
||||
{DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")},
|
||||
{DockerContainer::Dns, QObject::tr("DNS Service")},
|
||||
//{DockerContainer::FileShare, QObject::tr("SMB file sharing service")},
|
||||
{DockerContainer::Sftp, QObject::tr("Sftp file sharing service")}
|
||||
};
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } };
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
{
|
||||
return {
|
||||
{DockerContainer::OpenVpn, QObject::tr("OpenVPN container")},
|
||||
{DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks")},
|
||||
{DockerContainer::Cloak, QObject::tr("Container with OpenVpn and ShadowSocks protocols "
|
||||
"configured with traffic masking by Cloak plugin")},
|
||||
{DockerContainer::WireGuard, QObject::tr("WireGuard container")},
|
||||
{DockerContainer::Ipsec, QObject::tr("IPsec container")},
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is "
|
||||
"recognised by analysis systems in some highly censored regions.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
||||
"active-probbing detection. Ideal for bypassing blocking in regions with the highest levels "
|
||||
"of censorship.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption. Recommended for regions with low levels of censorship.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
||||
"but very resistant to blockages. "
|
||||
"Recommended for regions with high levels of censorship.") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||
|
||||
{DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")},
|
||||
{DockerContainer::Dns, QObject::tr("DNS Service")},
|
||||
//{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")},
|
||||
{DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service")}
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") },
|
||||
{ DockerContainer::Dns,
|
||||
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
|
||||
{ DockerContainer::Sftp,
|
||||
QObject::tr("Creates a file vault on your server to securely store and transfer files.") } };
|
||||
}
|
||||
|
||||
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
{
|
||||
return {
|
||||
{ DockerContainer::OpenVpn,
|
||||
QObject::tr(
|
||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
||||
"It employs its unique security protocol, "
|
||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
||||
"catering to a wide range of devices and operating systems. "
|
||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
||||
"which continually reinforces its security. "
|
||||
"With a strong balance of performance, security, and compatibility, "
|
||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Normal power consumption on mobile devices\n"
|
||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||
"* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
|
||||
"* Can operate over both TCP and UDP network protocols.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||
"* Normal power consumption on mobile devices\n\n"
|
||||
"* Configurable encryption protocol\n"
|
||||
"* Detectable by some DPI systems\n"
|
||||
"* Works over TCP network protocol.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||
"blocking protection.\n\n"
|
||||
"OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client "
|
||||
"and the server.\n\n"
|
||||
"Cloak protects OpenVPN from detection and blocking. \n\n"
|
||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||
"being detected\n\n"
|
||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||
"invisible to analysis systems.\n\n"
|
||||
"If there is a extreme level of Internet censorship in your region, we advise you to use only "
|
||||
"OpenVPN over Cloak from the first connection\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* High power consumption on mobile devices\n"
|
||||
"* Flexible settings\n"
|
||||
"* Not recognised by DPI analysis systems\n"
|
||||
"* Works over TCP network protocol, 443 port.\n") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||
"Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption "
|
||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
|
||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
||||
"While WireGuard is known for its efficiency, "
|
||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
||||
"making its traffic blend in with regular internet traffic.\n"
|
||||
"This means that AmneziaWG keeps the fast performance of the original "
|
||||
"while adding an extra layer of stealth, "
|
||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||
"making it particularly adaptive in dynamic network environments. \n"
|
||||
"While it offers a blend of security, stability, and speed, "
|
||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
||||
"* Available in the AmneziaVPN only on Windows\n"
|
||||
"* Low power consumption, on mobile devices\n"
|
||||
"* Minimal configuration\n"
|
||||
"* Recognised by DPI analysis systems\n"
|
||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") }
|
||||
};
|
||||
}
|
||||
|
||||
amnezia::ServiceType ContainerProps::containerService(DockerContainer c)
|
||||
{
|
||||
switch (c) {
|
||||
case DockerContainer::None : return ServiceType::None;
|
||||
case DockerContainer::OpenVpn : return ServiceType::Vpn;
|
||||
case DockerContainer::Cloak : return ServiceType::Vpn;
|
||||
case DockerContainer::ShadowSocks : return ServiceType::Vpn;
|
||||
case DockerContainer::WireGuard : return ServiceType::Vpn;
|
||||
case DockerContainer::Ipsec : return ServiceType::Vpn;
|
||||
case DockerContainer::TorWebSite : return ServiceType::Other;
|
||||
case DockerContainer::Dns : return ServiceType::Other;
|
||||
//case DockerContainer::FileShare : return ServiceType::Other;
|
||||
case DockerContainer::Sftp : return ServiceType::Other;
|
||||
default: return ServiceType::Other;
|
||||
}
|
||||
return ProtocolProps::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
{
|
||||
switch (c) {
|
||||
case DockerContainer::None : return Proto::Any;
|
||||
case DockerContainer::OpenVpn : return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak : return Proto::Cloak;
|
||||
case DockerContainer::ShadowSocks : return Proto::ShadowSocks;
|
||||
case DockerContainer::WireGuard : return Proto::WireGuard;
|
||||
case DockerContainer::Ipsec : return Proto::Ikev2;
|
||||
case DockerContainer::None: return Proto::Any;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak: return Proto::Cloak;
|
||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||
|
||||
case DockerContainer::TorWebSite : return Proto::TorWebSite;
|
||||
case DockerContainer::Dns : return Proto::Dns;
|
||||
//case DockerContainer::FileShare : return Protocol::FileShare;
|
||||
case DockerContainer::Sftp : return Proto::Sftp;
|
||||
default: return Proto::Any;
|
||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||
case DockerContainer::Dns: return Proto::Dns;
|
||||
case DockerContainer::Sftp: return Proto::Sftp;
|
||||
default: return Proto::Any;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,31 +245,34 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
#ifdef Q_OS_WINDOWS
|
||||
return true;
|
||||
|
||||
#elif defined (Q_OS_IOS)
|
||||
#elif defined(Q_OS_IOS)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
// case DockerContainer::ShadowSocks: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Cloak:
|
||||
return true;
|
||||
// case DockerContainer::ShadowSocks: return true;
|
||||
default: return false;
|
||||
}
|
||||
#elif defined (Q_OS_MAC)
|
||||
#elif defined(Q_OS_MAC)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Ipsec: return false;
|
||||
default: return true;
|
||||
}
|
||||
|
||||
#elif defined (Q_OS_ANDROID)
|
||||
#elif defined(Q_OS_ANDROID)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::ShadowSocks: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
#elif defined (Q_OS_LINUX)
|
||||
#elif defined(Q_OS_LINUX)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Ipsec: return false;
|
||||
@@ -183,14 +280,65 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
}
|
||||
|
||||
#else
|
||||
return false;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
||||
{
|
||||
switch (c) {
|
||||
case DockerContainer::Ipsec : return QStringList{"500", "4500"};
|
||||
default: return {};
|
||||
case DockerContainer::Ipsec: return QStringList { "500", "4500" };
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("Low");
|
||||
case DockerContainer::Awg: return tr("Medium or High");
|
||||
case DockerContainer::Cloak: return tr("Extreme");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
||||
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
||||
case DockerContainer::Cloak:
|
||||
return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return 3;
|
||||
case DockerContainer::Awg: return 2;
|
||||
case DockerContainer::Cloak: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isShareable(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
case DockerContainer::Sftp: return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,68 +8,72 @@
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace amnezia {
|
||||
|
||||
namespace ContainerEnumNS {
|
||||
Q_NAMESPACE
|
||||
enum DockerContainer {
|
||||
None = 0,
|
||||
OpenVpn,
|
||||
ShadowSocks,
|
||||
Cloak,
|
||||
WireGuard,
|
||||
Ipsec,
|
||||
|
||||
//non-vpn
|
||||
TorWebSite,
|
||||
Dns,
|
||||
//FileShare,
|
||||
Sftp
|
||||
};
|
||||
Q_ENUM_NS(DockerContainer)
|
||||
} // namespace ContainerEnumNS
|
||||
|
||||
using namespace ContainerEnumNS;
|
||||
using namespace ProtocolEnumNS;
|
||||
|
||||
class ContainerProps : public QObject
|
||||
namespace amnezia
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
||||
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
||||
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
||||
namespace ContainerEnumNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
enum DockerContainer {
|
||||
None = 0,
|
||||
Awg,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
|
||||
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
Dns,
|
||||
Sftp
|
||||
};
|
||||
Q_ENUM_NS(DockerContainer)
|
||||
} // namespace ContainerEnumNS
|
||||
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerHumanNames();
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDescriptions();
|
||||
using namespace ContainerEnumNS;
|
||||
using namespace ProtocolEnumNS;
|
||||
|
||||
// these protocols will be displayed in container settings
|
||||
Q_INVOKABLE static QVector<amnezia::Proto> protocolsForContainer(amnezia::DockerContainer container);
|
||||
class ContainerProps : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c);
|
||||
public:
|
||||
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
||||
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
||||
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
||||
|
||||
// binding between Docker container and main protocol of given container
|
||||
// it may be changed fot future containers :)
|
||||
Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c);
|
||||
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
||||
|
||||
Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c);
|
||||
Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c);
|
||||
};
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerHumanNames();
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDescriptions();
|
||||
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDetailedDescriptions();
|
||||
|
||||
// these protocols will be displayed in container settings
|
||||
Q_INVOKABLE static QVector<amnezia::Proto> protocolsForContainer(amnezia::DockerContainer container);
|
||||
|
||||
Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c);
|
||||
|
||||
static void declareQmlContainerEnum() {
|
||||
qmlRegisterUncreatableMetaObject(
|
||||
ContainerEnumNS::staticMetaObject,
|
||||
"ContainerEnum",
|
||||
1, 0,
|
||||
"ContainerEnum",
|
||||
"Error: only enums"
|
||||
);
|
||||
}
|
||||
// binding between Docker container and main protocol of given container
|
||||
// it may be changed fot future containers :)
|
||||
Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c);
|
||||
|
||||
Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c);
|
||||
Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c);
|
||||
|
||||
static bool isEasySetupContainer(amnezia::DockerContainer container);
|
||||
static QString easySetupHeader(amnezia::DockerContainer container);
|
||||
static QString easySetupDescription(amnezia::DockerContainer container);
|
||||
static int easySetupOrder(amnezia::DockerContainer container);
|
||||
|
||||
static bool isShareable(amnezia::DockerContainer container);
|
||||
};
|
||||
|
||||
static void declareQmlContainerEnum()
|
||||
{
|
||||
qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum",
|
||||
"Error: only enums");
|
||||
}
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ struct ServerCredentials
|
||||
{
|
||||
QString hostName;
|
||||
QString userName;
|
||||
QString password;
|
||||
QString secretData;
|
||||
int port = 22;
|
||||
|
||||
bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; }
|
||||
bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; }
|
||||
};
|
||||
|
||||
enum ErrorCode
|
||||
@@ -37,7 +37,7 @@ enum ErrorCode
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequsetDeniedError, SshInterruptedError, SshInternalError,
|
||||
SshPrivateKeyError, SshPrivateKeyFormatError,
|
||||
SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
|
||||
|
||||
// Ssh sftp errors
|
||||
SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError,
|
||||
@@ -69,7 +69,10 @@ enum ErrorCode
|
||||
OpenSslFailed,
|
||||
OpenVpnExecutableCrashed,
|
||||
ShadowSocksExecutableCrashed,
|
||||
CloakExecutableCrashed
|
||||
CloakExecutableCrashed,
|
||||
|
||||
// import and install errors
|
||||
ImportInvalidConfigError
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
@@ -24,6 +24,7 @@ QString errorString(ErrorCode code){
|
||||
case(SshInternalError): return QObject::tr("Ssh internal error");
|
||||
case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
|
||||
case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types");
|
||||
case(SshTimeoutError): return QObject::tr("Timeout connecting to server");
|
||||
|
||||
// Libssh sftp errors
|
||||
case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered");
|
||||
@@ -57,6 +58,8 @@ QString errorString(ErrorCode code){
|
||||
case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter");
|
||||
case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses");
|
||||
|
||||
case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentiaks for connecting to the server");
|
||||
|
||||
case(InternalError):
|
||||
default:
|
||||
return QObject::tr("Internal error");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "scripts_registry.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
{
|
||||
@@ -11,11 +11,11 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
|
||||
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||
case DockerContainer::Awg: return QLatin1String("awg");
|
||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||
|
||||
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
||||
case DockerContainer::Dns: return QLatin1String("dns");
|
||||
//case DockerContainer::FileShare: return QLatin1String("file_share");
|
||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||
default: return "";
|
||||
}
|
||||
@@ -45,6 +45,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
|
||||
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
|
||||
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
||||
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
||||
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ QString amnezia::scriptData(amnezia::SharedScriptType type)
|
||||
{
|
||||
QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type));
|
||||
QFile file(fileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Warning: script missing" << fileName;
|
||||
return "";
|
||||
}
|
||||
@@ -67,7 +68,7 @@ QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer co
|
||||
{
|
||||
QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type));
|
||||
QFile file(fileName);
|
||||
if (! file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Warning: script missing" << fileName;
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ enum ProtocolScriptType {
|
||||
configure_container,
|
||||
container_startup,
|
||||
openvpn_template,
|
||||
wireguard_template
|
||||
wireguard_template,
|
||||
awg_template
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QEventLoop>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QApplication>
|
||||
#include <QTemporaryFile>
|
||||
#include <QFileInfo>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <chrono>
|
||||
@@ -25,15 +24,14 @@
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "logger.h"
|
||||
#include "scripts_registry.h"
|
||||
#include "server_defs.h"
|
||||
#include "settings.h"
|
||||
#include "scripts_registry.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#include <configurators/vpn_configurator.h>
|
||||
|
||||
ServerController::ServerController(std::shared_ptr<Settings> settings, QObject *parent) :
|
||||
m_settings(settings)
|
||||
ServerController::ServerController(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -42,10 +40,10 @@ ServerController::~ServerController()
|
||||
m_sshClient.disconnectFromHost();
|
||||
}
|
||||
|
||||
|
||||
ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr) {
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||
{
|
||||
|
||||
auto error = m_sshClient.connectToHost(credentials);
|
||||
if (error != ErrorCode::NoError) {
|
||||
@@ -92,36 +90,36 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials,
|
||||
DockerContainer container, QString script,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr)
|
||||
ErrorCode
|
||||
ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||
{
|
||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
||||
|
||||
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName);
|
||||
e = runScript(credentials,
|
||||
replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||
runScript(credentials,
|
||||
replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
runScript(credentials, replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, const QString &file, const QString &path,
|
||||
libssh::SftpOverwriteMode overwriteMode)
|
||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &file, const QString &path,
|
||||
libssh::SftpOverwriteMode overwriteMode)
|
||||
{
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||
e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStd = [&](const QString &data, libssh::Client &) {
|
||||
@@ -130,61 +128,60 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
};
|
||||
|
||||
// mkdir
|
||||
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"")
|
||||
.arg(path);
|
||||
|
||||
e = runScript(credentials,
|
||||
replaceVars(mkdir, genVarsForScript(credentials, container)));
|
||||
if (e) return e;
|
||||
QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path);
|
||||
|
||||
e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container)));
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
if (e) return e;
|
||||
}
|
||||
else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
|
||||
if (e)
|
||||
return e;
|
||||
} else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
||||
genVarsForScript(credentials, container)), cbReadStd, cbReadStd);
|
||||
|
||||
if (e) return e;
|
||||
}
|
||||
else return ErrorCode::NotImplementedError;
|
||||
e = runScript(
|
||||
credentials,
|
||||
replaceVars(
|
||||
QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
if (e)
|
||||
return e;
|
||||
} else
|
||||
return ErrorCode::NotImplementedError;
|
||||
|
||||
if (stdOut.contains("Error: No such container:")) {
|
||||
return ErrorCode::ServerContainerMissingError;
|
||||
}
|
||||
|
||||
runScript(credentials,
|
||||
replaceVars(QString("sudo shred %1").arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)));
|
||||
|
||||
runScript(credentials,
|
||||
replaceVars(QString("sudo rm %1").arg(tmpFileName),
|
||||
genVarsForScript(credentials, container)));
|
||||
|
||||
runScript(credentials,
|
||||
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||
return e;
|
||||
}
|
||||
|
||||
QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode)
|
||||
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &path, ErrorCode *errorCode)
|
||||
{
|
||||
|
||||
if (errorCode) *errorCode = ErrorCode::NoError;
|
||||
|
||||
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").
|
||||
arg(ContainerProps::containerToString(container)).arg(path);
|
||||
if (errorCode)
|
||||
*errorCode = ErrorCode::NoError;
|
||||
|
||||
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"")
|
||||
.arg(ContainerProps::containerToString(container))
|
||||
.arg(path);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
@@ -196,8 +193,8 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
return QByteArray::fromHex(stdOut.toUtf8());
|
||||
}
|
||||
|
||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||
libssh::SftpOverwriteMode overwriteMode)
|
||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
|
||||
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode)
|
||||
{
|
||||
auto error = m_sshClient.connectToHost(credentials);
|
||||
if (error != ErrorCode::NoError) {
|
||||
@@ -209,7 +206,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
|
||||
localFile.write(data);
|
||||
localFile.close();
|
||||
|
||||
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc");
|
||||
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(),
|
||||
"non_desc");
|
||||
if (error != ErrorCode::NoError) {
|
||||
return error;
|
||||
}
|
||||
@@ -218,15 +216,14 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
|
||||
|
||||
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
|
||||
{
|
||||
return runScript(credentials,
|
||||
amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
return runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
genVarsForScript(credentials, container)));
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
genVarsForScript(credentials, container)));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
@@ -236,22 +233,33 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
e = isUserInSudo(credentials, container);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
e = isServerDpkgBusy(credentials, container);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
e = installDockerWorker(credentials, container);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished";
|
||||
|
||||
if (!isUpdate) {
|
||||
e = isServerPortBusy(credentials, container, config);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
}
|
||||
|
||||
if (!isUpdate) {
|
||||
e = isServerPortBusy(credentials, container, config);
|
||||
if (e)
|
||||
return e;
|
||||
}
|
||||
|
||||
e = prepareHostWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished";
|
||||
|
||||
removeContainer(credentials, container);
|
||||
@@ -259,15 +267,18 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
e = buildContainerWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished";
|
||||
|
||||
e = runContainerWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
qDebug().noquote() << "ServerController::setupContainer runContainerWorker finished";
|
||||
|
||||
e = configureContainerWorker(credentials, container, config);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
qDebug().noquote() << "ServerController::setupContainer configureContainerWorker finished";
|
||||
|
||||
setupServerFirewall(credentials);
|
||||
@@ -277,46 +288,25 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
|
||||
}
|
||||
|
||||
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &oldConfig, QJsonObject &newConfig)
|
||||
const QJsonObject &oldConfig, QJsonObject &newConfig)
|
||||
{
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is"
|
||||
<< reinstallRequired;
|
||||
|
||||
if (reinstallRequired) {
|
||||
return setupContainer(credentials, container, newConfig, true);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ErrorCode e = configureContainerWorker(credentials, container, newConfig);
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
return startupContainerWorker(credentials, container, newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject ServerController::createContainerInitialConfig(DockerContainer container, int port, TransportProto tp)
|
||||
{
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
|
||||
QJsonObject config {
|
||||
{ config_key::container, ContainerProps::containerToString(container) }
|
||||
};
|
||||
|
||||
QJsonObject protoConfig;
|
||||
protoConfig.insert(config_key::port, QString::number(port));
|
||||
protoConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto));
|
||||
|
||||
|
||||
if (container == DockerContainer::Sftp) {
|
||||
protoConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
|
||||
protoConfig.insert(config_key::password, Utils::getRandomString(10));
|
||||
}
|
||||
|
||||
config.insert(ProtocolProps::protoToString(mainProto), protoConfig);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
|
||||
const QJsonObject &newConfig)
|
||||
{
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
|
||||
@@ -324,25 +314,29 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||
|
||||
if (container == DockerContainer::OpenVpn) {
|
||||
if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) !=
|
||||
newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto))
|
||||
return true;
|
||||
if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)
|
||||
!= newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto))
|
||||
return true;
|
||||
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) !=
|
||||
newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort))
|
||||
return true;
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Cloak) {
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) !=
|
||||
newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort))
|
||||
return true;
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::ShadowSocks) {
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) !=
|
||||
newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort))
|
||||
return true;
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -364,75 +358,88 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
ErrorCode error = runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::install_docker),
|
||||
genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||
ErrorCode error =
|
||||
runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
||||
if (stdOut.contains("lock")) return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("command not found")) return ErrorCode::ServerDockerFailedError;
|
||||
if (stdOut.contains("lock"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("command not found"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
if (stdOut.contains("Docker status is not active"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config)
|
||||
{
|
||||
// create folder on host
|
||||
return runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host),
|
||||
genVarsForScript(credentials, container)));
|
||||
return runScript(
|
||||
credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
|
||||
}
|
||||
|
||||
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config)
|
||||
{
|
||||
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
|
||||
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
||||
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
||||
|
||||
if (e) return e;
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
|
||||
e = runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
|
||||
genVarsForScript(credentials, container, config)), cbReadStdOut);
|
||||
if (e) return e;
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
|
||||
genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut);
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
QJsonObject &config)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
||||
// stdOut += data + "\n";
|
||||
// };
|
||||
|
||||
ErrorCode e = runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
||||
genVarsForScript(credentials, container, config)), cbReadStdOut);
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
||||
genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut);
|
||||
|
||||
|
||||
if (stdOut.contains("docker: Error response from daemon")) return ErrorCode::ServerDockerFailedError;
|
||||
|
||||
if (stdOut.contains("address already in use")) return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||
if (stdOut.contains("is already in use by container")) return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||
if (stdOut.contains("invalid publish")) return ErrorCode::ServerDockerFailedError;
|
||||
if (stdOut.contains("address already in use"))
|
||||
return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||
if (stdOut.contains("is already in use by container"))
|
||||
return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||
if (stdOut.contains("invalid publish"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
QJsonObject &config)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
@@ -444,19 +451,18 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
|
||||
ErrorCode e = runContainerScript(credentials, container,
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
|
||||
genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container),
|
||||
genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config)
|
||||
{
|
||||
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
|
||||
|
||||
@@ -465,104 +471,144 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
|
||||
}
|
||||
|
||||
ErrorCode e = uploadTextFileToContainer(container, credentials,
|
||||
replaceVars(script, genVarsForScript(credentials, container, config)),
|
||||
"/opt/amnezia/start.sh");
|
||||
if (e) return e;
|
||||
replaceVars(script, genVarsForScript(credentials, container, config)),
|
||||
"/opt/amnezia/start.sh");
|
||||
if (e)
|
||||
return e;
|
||||
|
||||
return runScript(credentials,
|
||||
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"",
|
||||
genVarsForScript(credentials, container, config)));
|
||||
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && "
|
||||
"/opt/amnezia/start.sh\"",
|
||||
genVarsForScript(credentials, container, config)));
|
||||
}
|
||||
|
||||
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials,
|
||||
DockerContainer container, const QJsonObject &config)
|
||||
{
|
||||
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
|
||||
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
|
||||
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
|
||||
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
||||
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
||||
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
||||
|
||||
Vars vars;
|
||||
|
||||
vars.append({{"$REMOTE_HOST", credentials.hostName}});
|
||||
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
|
||||
|
||||
// OpenVPN vars
|
||||
vars.append({{"$OPENVPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) }});
|
||||
vars.append({{"$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) }});
|
||||
vars.append({{"$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) }});
|
||||
vars.append(
|
||||
{ { "$OPENVPN_SUBNET_IP",
|
||||
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
|
||||
vars.append({ { "$OPENVPN_SUBNET_CIDR",
|
||||
openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
|
||||
vars.append({ { "$OPENVPN_SUBNET_MASK",
|
||||
openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
|
||||
|
||||
vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) }});
|
||||
vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) }});
|
||||
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
|
||||
vars.append(
|
||||
{ { "$OPENVPN_TRANSPORT_PROTO",
|
||||
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
|
||||
|
||||
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
|
||||
vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }});
|
||||
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
|
||||
|
||||
vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) }});
|
||||
vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) }});
|
||||
vars.append({ { "$OPENVPN_CIPHER",
|
||||
openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
|
||||
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
|
||||
|
||||
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
|
||||
vars.append({{"$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" }});
|
||||
vars.append({ { "$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" } });
|
||||
if (!isTlsAuth) {
|
||||
// erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig
|
||||
vars.append({{"$OPENVPN_TA_KEY", "" }});
|
||||
vars.append({ { "$OPENVPN_TA_KEY", "" } });
|
||||
}
|
||||
|
||||
vars.append({{"$OPENVPN_ADDITIONAL_CLIENT_CONFIG", openvpnConfig.value(config_key::additional_client_config).
|
||||
toString(protocols::openvpn::defaultAdditionalClientConfig) }});
|
||||
vars.append({{"$OPENVPN_ADDITIONAL_SERVER_CONFIG", openvpnConfig.value(config_key::additional_server_config).
|
||||
toString(protocols::openvpn::defaultAdditionalServerConfig) }});
|
||||
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
|
||||
openvpnConfig.value(config_key::additional_client_config)
|
||||
.toString(protocols::openvpn::defaultAdditionalClientConfig) } });
|
||||
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
|
||||
openvpnConfig.value(config_key::additional_server_config)
|
||||
.toString(protocols::openvpn::defaultAdditionalServerConfig) } });
|
||||
|
||||
// ShadowSocks vars
|
||||
vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) }});
|
||||
vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) }});
|
||||
vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) }});
|
||||
vars.append({ { "$SHADOWSOCKS_SERVER_PORT",
|
||||
ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
|
||||
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
|
||||
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
|
||||
vars.append({ { "$SHADOWSOCKS_CIPHER",
|
||||
ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
|
||||
|
||||
vars.append({{"$CONTAINER_NAME", ContainerProps::containerToString(container)}});
|
||||
vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container)}});
|
||||
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
|
||||
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
|
||||
|
||||
// Cloak vars
|
||||
vars.append({{"$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) }});
|
||||
vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }});
|
||||
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
|
||||
vars.append({ { "$FAKE_WEB_SITE_ADDRESS",
|
||||
cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
|
||||
|
||||
// Wireguard vars
|
||||
vars.append({{"$WIREGUARD_SUBNET_IP", wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) }});
|
||||
vars.append({{"$WIREGUARD_SUBNET_CIDR", wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) }});
|
||||
vars.append({{"$WIREGUARD_SUBNET_MASK", wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) }});
|
||||
vars.append(
|
||||
{ { "$WIREGUARD_SUBNET_IP",
|
||||
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
||||
vars.append({ { "$WIREGUARD_SUBNET_CIDR",
|
||||
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
|
||||
vars.append({ { "$WIREGUARD_SUBNET_MASK",
|
||||
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
|
||||
|
||||
vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }});
|
||||
vars.append({ { "$WIREGUARD_SERVER_PORT",
|
||||
wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
|
||||
|
||||
// IPsec vars
|
||||
vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}});
|
||||
vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}});
|
||||
vars.append({{"$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1"}});
|
||||
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
|
||||
vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } });
|
||||
vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } });
|
||||
|
||||
vars.append({{"$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24"}});
|
||||
vars.append({{"$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250"}});
|
||||
vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } });
|
||||
vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } });
|
||||
|
||||
vars.append({{"$IPSEC_VPN_SHA2_TRUNCBUG", "yes"}});
|
||||
vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } });
|
||||
|
||||
vars.append({{"$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes"}});
|
||||
vars.append({{"$IPSEC_VPN_DISABLE_IKEV2", "no"}});
|
||||
vars.append({{"$IPSEC_VPN_DISABLE_L2TP", "no"}});
|
||||
vars.append({{"$IPSEC_VPN_DISABLE_XAUTH", "no"}});
|
||||
vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } });
|
||||
vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } });
|
||||
vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } });
|
||||
vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } });
|
||||
|
||||
vars.append({{"$IPSEC_VPN_C2C_TRAFFIC", "no"}});
|
||||
|
||||
vars.append({{"$PRIMARY_SERVER_DNS", m_settings->primaryDns()}});
|
||||
vars.append({{"$SECONDARY_SERVER_DNS", m_settings->secondaryDns()}});
|
||||
vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } });
|
||||
|
||||
vars.append({ { "$PRIMARY_SERVER_DNS", m_settings->primaryDns() } });
|
||||
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
|
||||
|
||||
// Sftp vars
|
||||
vars.append({{"$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) }});
|
||||
vars.append({{"$SFTP_USER", sftpConfig.value(config_key::userName).toString() }});
|
||||
vars.append({{"$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() }});
|
||||
vars.append(
|
||||
{ { "$SFTP_PORT",
|
||||
sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
|
||||
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
|
||||
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
||||
|
||||
// Amnezia wireguard vars
|
||||
vars.append({ { "$AWG_SERVER_PORT",
|
||||
amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
||||
|
||||
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
||||
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
|
||||
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
|
||||
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
|
||||
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE",
|
||||
amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
|
||||
vars.append({ { "$INIT_PACKET_MAGIC_HEADER",
|
||||
amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
|
||||
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER",
|
||||
amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
|
||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER",
|
||||
amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
|
||||
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||
|
||||
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
||||
if (!serverIp.isEmpty()) {
|
||||
vars.append({{"$SERVER_IP_ADDRESS", serverIp}});
|
||||
}
|
||||
else {
|
||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||
} else {
|
||||
qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName";
|
||||
}
|
||||
|
||||
@@ -581,10 +627,11 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
ErrorCode e = runScript(credentials,
|
||||
amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
||||
ErrorCode e =
|
||||
runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
if (errorCode) *errorCode = e;
|
||||
if (errorCode)
|
||||
*errorCode = e;
|
||||
|
||||
return stdOut;
|
||||
}
|
||||
@@ -596,9 +643,9 @@ void ServerController::setCancelInstallation(const bool cancel)
|
||||
|
||||
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
||||
{
|
||||
return runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall),
|
||||
genVarsForScript(credentials)));
|
||||
return runScript(
|
||||
credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
|
||||
}
|
||||
|
||||
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
||||
@@ -610,7 +657,8 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
||||
return s;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config)
|
||||
{
|
||||
if (container == DockerContainer::Dns) {
|
||||
return ErrorCode::NoError;
|
||||
@@ -633,11 +681,15 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
|
||||
|
||||
QString defaultPort("%1");
|
||||
QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
|
||||
QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
|
||||
QString port =
|
||||
containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
|
||||
QString defaultTransportProto =
|
||||
ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
|
||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||
|
||||
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n | grep -E ':%1 ").arg(port);
|
||||
// TODO reimplement with netstat
|
||||
QString script =
|
||||
QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||
for (auto &port : fixedPorts) {
|
||||
script = script.append("|:%1").arg(port);
|
||||
}
|
||||
@@ -647,8 +699,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
script = script.append(" | grep LISTEN");
|
||||
}
|
||||
|
||||
ErrorCode errorCode = runScript(credentials,
|
||||
replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
@@ -676,9 +728,11 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
||||
};
|
||||
|
||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||
ErrorCode error =
|
||||
runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
if (!stdOut.contains("sudo")) return ErrorCode::ServerUserNotInSudo;
|
||||
if (!stdOut.contains("sudo"))
|
||||
return ErrorCode::ServerUserNotInSudo;
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -699,27 +753,29 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
||||
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, &stdOut, &cbReadStdOut, &cbReadStdErr, &credentials]() {
|
||||
// max 100 attempts
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
if (m_cancelInstallation) {
|
||||
return ErrorCode::ServerCancelInstallation;
|
||||
}
|
||||
stdOut.clear();
|
||||
runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
|
||||
genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||
genVarsForScript(credentials)),
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
// if 'fuser' is not installed, skip check
|
||||
if (stdOut.contains("Not installed")) return ErrorCode::NoError;
|
||||
if (stdOut.contains("Packet manager not found"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("fuser not installed"))
|
||||
return ErrorCode::NoError;
|
||||
|
||||
if (stdOut.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
else {
|
||||
#ifdef MZ_DEBUG
|
||||
} else {
|
||||
#ifdef MZ_DEBUG
|
||||
qDebug().noquote() << stdOut;
|
||||
#endif
|
||||
#endif
|
||||
emit serverIsBusy(true);
|
||||
QThread::msleep(5000);
|
||||
QThread::msleep(10000);
|
||||
}
|
||||
}
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
@@ -736,7 +792,8 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
||||
return future.result();
|
||||
}
|
||||
|
||||
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers)
|
||||
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
||||
QMap<DockerContainer, QJsonObject> &installedContainers)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
@@ -767,14 +824,47 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential
|
||||
QString port = containerAndPortMatch.captured(2);
|
||||
QString transportProto = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerProps::containerFromString(name);
|
||||
|
||||
QJsonObject config;
|
||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||
QJsonObject config {
|
||||
{ config_key::container, name },
|
||||
{ ProtocolProps::protoToString(mainProto), QJsonObject {
|
||||
{ config_key::port, port },
|
||||
{ config_key::transport_proto, transportProto }}
|
||||
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||
QJsonObject containerConfig;
|
||||
if (protocol == mainProto) {
|
||||
containerConfig.insert(config_key::port, port);
|
||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
||||
|
||||
if (protocol == Proto::Awg) {
|
||||
QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode);
|
||||
|
||||
QMap<QString, QString> serverConfigMap;
|
||||
auto serverConfigLines = serverConfig.split("\n");
|
||||
for (auto &line : serverConfigLines) {
|
||||
auto trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
|
||||
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
|
||||
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
|
||||
containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
|
||||
containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
|
||||
containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
|
||||
containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
|
||||
containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
|
||||
containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
|
||||
}
|
||||
|
||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||
}
|
||||
};
|
||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
||||
}
|
||||
installedContainers.insert(container, config);
|
||||
}
|
||||
}
|
||||
@@ -782,7 +872,8 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &callback)
|
||||
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
||||
const std::function<QString()> &callback)
|
||||
{
|
||||
auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback);
|
||||
return error;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
|
||||
#include "defs.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "defs.h"
|
||||
#include "sshclient.h"
|
||||
|
||||
class Settings;
|
||||
@@ -24,52 +24,62 @@ public:
|
||||
|
||||
ErrorCode removeAllContainers(const ServerCredentials &credentials);
|
||||
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
QJsonObject &config, bool isUpdate = false);
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config,
|
||||
bool isUpdate = false);
|
||||
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &oldConfig, QJsonObject &newConfig);
|
||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, QJsonObject> &installedContainers);
|
||||
|
||||
// create initial config - generate passwords, etc
|
||||
QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp);
|
||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
||||
QMap<DockerContainer, QJsonObject> &installedContainers);
|
||||
|
||||
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &file, const QString &path,
|
||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config = QJsonObject());
|
||||
|
||||
ErrorCode uploadTextFileToContainer(
|
||||
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
|
||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &path, ErrorCode *errorCode = nullptr);
|
||||
|
||||
QString replaceVars(const QString &script, const Vars &vars);
|
||||
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject());
|
||||
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
|
||||
const QJsonObject &config = QJsonObject());
|
||||
|
||||
ErrorCode runScript(const ServerCredentials &credentials, QString script,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||
|
||||
ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||
const std::function<ErrorCode (const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||
ErrorCode
|
||||
runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||
|
||||
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
||||
|
||||
void setCancelInstallation(const bool cancel);
|
||||
|
||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &callback);
|
||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
||||
const std::function<QString()> &callback);
|
||||
|
||||
private:
|
||||
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
||||
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config = QJsonObject());
|
||||
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config = QJsonObject());
|
||||
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||
QJsonObject &config);
|
||||
|
||||
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
|
||||
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
|
||||
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &config);
|
||||
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
|
||||
const QJsonObject &newConfig);
|
||||
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
|
||||
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
||||
|
||||
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
|
||||
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
|
||||
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
||||
|
||||
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ const uint32_t S_IRWXU = 0644;
|
||||
#endif
|
||||
|
||||
namespace libssh {
|
||||
const QString libsshTimeoutError = "Timeout connecting to";
|
||||
|
||||
std::function<QString()> Client::m_passphraseCallback;
|
||||
|
||||
Client::Client(QObject *parent) : QObject(parent)
|
||||
@@ -45,20 +47,29 @@ namespace libssh {
|
||||
ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str());
|
||||
ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity);
|
||||
|
||||
int connectionResult = ssh_connect(m_session);
|
||||
QFutureWatcher<int> watcher;
|
||||
QFuture<int> future = QtConcurrent::run([this]() {
|
||||
return ssh_connect(m_session);
|
||||
});
|
||||
|
||||
QEventLoop wait;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
||||
watcher.setFuture(future);
|
||||
wait.exec();
|
||||
|
||||
int connectionResult = watcher.result();
|
||||
|
||||
if (connectionResult != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
std::string authUsername = credentials.userName.toStdString();
|
||||
|
||||
int authResult = SSH_ERROR;
|
||||
if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) {
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
ssh_key privateKey = nullptr;
|
||||
ssh_key publicKey = nullptr;
|
||||
authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
|
||||
authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
|
||||
if (authResult == SSH_OK) {
|
||||
authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey);
|
||||
}
|
||||
@@ -78,18 +89,17 @@ namespace libssh {
|
||||
ssh_key_free(privateKey);
|
||||
}
|
||||
if (authResult != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
ErrorCode errorCode = fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
qCritical() << ssh_get_error(m_session);
|
||||
ErrorCode errorCode = fromLibsshErrorCode();
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = ErrorCode::SshPrivateKeyFormatError;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
} else {
|
||||
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str());
|
||||
authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str());
|
||||
if (authResult != SSH_OK) {
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,16 +196,15 @@ namespace libssh {
|
||||
ErrorCode Client::writeResponse(const QString &data)
|
||||
{
|
||||
if (m_channel == nullptr) {
|
||||
qDebug() << "ssh channel not initialized";
|
||||
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
qCritical() << "ssh channel not initialized";
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size());
|
||||
if (bytesWritten == data.size() && ssh_channel_write(m_channel, "\n", 1)) {
|
||||
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
ErrorCode Client::closeChannel()
|
||||
@@ -210,8 +219,7 @@ namespace libssh {
|
||||
ssh_channel_free(m_channel);
|
||||
m_channel = nullptr;
|
||||
}
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
return fromLibsshErrorCode(ssh_get_error_code(m_session));
|
||||
return fromLibsshErrorCode();
|
||||
}
|
||||
|
||||
ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc)
|
||||
@@ -308,12 +316,21 @@ namespace libssh {
|
||||
sftp_free(m_sftpSession);
|
||||
m_sftpSession = nullptr;
|
||||
}
|
||||
qDebug() << ssh_get_error(m_session);
|
||||
qCritical() << ssh_get_error(m_session);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode Client::fromLibsshErrorCode(int errorCode)
|
||||
ErrorCode Client::fromLibsshErrorCode()
|
||||
{
|
||||
int errorCode = ssh_get_error_code(m_session);
|
||||
if (errorCode != SSH_NO_ERROR) {
|
||||
QString errorMessage = ssh_get_error(m_session);
|
||||
qCritical() << errorMessage;
|
||||
if (errorMessage.contains(libsshTimeoutError)) {
|
||||
return ErrorCode::SshTimeoutError;
|
||||
}
|
||||
}
|
||||
|
||||
switch (errorCode) {
|
||||
case(SSH_NO_ERROR): return ErrorCode::NoError;
|
||||
case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError;
|
||||
@@ -350,7 +367,7 @@ namespace libssh {
|
||||
|
||||
ssh_key privateKey = nullptr;
|
||||
m_passphraseCallback = passphraseCallback;
|
||||
authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
|
||||
authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey);
|
||||
if (authResult == SSH_OK) {
|
||||
char *b64 = nullptr;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace libssh {
|
||||
private:
|
||||
ErrorCode closeChannel();
|
||||
ErrorCode closeSftpSession();
|
||||
ErrorCode fromLibsshErrorCode(int errorCode);
|
||||
ErrorCode fromLibsshErrorCode();
|
||||
ErrorCode fromLibsshSftpErrorCode(int errorCode);
|
||||
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMetaEnum>
|
||||
#include <QTimer>
|
||||
|
||||
#include "leakdetector.h"
|
||||
@@ -64,9 +65,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
// method calls switchServer().
|
||||
//
|
||||
// At the end, if the activation succeds, the `connected` signal is emitted.
|
||||
// If the activation abort's for any reason `the `activationFailure` signal is
|
||||
// emitted.
|
||||
logger.debug() << "Activating interface";
|
||||
auto emit_failure_guard = qScopeGuard([this] { emit activationFailure(); });
|
||||
|
||||
if (m_connections.contains(config.m_hopindex)) {
|
||||
if (m_connections.contains(config.m_hopType)) {
|
||||
if (supportServerSwitching(config)) {
|
||||
logger.debug() << "Already connected. Server switching supported.";
|
||||
|
||||
@@ -85,10 +89,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
bool status = run(Switch, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return status;
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.warning() << "Already connected. Server switching not supported.";
|
||||
@@ -96,8 +102,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopindex));
|
||||
return activate(config);
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
||||
if (activate(config)) {
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareActivation(config);
|
||||
@@ -112,13 +122,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
|
||||
// Configure routing for excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
QHostAddress address(i);
|
||||
if (m_excludedAddrSet.contains(address)) {
|
||||
m_excludedAddrSet[address]++;
|
||||
continue;
|
||||
}
|
||||
wgutils()->addExclusionRoute(address);
|
||||
m_excludedAddrSet[address] = 1;
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Add the peer to this interface.
|
||||
@@ -142,7 +146,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
|
||||
// set routing
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.debug() << "Routing configuration failed for"
|
||||
<< logger.sensitive(ip.toString());
|
||||
return false;
|
||||
@@ -152,15 +156,21 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
bool status = run(Up, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
return status;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||
if ((config.m_hopindex == 0) && supportDnsUtils()) {
|
||||
if (!supportDnsUtils()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||
QList<QHostAddress> resolvers;
|
||||
resolvers.append(QHostAddress(config.m_dnsServer));
|
||||
|
||||
@@ -199,6 +209,28 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::addExclusionRoute(const IPAddress& prefix) {
|
||||
if (m_excludedAddrSet.contains(prefix)) {
|
||||
m_excludedAddrSet[prefix]++;
|
||||
return true;
|
||||
}
|
||||
if (!wgutils()->addExclusionRoute(prefix)) {
|
||||
return false;
|
||||
}
|
||||
m_excludedAddrSet[prefix] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::delExclusionRoute(const IPAddress& prefix) {
|
||||
Q_ASSERT(m_excludedAddrSet.contains(prefix));
|
||||
if (m_excludedAddrSet[prefix] > 1) {
|
||||
m_excludedAddrSet[prefix]--;
|
||||
return true;
|
||||
}
|
||||
m_excludedAddrSet.remove(prefix);
|
||||
return wgutils()->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
#define GETVALUE(name, where, jsontype) \
|
||||
@@ -216,8 +248,8 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
|
||||
GETVALUE("privateKey", config.m_privateKey, String);
|
||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
|
||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
||||
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
|
||||
@@ -247,15 +279,24 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
config.m_dnsServer = value.toString();
|
||||
}
|
||||
|
||||
if (!obj.contains("hopindex")) {
|
||||
config.m_hopindex = 0;
|
||||
if (!obj.contains("hopType")) {
|
||||
config.m_hopType = InterfaceConfig::SingleHop;
|
||||
} else {
|
||||
QJsonValue value = obj.value("hopindex");
|
||||
if (!value.isDouble()) {
|
||||
logger.error() << "hopindex is not a number";
|
||||
QJsonValue value = obj.value("hopType");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "hopType is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool okay;
|
||||
QByteArray vdata = value.toString().toUtf8();
|
||||
QMetaEnum meta = QMetaEnum::fromType<InterfaceConfig::HopType>();
|
||||
config.m_hopType =
|
||||
InterfaceConfig::HopType(meta.keyToValue(vdata.constData(), &okay));
|
||||
if (!okay) {
|
||||
logger.error() << "hopType" << value.toString() << "is not valid";
|
||||
return false;
|
||||
}
|
||||
config.m_hopindex = value.toInt();
|
||||
}
|
||||
|
||||
if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) {
|
||||
@@ -318,6 +359,23 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull()
|
||||
&& !obj.value("Jmax").isNull() && !obj.value("S1").isNull()
|
||||
&& !obj.value("S2").isNull() && !obj.value("H1").isNull()
|
||||
&& !obj.value("H2").isNull() && !obj.value("H3").isNull()
|
||||
&& !obj.value("H4").isNull()) {
|
||||
config.m_junkPacketCount = obj.value("Jc").toString();
|
||||
config.m_junkPacketMinSize = obj.value("Jmin").toString();
|
||||
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
|
||||
config.m_initPacketJunkSize = obj.value("S1").toString();
|
||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||
config.m_responsePacketMagicHeader = obj.value("H2").toString();
|
||||
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
|
||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -325,8 +383,8 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
// Deactivate the main interface.
|
||||
if (m_connections.contains(0)) {
|
||||
const ConnectionState& state = m_connections.value(0);
|
||||
if (!m_connections.isEmpty()) {
|
||||
const ConnectionState& state = m_connections.first();
|
||||
if (!run(Down, state.m_config)) {
|
||||
return false;
|
||||
}
|
||||
@@ -349,9 +407,9 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||
// Cleanup peers and routing
|
||||
for (const ConnectionState& state : m_connections) {
|
||||
const InterfaceConfig& config = state.m_config;
|
||||
logger.debug() << "Deleting routes for hop" << config.m_hopindex;
|
||||
logger.debug() << "Deleting routes for" << config.m_hopType;
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
}
|
||||
wgutils()->deletePeer(config);
|
||||
}
|
||||
@@ -376,14 +434,14 @@ QString Daemon::logs() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Daemon::cleanLogs() { }
|
||||
void Daemon::cleanLogs() { }
|
||||
|
||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||
if (!m_connections.contains(config.m_hopindex)) {
|
||||
if (!m_connections.contains(config.m_hopType)) {
|
||||
return false;
|
||||
}
|
||||
const InterfaceConfig& current =
|
||||
m_connections.value(config.m_hopindex).m_config;
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
return current.m_privateKey == config.m_privateKey &&
|
||||
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||
@@ -395,21 +453,15 @@ bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
logger.debug() << "Switching server for hop" << config.m_hopindex;
|
||||
logger.debug() << "Switching server for" << config.m_hopType;
|
||||
|
||||
Q_ASSERT(m_connections.contains(config.m_hopindex));
|
||||
Q_ASSERT(m_connections.contains(config.m_hopType));
|
||||
const InterfaceConfig& lastConfig =
|
||||
m_connections.value(config.m_hopindex).m_config;
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
// Configure routing for new excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
QHostAddress address(i);
|
||||
if (m_excludedAddrSet.contains(address)) {
|
||||
m_excludedAddrSet[address]++;
|
||||
continue;
|
||||
}
|
||||
wgutils()->addExclusionRoute(address);
|
||||
m_excludedAddrSet[address] = 1;
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Activate the new peer and its routes.
|
||||
@@ -418,7 +470,7 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
return false;
|
||||
}
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.error() << "Server switch failed to update the routing table";
|
||||
break;
|
||||
}
|
||||
@@ -426,18 +478,11 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
|
||||
// Remove routing entries for the old peer.
|
||||
for (const QString& i : lastConfig.m_excludedAddresses) {
|
||||
QHostAddress address(i);
|
||||
Q_ASSERT(m_excludedAddrSet.contains(address));
|
||||
if (m_excludedAddrSet[address] > 1) {
|
||||
m_excludedAddrSet[address]--;
|
||||
continue;
|
||||
}
|
||||
wgutils()->deleteExclusionRoute(address);
|
||||
m_excludedAddrSet.remove(address);
|
||||
delExclusionRoute(QHostAddress(i));
|
||||
}
|
||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
||||
wgutils()->deleteRoutePrefix(ip, config.m_hopindex);
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +493,7 @@ bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
}
|
||||
}
|
||||
|
||||
m_connections[config.m_hopindex] = ConnectionState(config);
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -457,12 +502,12 @@ QJsonObject Daemon::getStatus() {
|
||||
QJsonObject json;
|
||||
logger.debug() << "Status request";
|
||||
|
||||
if (!m_connections.contains(0) || !wgutils()->interfaceExists()) {
|
||||
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
const ConnectionState& connection = m_connections.value(0);
|
||||
const ConnectionState& connection = m_connections.first();
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||
@@ -495,6 +540,7 @@ void Daemon::checkHandshake() {
|
||||
if (connection.m_date.isValid()) {
|
||||
continue;
|
||||
}
|
||||
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
||||
|
||||
// Check if the handshake has completed.
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
|
||||
@@ -43,11 +43,18 @@ class Daemon : public QObject {
|
||||
|
||||
signals:
|
||||
void connected(const QString& pubkey);
|
||||
/**
|
||||
* Can be fired if a call to activate() was unsucessfull
|
||||
* and connected systems should rollback
|
||||
*/
|
||||
void activationFailure();
|
||||
void disconnected();
|
||||
void backendFailure();
|
||||
|
||||
private:
|
||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||
bool addExclusionRoute(const IPAddress& address);
|
||||
bool delExclusionRoute(const IPAddress& address);
|
||||
|
||||
protected:
|
||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||
@@ -75,8 +82,8 @@ class Daemon : public QObject {
|
||||
QDateTime m_date;
|
||||
InterfaceConfig m_config;
|
||||
};
|
||||
QMap<int, ConnectionState> m_connections;
|
||||
QHash<QHostAddress, int> m_excludedAddrSet;
|
||||
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
||||
QHash<IPAddress, int> m_excludedAddrSet;
|
||||
QTimer m_handshakeTimer;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#ifdef MZ_MACOS
|
||||
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
@@ -68,7 +68,8 @@ bool DaemonLocalServer::initialize() {
|
||||
QString DaemonLocalServer::daemonPath() const {
|
||||
#if defined(MZ_WINDOWS)
|
||||
return "\\\\.\\pipe\\amneziavpn";
|
||||
#elif defined(MZ_MACOS)
|
||||
#endif
|
||||
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||
QDir dir("/var/run");
|
||||
if (!dir.exists()) {
|
||||
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
|
||||
@@ -92,7 +93,5 @@ QString DaemonLocalServer::daemonPath() const {
|
||||
}
|
||||
|
||||
return VAR_PATH;
|
||||
#else
|
||||
# error Unsupported platform
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
}
|
||||
|
||||
if (type == "deactivate") {
|
||||
Daemon::instance()->deactivate();
|
||||
Daemon::instance()->deactivate(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
150
client/daemon/interfaceconfig.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMetaEnum>
|
||||
|
||||
QJsonObject InterfaceConfig::toJson() const {
|
||||
QJsonObject json;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<HopType>();
|
||||
|
||||
json.insert("hopType", QJsonValue(metaEnum.valueToKey(m_hopType)));
|
||||
json.insert("privateKey", QJsonValue(m_privateKey));
|
||||
json.insert("deviceIpv4Address", QJsonValue(m_deviceIpv4Address));
|
||||
json.insert("deviceIpv6Address", QJsonValue(m_deviceIpv6Address));
|
||||
json.insert("serverPublicKey", QJsonValue(m_serverPublicKey));
|
||||
json.insert("serverPskKey", QJsonValue(m_serverPskKey));
|
||||
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
|
||||
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
|
||||
json.insert("serverPort", QJsonValue((double)m_serverPort));
|
||||
if ((m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
|
||||
json.insert("dnsServer", QJsonValue(m_dnsServer));
|
||||
}
|
||||
|
||||
QJsonArray allowedIPAddesses;
|
||||
for (const IPAddress& i : m_allowedIPAddressRanges) {
|
||||
QJsonObject range;
|
||||
range.insert("address", QJsonValue(i.address().toString()));
|
||||
range.insert("range", QJsonValue((double)i.prefixLength()));
|
||||
range.insert("isIpv6",
|
||||
QJsonValue(i.type() == QAbstractSocket::IPv6Protocol));
|
||||
allowedIPAddesses.append(range);
|
||||
};
|
||||
json.insert("allowedIPAddressRanges", allowedIPAddesses);
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
for (const QString& i : m_excludedAddresses) {
|
||||
jsExcludedAddresses.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
QJsonArray disabledApps;
|
||||
for (const QString& i : m_vpnDisabledApps) {
|
||||
disabledApps.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("vpnDisabledApps", disabledApps);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
#define VALIDATE(x) \
|
||||
if (x.contains("\n")) return "";
|
||||
|
||||
VALIDATE(m_privateKey);
|
||||
VALIDATE(m_deviceIpv4Address);
|
||||
VALIDATE(m_deviceIpv6Address);
|
||||
VALIDATE(m_serverIpv4Gateway);
|
||||
VALIDATE(m_serverIpv6Gateway);
|
||||
VALIDATE(m_serverPublicKey);
|
||||
VALIDATE(m_serverIpv4AddrIn);
|
||||
VALIDATE(m_serverIpv6AddrIn);
|
||||
#undef VALIDATE
|
||||
|
||||
QString content;
|
||||
QTextStream out(&content);
|
||||
out << "[Interface]\n";
|
||||
out << "PrivateKey = " << m_privateKey << "\n";
|
||||
|
||||
QStringList addresses;
|
||||
if (!m_deviceIpv4Address.isNull()) {
|
||||
addresses.append(m_deviceIpv4Address);
|
||||
}
|
||||
if (!m_deviceIpv6Address.isNull()) {
|
||||
addresses.append(m_deviceIpv6Address);
|
||||
}
|
||||
if (addresses.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
out << "Address = " << addresses.join(", ") << "\n";
|
||||
|
||||
if (!m_dnsServer.isNull()) {
|
||||
QStringList dnsServers(m_dnsServer);
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (m_dnsServer == m_serverIpv4Gateway) {
|
||||
dnsServers.append(m_serverIpv6Gateway);
|
||||
}
|
||||
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||
}
|
||||
|
||||
if (!m_junkPacketCount.isNull()) {
|
||||
out << "Jc = " << m_junkPacketCount << "\n";
|
||||
}
|
||||
if (!m_junkPacketMinSize.isNull()) {
|
||||
out << "JMin = " << m_junkPacketMinSize << "\n";
|
||||
}
|
||||
if (!m_junkPacketMaxSize.isNull()) {
|
||||
out << "JMax = " << m_junkPacketMaxSize << "\n";
|
||||
}
|
||||
if (!m_initPacketJunkSize.isNull()) {
|
||||
out << "S1 = " << m_initPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_responsePacketJunkSize.isNull()) {
|
||||
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_initPacketMagicHeader.isNull()) {
|
||||
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_responsePacketMagicHeader.isNull()) {
|
||||
out << "H2 = " << m_responsePacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_underloadPacketMagicHeader.isNull()) {
|
||||
out << "H3 = " << m_underloadPacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_transportPacketMagicHeader.isNull()) {
|
||||
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
// If any extra config was provided, append it now.
|
||||
for (const QString& key : extra.keys()) {
|
||||
out << key << " = " << extra[key] << "\n";
|
||||
}
|
||||
|
||||
out << "\n[Peer]\n";
|
||||
out << "PublicKey = " << m_serverPublicKey << "\n";
|
||||
out << "Endpoint = " << m_serverIpv4AddrIn.toUtf8() << ":" << m_serverPort
|
||||
<< "\n";
|
||||
|
||||
/* In theory, we should use the ipv6 endpoint, but wireguard doesn't seem
|
||||
* to be happy if there are 2 endpoints.
|
||||
out << "Endpoint = [" << config.m_serverIpv6AddrIn << "]:"
|
||||
<< config.m_serverPort << "\n";
|
||||
*/
|
||||
QStringList ranges;
|
||||
for (const IPAddress& ip : m_allowedIPAddressRanges) {
|
||||
ranges.append(ip.toString());
|
||||
}
|
||||
out << "AllowedIPs = " << ranges.join(", ") << "\n";
|
||||
|
||||
return content;
|
||||
}
|
||||
@@ -10,22 +10,49 @@
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
||||
struct InterfaceConfig {
|
||||
int m_hopindex = 0;
|
||||
class QJsonObject;
|
||||
|
||||
class InterfaceConfig {
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
InterfaceConfig() {}
|
||||
|
||||
enum HopType { SingleHop, MultiHopEntry, MultiHopExit };
|
||||
Q_ENUM(HopType)
|
||||
|
||||
HopType m_hopType;
|
||||
QString m_privateKey;
|
||||
QString m_deviceIpv4Address;
|
||||
QString m_deviceIpv6Address;
|
||||
QString m_serverIpv4Gateway;
|
||||
QString m_serverIpv6Gateway;
|
||||
QString m_serverPublicKey;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv4AddrIn;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv6AddrIn;
|
||||
QString m_dnsServer;
|
||||
int m_serverPort = 0;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
#endif
|
||||
|
||||
QString m_junkPacketCount;
|
||||
QString m_junkPacketMinSize;
|
||||
QString m_junkPacketMaxSize;
|
||||
QString m_initPacketJunkSize;
|
||||
QString m_responsePacketJunkSize;
|
||||
QString m_initPacketMagicHeader;
|
||||
QString m_responsePacketMagicHeader;
|
||||
QString m_underloadPacketMagicHeader;
|
||||
QString m_transportPacketMagicHeader;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||
};
|
||||
|
||||
#endif // INTERFACECONFIG_H
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef WIREGUARDUTILS_H
|
||||
#define WIREGUARDUTILS_H
|
||||
|
||||
#define _WINSOCKAPI_
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
@@ -12,7 +14,7 @@
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
constexpr const char* WG_INTERFACE = "moz0";
|
||||
constexpr const char* WG_INTERFACE = "amn0";
|
||||
|
||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||
|
||||
@@ -41,11 +43,11 @@ class WireguardUtils : public QObject {
|
||||
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
||||
virtual QList<PeerStatus> getPeerStatus() = 0;
|
||||
|
||||
virtual bool updateRoutePrefix(const IPAddress& prefix, int hopindex) = 0;
|
||||
virtual bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) = 0;
|
||||
virtual bool updateRoutePrefix(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0;
|
||||
|
||||
virtual bool addExclusionRoute(const QHostAddress& address) = 0;
|
||||
virtual bool deleteExclusionRoute(const QHostAddress& address) = 0;
|
||||
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILS_H
|
||||
|
||||
BIN
client/fonts/pt-root-ui_vf.ttf
Normal file
BIN
client/images/amneziaBigLogo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
1
client/images/amneziaBigLogo.svg
Normal file
|
After Width: | Height: | Size: 236 KiB |
18
client/images/connectionOff.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="280" height="280" viewBox="0 0 280 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_f_1379_19114)">
|
||||
<circle cx="140" cy="140" r="107.5" stroke="#FBB36A"/>
|
||||
</g>
|
||||
<circle cx="140" cy="140" r="107" stroke="#FBB36A" stroke-width="2"/>
|
||||
<circle cx="140" cy="140" r="107" stroke="url(#paint0_linear_1379_19114)" stroke-width="2"/>
|
||||
<defs>
|
||||
<filter id="filter0_f_1379_19114" x="2" y="2" width="276" height="276" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="15" result="effect1_foregroundBlur_1379_19114"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_1379_19114" x1="-2.43527" y1="89.3291" x2="192.652" y2="11.9798" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E0AA84"/>
|
||||
<stop offset="1" stop-color="#DF7D37"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 969 B |
17
client/images/connectionOn.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="280" height="280" viewBox="0 0 280 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_1379_19118)">
|
||||
<path d="M140 235C127.524 235 115.171 232.543 103.645 227.769C92.1191 222.994 81.6464 215.997 72.8248 207.175C64.0033 198.354 57.0056 187.881 52.2314 176.355C47.4572 164.829 45 152.476 45 140C45 127.524 47.4572 115.171 52.2314 103.645C57.0056 92.1191 64.0033 81.6464 72.8249 72.8248C81.6464 64.0033 92.1191 57.0056 103.645 52.2314C115.171 47.4572 127.524 45 140 45C152.476 45 164.829 47.4572 176.355 52.2314C187.881 57.0056 198.354 64.0033 207.175 72.8249C215.997 81.6464 222.994 92.1192 227.769 103.645C232.543 115.171 235 127.524 235 140C235 152.476 232.543 164.829 227.769 176.355C222.994 187.881 215.997 198.354 207.175 207.175C198.354 215.997 187.881 222.994 176.355 227.769C164.829 232.543 152.476 235 140 235L140 235Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_1379_19118" x="38" y="38" width="204" height="204" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="3"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1379_19118"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1379_19118" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
30
client/images/connectionProgress.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<svg width="280" height="280" viewBox="0 0 280 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.1" filter="url(#filter0_d_1379_19115)">
|
||||
<path d="M235 140C235 152.476 232.543 164.829 227.769 176.355C222.994 187.881 215.997 198.354 207.175 207.175C198.354 215.997 187.881 222.994 176.355 227.769C164.829 232.543 152.476 235 140 235C127.524 235 115.171 232.543 103.645 227.769C92.1191 222.994 81.6464 215.997 72.8249 207.175C64.0033 198.354 57.0056 187.881 52.2314 176.355C47.4572 164.829 45 152.476 45 140C45 127.524 47.4572 115.171 52.2314 103.645C57.0056 92.1191 64.0033 81.6464 72.8249 72.8248C81.6464 64.0033 92.1192 57.0056 103.645 52.2314C115.171 47.4572 127.524 45 140 45C152.476 45 164.829 47.4573 176.355 52.2314C187.881 57.0056 198.354 64.0033 207.175 72.8249C215.997 81.6464 222.994 92.1192 227.769 103.645C232.543 115.171 235 127.524 235 140L235 140Z" stroke="#FBB36A"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_1379_19115)">
|
||||
<path d="M140 235C126.016 235 112.204 231.913 99.551 225.959C86.8977 220.004 75.7151 211.33 66.8012 200.555C57.8874 189.78 51.4623 177.17 47.9846 163.626C44.5069 150.081 44.0623 135.935 46.6827 122.199C49.3031 108.462 54.9237 95.4738 63.1434 84.1604C71.363 72.847 81.979 63.4878 94.2334 56.7509C106.488 50.014 120.078 46.0655 134.035 45.1875C147.991 44.3094 161.97 46.5233 174.972 51.6712" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_1379_19115" x="38.5" y="38.5" width="203" height="203" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="3"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.984314 0 0 0 0 0.717647 0 0 0 0 0.317647 0 0 0 1 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1379_19115"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1379_19115" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d_1379_19115" x="38" y="38" width="143.973" height="204" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="3"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.988235 0 0 0 0 0.301961 0 0 0 0 0.0705883 0 0 0 0.49 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1379_19115"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1379_19115" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
3
client/images/controls/amnezia.svg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
6
client/images/controls/app.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 10C18.6569 10 20 8.65685 20 7C20 5.34315 18.6569 4 17 4C15.3431 4 14 5.34315 14 7C14 8.65685 15.3431 10 17 10Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="4" y="4" width="6" height="6" rx="1" stroke="#D7D8DB" stroke-width="2"/>
|
||||
<rect x="14" y="14" width="6" height="6" rx="1" stroke="#D7D8DB" stroke-width="2"/>
|
||||
<path d="M7.18963 13.8523L10.8078 20H2.91364L7.18963 13.8523Z" stroke="#D7D8DB" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 576 B |
4
client/images/controls/arrow-left.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 12H5" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 19L5 12L12 5" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
4
client/images/controls/arrow-right.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12H19" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 5L19 12L12 19" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
3
client/images/controls/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 6L9 17L4 12" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
3
client/images/controls/chevron-down.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9L12 15L18 9" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
3
client/images/controls/chevron-right.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 18L15 12L9 6" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
3
client/images/controls/chevron-up.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 15L12 9L6 15" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 213 B |
4
client/images/controls/copy.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 9H11C9.89543 9 9 9.89543 9 11V20C9 21.1046 9.89543 22 11 22H20C21.1046 22 22 21.1046 22 20V11C22 9.89543 21.1046 9 20 9Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 15H4C3.46957 15 2.96086 14.7893 2.58579 14.4142C2.21071 14.0391 2 13.5304 2 13V4C2 3.46957 2.21071 2.96086 2.58579 2.58579C2.96086 2.21071 3.46957 2 4 2H13C13.5304 2 14.0391 2.21071 14.4142 2.58579C14.7893 2.96086 15 3.46957 15 4V5" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 649 B |
5
client/images/controls/delete.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.332 5H9.33203L2.33203 12L9.33203 19H20.332C20.8625 19 21.3712 18.7893 21.7462 18.4142C22.1213 18.0391 22.332 17.5304 22.332 17V7C22.332 6.46957 22.1213 5.96086 21.7462 5.58579C21.3712 5.21071 20.8625 5 20.332 5V5Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.332 9L12.332 15" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.332 9L18.332 15" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 640 B |
5
client/images/controls/download.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 10L12 15L17 10" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 15V3" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 574 B |
4
client/images/controls/edit-3.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 20H21" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.5 3.49998C16.8978 3.10216 17.4374 2.87866 18 2.87866C18.2786 2.87866 18.5544 2.93353 18.8118 3.04014C19.0692 3.14674 19.303 3.303 19.5 3.49998C19.697 3.69697 19.8532 3.93082 19.9598 4.18819C20.0665 4.44556 20.1213 4.72141 20.1213 4.99998C20.1213 5.27856 20.0665 5.55441 19.9598 5.81178C19.8532 6.06915 19.697 6.303 19.5 6.49998L7 19L3 20L4 16L16.5 3.49998Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 660 B |
6
client/images/controls/eye-off.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.88 9.87988C9.58526 10.1545 9.34885 10.4857 9.18488 10.8537C9.02091 11.2217 8.93274 11.619 8.92564 12.0218C8.91853 12.4246 8.99263 12.8247 9.14351 13.1983C9.2944 13.5718 9.51898 13.9112 9.80385 14.196C10.0887 14.4809 10.4281 14.7055 10.8016 14.8564C11.1752 15.0073 11.5753 15.0814 11.9781 15.0742C12.3809 15.0671 12.7782 14.979 13.1462 14.815C13.5142 14.651 13.8454 14.4146 14.12 14.1199" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.73 5.08C11.1513 5.02751 11.5754 5.00079 12 5C19 5 22 12 22 12C21.5529 12.9571 20.9922 13.8569 20.33 14.68" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.61 6.61011C4.62125 7.96473 3.02987 9.82537 2 12.0001C2 12.0001 5 19.0001 12 19.0001C13.9159 19.0052 15.7908 18.4452 17.39 17.3901" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2 2L22 22" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
4
client/images/controls/eye.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 12C2 12 5 5 12 5C19 5 22 12 22 12C22 12 19 19 12 19C5 19 2 12 2 12Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
11
client/images/controls/file-cog-2.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.5 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V7.5L14.5 2Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 2V8H20" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 17C13.1046 17 14 16.1046 14 15C14 13.8954 13.1046 13 12 13C10.8954 13 10 13.8954 10 15C10 16.1046 10.8954 17 12 17Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 12V13" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 17V18" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.6 13.5L13.73 14" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.27 16L9.40002 16.5" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.6 16.5L13.73 16" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.27 14L9.40002 13.5" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
client/images/controls/folder-open.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 14L7.45 11.1C7.61696 10.7687 7.87281 10.4903 8.18893 10.296C8.50504 10.1018 8.86897 9.99927 9.24 10H20C20.3055 9.99946 20.6071 10.0689 20.8816 10.2031C21.1561 10.3372 21.3963 10.5325 21.5836 10.7739C21.7709 11.0152 21.9004 11.2963 21.9622 11.5956C22.024 11.8948 22.0164 12.2042 21.94 12.5L20.39 18.5C20.279 18.9299 20.0281 19.3106 19.6769 19.5822C19.3256 19.8538 18.894 20.0008 18.45 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V5C2 3.9 2.9 3 4 3H7.93C8.25941 3.0017 8.58331 3.08475 8.8729 3.24176C9.1625 3.39877 9.40882 3.62488 9.59 3.9L10.41 5.1C10.5912 5.37512 10.8375 5.60123 11.1271 5.75824C11.4167 5.91525 11.7406 5.9983 12.07 6H18C18.5304 6 19.0391 6.21071 19.4142 6.58579C19.7893 6.96086 20 7.46957 20 8V10" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 948 B |
4
client/images/controls/github.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 22V18C15.1392 16.7473 14.78 15.4901 14 14.5C17 14.5 20 12.5 20 9C20.08 7.75 19.73 6.52 19 5.5C19.28 4.35 19.28 3.15 19 2C19 2 18 2 16 3.5C13.36 3 10.64 3 8.00004 3.5C6.00004 2 5.00004 2 5.00004 2C4.70004 3.15 4.70004 4.35 5.00004 5.5C4.27191 6.51588 3.91851 7.75279 4.00004 9C4.00004 12.5 7.00004 14.5 10 14.5C9.61004 14.99 9.32004 15.55 9.15004 16.15C8.98004 16.75 8.93004 17.38 9.00004 18V22" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 18C4.49 20 4 16 2 16" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 711 B |
4
client/images/controls/home.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 22V12H15V22" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
4
client/images/controls/mail.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 4H4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H20C21.1046 20 22 19.1046 22 18V6C22 4.89543 21.1046 4 20 4Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22 7L13.03 12.7C12.7213 12.8934 12.3643 12.996 12 12.996C11.6357 12.996 11.2787 12.8934 10.97 12.7L2 7" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 514 B |
5
client/images/controls/more-vertical.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 733 B |
4
client/images/controls/plus.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 5V19" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 12H19" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 307 B |
14
client/images/controls/qr-code.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 3H4C3.44772 3 3 3.44772 3 4V7C3 7.55228 3.44772 8 4 8H7C7.55228 8 8 7.55228 8 7V4C8 3.44772 7.55228 3 7 3Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M20 3H17C16.4477 3 16 3.44772 16 4V7C16 7.55228 16.4477 8 17 8H20C20.5523 8 21 7.55228 21 7V4C21 3.44772 20.5523 3 20 3Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 16H4C3.44772 16 3 16.4477 3 17V20C3 20.5523 3.44772 21 4 21H7C7.55228 21 8 20.5523 8 20V17C8 16.4477 7.55228 16 7 16Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 16H18C17.4696 16 16.9609 16.2107 16.5858 16.5858C16.2107 16.9609 16 17.4696 16 18V21" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 21V21.01" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 7V10C12 10.5304 11.7893 11.0391 11.4142 11.4142C11.0391 11.7893 10.5304 12 10 12H7" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 12H3.01" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 3H12.01" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 16V16.01" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16 12H17" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 12V12.01" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 21V20" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
client/images/controls/radio-button-inner-circle-pressed.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
client/images/controls/radio-button-inner-circle.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
3
client/images/controls/radio-button-pressed.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="23" height="23" rx="11.5" stroke="#A85809"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 177 B |
3
client/images/controls/radio-button.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="23" height="23" rx="11.5" stroke="#878B91"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 177 B |
7
client/images/controls/radio.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.93006 19.0702C3.05535 17.1949 2.0022 14.6518 2.0022 12.0002C2.0022 9.34853 3.05535 6.80545 4.93006 4.93018" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.75993 16.24C7.20263 15.6818 6.76087 15.0191 6.45993 14.29C5.85157 12.8205 5.85157 11.1695 6.45993 9.7C6.76087 8.97087 7.20263 8.30823 7.75993 7.75" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.24 7.75977C16.8028 8.33271 17.2449 9.01281 17.54 9.75977C18.1483 11.2293 18.1483 12.8802 17.54 14.3498C17.2391 15.0789 16.7973 15.7415 16.24 16.2998" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.0701 4.93018C20.9448 6.80545 21.9979 9.34853 21.9979 12.0002C21.9979 14.6518 20.9448 17.1949 19.0701 19.0702" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 491 B |
|
Before Width: | Height: | Size: 624 B |
5
client/images/controls/save.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H16L21 8V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21Z" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17 21V13H7V21" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 3V8H15" stroke="#CBCBCB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 652 B |