Compare commits

...

161 Commits

Author SHA1 Message Date
lunardunno
a5d7a9514a Rollback some changes to dev 2023-12-14 21:09:34 +04:00
lunardunno
1bae323a1a Back to some changes install_docker.sh 2023-12-14 21:00:12 +04:00
lunardunno
a36eafa56c variable replacement
replacing the $check_pkgs variable
2023-12-01 03:05:53 +04:00
lunardunno
4fd3760a35 Merge pull request #447 from amnezia-vpn/test_support_archlinux
Test support archlinux
2023-12-01 02:13:00 +04:00
lunardunno
51c46bae12 Merge branch 'ArchLinux-support' into test_support_archlinux 2023-12-01 01:57:04 +04:00
lunardunno
112df79f94 rollback to old dev 2023-12-01 01:28:41 +04:00
lunardunno
2904bb96b9 small edit 2023-12-01 00:34:33 +04:00
lunardunno
669f5c21dd small edit 2023-12-01 00:27:13 +04:00
pokamest
dd233f77fc Merge pull request #432 from amnezia-vpn/feature/client-management
added client management
2023-11-29 07:04:56 -08:00
vladimir.kuznetsov
02efd9c217 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/client-management 2023-11-29 17:26:26 +03:00
pokamest
b897e7102e Merge pull request #437 from amnezia-vpn/bugfix/minor-ui-fixes
Bugfix/minor UI fixes
2023-11-29 05:34:30 -08:00
pokamest
1cc5c5384e Merge pull request #407 from amnezia-vpn/feature/ss-and-cloak-native-configs
added native config generation for ss and cloak
2023-11-29 05:28:33 -08:00
vladimir.kuznetsov
db602ac65b fixed ss string generation 2023-11-29 10:57:47 +07:00
vladimir.kuznetsov
2d22b52b5d limited client name length 2023-11-27 13:42:08 +07:00
vladimir.kuznetsov
426ac49f6f fixed "auto-connect" option 2023-11-27 12:46:59 +07:00
vladimir.kuznetsov
c164814abd fixed default server setting after importing 2023-11-27 10:59:48 +07:00
vladimir.kuznetsov
3084892ed8 added selection of default server and container on the sharing page 2023-11-26 21:15:58 +07:00
lunardunno
4fbacab643 updating script variables
adding variables to update the list of packages in repositories and to install packages
2023-11-25 21:57:06 +04:00
vladimir.kuznetsov
1bf808c9ee fixed qr code generation for native configs 2023-11-25 13:02:02 +07:00
vladimir.kuznetsov
5dc3b64e0b added search bar for client management page 2023-11-23 14:54:49 +07:00
vladimir.kuznetsov
e8ceeb6e20 added full access sharing 2023-11-23 00:04:06 +07:00
pokamest
d38c7ce6a5 Error codes cleanup 2023-11-22 13:57:05 +00:00
vladimir.kuznetsov
c6a312845a added client management 2023-11-21 20:31:53 +07:00
pokamest
ef0530ec6b Merge pull request #420 from amnezia-vpn/adaptation_to_different_os
Improve logic of install_docker.sh
2023-11-17 11:51:58 -08:00
pokamest
e8a2e54d05 Typo fix 2023-11-15 12:51:39 +00:00
pokamest
b4694313a0 Merge pull request #426 from amnezia-vpn/ios-build
Change Qt mirror for builds
2023-11-14 09:54:54 -08:00
tiaga
abb2cae1f8 Change Qt mirror for builds
Use UC Berkeley mirror for installing Qt during a build. In addition, don't trigger builds on a tag push.
2023-11-14 23:39:15 +07:00
pokamest
b0004fd9dc Version bump 2023-11-14 12:50:52 +00:00
tiaga
362a82f944 Improve logic of install_docker.sh
- check packages update only when it's required
- avoid `dnf/yum update` for RHEL-based systems
2023-11-14 16:57:16 +07:00
pokamest
19fe61ed29 Merge pull request #423 from amnezia-vpn/r2
Upload new versions to R2
2023-11-13 09:34:30 -08:00
tiaga
72de38b4fb Upload new versions to R2
A new GitHub Actions workflow for a tagged commit which uploads installers for a desktop version to Cloudflare R2.
2023-11-10 23:19:00 +07:00
pokamest
02c0f96e5e Merge pull request #418 from amnezia-vpn/feature/split-tunnel-dns-forwad
Use DNS over VPN for ForwardSites mode split tunnel
2023-11-07 06:35:43 -08:00
Mykola Baibuz
5e9f688000 Use DNS over VPN for ForwardSites mode split tunnel
This feature was in previous version of Split Tunnel
2023-11-04 15:28:59 -04:00
pokamest
6a7e346695 Merge pull request #416 from amnezia-vpn/bugfix/openvpn-exclude-route
Fix Split tunnel exclude sites mode for OpenVPN and Cloak. Windows.
2023-11-04 06:09:01 -07:00
Mykola Baibuz
071738116e Update Windows OpenVPN binary
This binary builded with ENABLE_DEBUG flag. This flag needed for ROUTE_GATEWAY varible output in log.
2023-11-03 17:29:40 -04:00
pokamest
ae4ee6431d Merge pull request #409 from useribs/patch-2
Update servercontroller.cpp, replace 2 calls (shred ; rm)  with one
2023-11-01 12:23:14 -07:00
vladimir.kuznetsov
9cfcb714ae added native config generation for ss and cloak 2023-11-01 21:29:58 +05:00
pokamest
d1ccde2a4b Merge pull request #396 from amnezia-vpn/bugfix/server-config-sync
bugfix/server config sync
2023-11-01 07:15:10 -07:00
useribs
4848091203 Update servercontroller.cpp, replace 2 calls (shred ; rm) with one (shred -u) 2023-10-30 20:09:13 +03:00
lunardunno
abb4678b0a feature/test_support_pacman2
adding pacman check to see if the server is busy
2023-10-30 17:14:46 +04:00
lunardunno
e609b22cf8 feature/test_support_pacman1
pacman was added as a test, assuming that all other components were pre-installed on the server.
2023-10-30 16:41:36 +04:00
pokamest
282f159311 Merge pull request #402 from amnezia-vpn/bugfix/description_dns
Update DNS description
2023-10-29 09:07:42 -07:00
lunardunno
4ef8c77a2d Update DNS description 2023-10-29 17:56:10 +04:00
pokamest
08c1cf2439 Merge pull request #382 from amnezia-vpn/feature/split-tunnel-mobile
Split tunnel for missed Protocol/OS
2023-10-29 06:26:07 -07:00
Mykola Baibuz
2fc33875bb Bump version 2023-10-27 15:38:24 -04:00
Mykola Baibuz
9e92e4b5ff Merge branch 'dev' into feature/split-tunnel-mobile 2023-10-27 15:37:28 -04:00
pokamest
7f2cf70bf5 Merge pull request #393 from amnezia-vpn/bugfix/return-after-installation
fixed page return after installation
2023-10-26 12:40:39 -07:00
vladimir.kuznetsov
8164026891 fixed server config update, after container config change 2023-10-26 23:37:51 +05:00
Mykola Baibuz
0e23b3a1ac Allow traffic to Amezia DNS for all OS 2023-10-25 22:19:07 +03:00
Mykola Baibuz
1739d4861e Allow acces to Amnezia DNS when used only for selected sites 2023-10-25 21:50:35 +03:00
Mykola Baibuz
a6b6e7850d Allow traffic for excluded route on Windows kill switch 2023-10-24 23:07:07 +03:00
Mykola Baibuz
3e9dea6f07 Remove some not implemented notification 2023-10-24 13:37:40 +03:00
Mykola Baibuz
1b37ca805f Cleanup debug stuff 2023-10-24 11:10:16 +03:00
Mykola Baibuz
c772f56da7 Fixes after merge 2023-10-24 11:00:40 +03:00
Mykola Baibuz
bc183e39bb Merge branch 'feature/split-tunnel-mobile' of github.com:amnezia-vpn/amnezia-client into feature/split-tunnel-mobile 2023-10-24 00:35:58 +03:00
Mykola Baibuz
306d4f70a8 Update NE Sources 2023-10-24 00:33:35 +03:00
Mykola Baibuz
a386d39495 iOS Cloak/OVPN SplitTunnel 2023-10-24 00:28:41 +03:00
Mykola Baibuz
22b14dff5f iOS AWG/WG split tunnel 2023-10-23 22:42:02 +03:00
pokamest
e749cc7578 Update amneziavpn_ru.ts
Typo fix
2023-10-23 20:32:28 +01:00
vladimir.kuznetsov
6a12cad1c9 fixed page return after installation 2023-10-23 21:33:07 +05:00
Mykola Baibuz
c15665803d Merge branch 'dev' into feature/split-tunnel-mobile 2023-10-22 15:26:20 -04:00
pokamest
97090888d5 Bump version 2023-10-22 08:11:37 -07:00
pokamest
4642308fbb Merge pull request #374 from amnezia-vpn/bugfix/split-tunneling
Bugfix/split tunneling
2023-10-22 08:02:43 -07:00
vladimir.kuznetsov
59bccb1188 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into bugfix/split-tunneling 2023-10-22 20:00:39 +05:00
pokamest
cd8fc007ac Merge pull request #392 from amnezia-vpn/bugfix/existing-awg-container
added getting awg parameters when adding an already installed awg container
2023-10-22 07:49:34 -07:00
vladimir.kuznetsov
7cfb38307e removed re-processing of server config for awg 2023-10-22 18:04:34 +05:00
vladimir.kuznetsov
994aa32745 added getting awg parameters when adding an already installed awg container 2023-10-22 17:31:13 +05:00
Mykola Baibuz
f0b872e86b Merge remote-tracking branch 'origin/bugfix/pull-awg-config' into feature/split-tunnel-mobile 2023-10-21 23:24:54 +03:00
Mykola Baibuz
0c33432436 Fix pulling exiting AWG config from server 2023-10-21 14:55:15 -04:00
pokamest
0bb4dd9442 Text and translations fixes 2023-10-21 18:32:30 +01:00
pokamest
7a54dc15da Update amneziavpn_ru.ts 2023-10-21 16:33:21 +01:00
pokamest
e16a1100d8 Update amneziavpn_ru.ts 2023-10-21 16:20:57 +01:00
pokamest
99214e22e3 Fix docs url 2023-10-21 16:05:09 +01:00
pokamest
c77d35a2ed Merge pull request #390 from amnezia-vpn/revert-370-feature/custom_drawer_component
Revert "added new drawer2type for replacing drawertype"
2023-10-21 06:21:07 -07:00
pokamest
d98fdbdc5c Revert "added new drawer2type for replacing drawertype" 2023-10-21 14:17:45 +01:00
ronoaer
4551cf0a21 Merge pull request #370 from amnezia-vpn/feature/custom_drawer_component
added new drawer2type for replacing drawertype
2023-10-21 09:34:21 +08:00
ronoaer
023c3474d2 Merge branch 'dev' into feature/custom_drawer_component 2023-10-21 09:28:41 +08:00
pokamest
2a4cefb4bf Merge pull request #387 from amnezia-vpn/bugfix/awg-mtu-len-fix
Fix MTU len for Win WG/AWG
2023-10-20 15:02:29 -07:00
Mykola Baibuz
09305724fa Fix MTU len for Win WG/AWG 2023-10-20 16:44:30 -04:00
pokamest
360fda1ba7 Merge pull request #386 from amnezia-vpn/bugfix/minor-ui-fixes-4-version
Bugfix/minor UI fixes 4 version
2023-10-20 12:23:03 -07:00
vladimir.kuznetsov
dadf0cf96e Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into dev 2023-10-20 21:51:40 +05:00
vladimir.kuznetsov
3d60ac751e removed the default protocol/server change if connected to VPN 2023-10-20 20:52:14 +05:00
pokamest
32793eef8c Merge pull request #385 from amnezia-vpn/bugfix/translated_new_source_strings_to_chinese
translated new source strings to chinese
2023-10-20 06:36:17 -07:00
ronoaer
da1cdfd6fa translated new source strings to chinese 2023-10-20 18:01:57 +08:00
vladimir.kuznetsov
58ad7dc161 removed the "remove protocol" buttons from where they shouldn't be 2023-10-20 14:10:04 +05:00
ronoaer
0a15f44193 removed states 'opened', 'closed' 2023-10-20 10:38:12 +08:00
pokamest
e1dec3c1ba Merge pull request #384 from amnezia-vpn/bugfix/startCentos7docker
Restoring autostart and enable docker for CentOS 7
2023-10-19 18:30:45 -07:00
pokamest
7834860245 Merge pull request #383 from amnezia-vpn/feature/awg-random-values
Feature/awg random values
2023-10-19 18:28:13 -07:00
pokamest
2da1025f26 Random port on install 2023-10-20 02:25:40 +01:00
Mykola Baibuz
78c83b2e21 Some logic fix 2023-10-19 17:03:24 -04:00
Mykola Baibuz
414a47e2f2 WG/AWG Desktop AllowedIP from plain WG config 2023-10-19 14:50:51 -04:00
ronoaer
6c78b4ec8f enabled drag-pagehome-drawer in tabBar 2023-10-19 23:01:03 +08:00
ronoaer
a6949bd3ae resized questiondrawer of page serverdata 2023-10-19 19:45:22 +08:00
ronoaer
f7bed04ab2 removed invalid function code 2023-10-19 19:32:15 +08:00
ronoaer
6ec773079c added hovering effect of button 2023-10-19 11:22:52 +08:00
ronoaer
366e27a321 re-adatped pagehome 2023-10-19 09:27:39 +08:00
Mykola Baibuz
32c304dc1b WG/AWG SplitTunnel for desktop 2023-10-18 17:44:28 -04:00
vladimir.kuznetsov
338499247d changed the display order of containers 2023-10-19 01:16:36 +05:00
vladimir.kuznetsov
79e1761c1f added generation of random values for awg parameters 2023-10-19 01:14:09 +05:00
Mykola Baibuz
4ea1a19572 Cleanup WG implementation 2023-10-18 13:41:58 -04:00
pokamest
e2ae341ba9 AndroidManifest fix 2023-10-18 14:01:06 +01:00
pokamest
de03435bac Merge pull request #381 from amnezia-vpn/bugfix/minor-ui-fixes-4-version
Bugfix/minor UI fixes 4 version
2023-10-18 04:05:18 -07:00
pokamest
e16c425f87 PageHome.qml fix 2023-10-18 12:04:39 +01:00
ronoaer
c461e00c5c keeping parent's cusorshape and Drawer2Type's close-animation 2023-10-18 16:17:57 +08:00
vladimir.kuznetsov
fcf6bb43b7 Merge branch 'bugfix/split-tunneling' of github.com:amnezia-vpn/amnezia-client into bugfix/split-tunneling 2023-10-18 12:18:46 +05:00
vladimir.kuznetsov
f5f72f87a6 fixed switcher status display for page split site tunneling 2023-10-18 12:17:24 +05:00
vladimir.kuznetsov
3340451245 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into bugfix/split-tunneling 2023-10-18 11:55:24 +05:00
Mykola Baibuz
c14f1b5000 Android OpenVPN/Cloak Split tunnel 2023-10-17 16:39:56 -04:00
vladimir.kuznetsov
a46e55d5c2 added a dash for drawerType 2023-10-18 01:11:41 +05:00
vladimir.kuznetsov
4b64bfaec0 fixed questionDrawer height 2023-10-18 00:37:15 +05:00
vladimir.kuznetsov
2f0c1eeecc fixed selection of default container after installing a new server 2023-10-18 00:36:40 +05:00
Mykola Baibuz
546d4c1d3d WG/AWG Android splitTunnel 2023-10-17 14:54:46 -04:00
lunardunno
160d88f002 Restoring autostart and enable docker for CentOS 7 2023-10-17 21:26:50 +04:00
ronoaer
a83cd29f72 fixed the cursorShape, and some minor issues 2023-10-17 22:00:19 +08:00
pokamest
94304b5777 Version bump 2023-10-17 14:47:31 +01:00
pokamest
61ddfe01a1 macos build script updated [no ci] 2023-10-17 06:39:49 -07:00
pokamest
00d334f704 Merge pull request #377 from amnezia-vpn/bugfix/minor-ui-fixes-4-version
disabled the ability to change the protocol/server when a vpn connection is active
2023-10-17 05:39:51 -07:00
pokamest
f4a4979997 Merge pull request #378 from amnezia-vpn/bugfix/updated_chinese_translations
updated Chinese translations for updating source strings
2023-10-17 05:11:19 -07:00
ronoaer
03171e4743 update background color and drag-effect, moved dulicated code 2023-10-17 19:34:34 +08:00
ronoaer
5369e68267 updated Chinese translations for updating source strings 2023-10-17 14:30:59 +08:00
vladimir.kuznetsov
9eb23e38bd disabled the ability to change the protocol/server when a vpn connection is active 2023-10-16 22:57:12 +05:00
Mykola Baibuz
2a0166bb26 Merge branch 'bugfix/split-tunneling' of https://github.com/amnezia-vpn/amnezia-client into bugfix/split-tunneling 2023-10-16 12:05:50 -04:00
Mykola Baibuz
2df612ec1f Android SplitTunnel 2023-10-16 12:05:35 -04:00
pokamest
36ba3758db Translation updates 2023-10-16 15:27:26 +01:00
ronoaer
7cc0f39d3c adapted pagehome by new custom drawer type 2023-10-16 22:21:01 +08:00
vladimir.kuznetsov
9cf5590371 disabled split site tunneling for awg 2023-10-16 15:17:09 +05:00
pokamest
81f835458f Merge pull request #375 from amnezia-vpn/bugfix/minor-ui-fixes-4-version
Bugfix/minor UI fixes 4 version
2023-10-16 03:10:53 -07:00
vladimir.kuznetsov
e01b1db706 text corrections 2023-10-16 14:34:03 +05:00
vladimir.kuznetsov
cdb18de305 brought back the ability to share wireguard native format configs 2023-10-16 13:43:27 +05:00
vladimir.kuznetsov
8e0eef3316 fixed selection of default container after installing a new container 2023-10-16 13:40:43 +05:00
vladimir.kuznetsov
221d45f564 fixed pageSettingsDns width 2023-10-16 13:32:56 +05:00
vladimir.kuznetsov
2a4a01a4be removed split site tunneling page blocking when switcher is turned off 2023-10-16 13:28:37 +05:00
Mykola Baibuz
501670bdd2 Merge branch 'feature/split-tunneling-config' into bugfix/split-tunneling 2023-10-15 15:10:05 -04:00
vladimir.kuznetsov
24637a1693 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2023-10-15 21:08:45 +05:00
vladimir.kuznetsov
7bd1340190 fixed display of sites on page split tunneling 2023-10-15 20:41:49 +05:00
ronoaer
cb5c09d967 adapted questionDrawer 2023-10-15 21:29:01 +08:00
pokamest
a01ba5909c Version bump 2023-10-15 12:53:44 +01:00
pokamest
1c4678af95 Merge pull request #373 from amnezia-vpn/fix/update_ru_ts
Updated ru ts #2
2023-10-15 04:43:04 -07:00
pokamest
5f5435c645 Updated ru ts 2023-10-15 12:41:01 +01:00
pokamest
2f7dc2c46c Merge pull request #372 from amnezia-vpn/fix/update_ru_ts
Update ru translation
2023-10-15 04:38:29 -07:00
pokamest
9bc1c9dd03 Merge branch 'dev' into fix/update_ru_ts 2023-10-15 12:30:13 +01:00
pokamest
4c81cdb4a2 Update translation 2023-10-15 12:29:41 +01:00
pokamest
3406ffa7a2 Merge pull request #371 from amnezia-vpn/bugfix/minor-ui-fixes-4-version
Minor UI fixes 4 version
2023-10-15 02:54:27 -07:00
pokamest
6d05b6845e VPN protocol descriptions updated 2023-10-15 10:53:09 +01:00
ronoaer
29b4966119 shown ConnectionTypeSelectionDrawer on top level alway 2023-10-15 17:34:35 +08:00
ronoaer
d0f8358431 removed invalid code, and fixed top button hidden-shown 2023-10-15 17:29:22 +08:00
ronoaer
a75bd07cd8 fixed the clicked event 2023-10-15 15:54:05 +08:00
ronoaer
8c1835950b added transparent-background, for blocking clicked event 2023-10-15 15:17:04 +08:00
pokamest
c7cd8e4c80 Minor ui fixes and refactoring 2023-10-15 02:30:42 +01:00
pokamest
f65e4066e3 ui fixes 2023-10-14 23:59:46 +01:00
vladimir.kuznetsov
37c18c5d3c Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into dev 2023-10-14 21:25:09 +05:00
vladimir.kuznetsov
8885f580b2 added notification after saving backup and logs files 2023-10-14 21:18:38 +05:00
vladimir.kuznetsov
512ac74ee6 temporarily disabled the drawer close button 2023-10-14 20:59:03 +05:00
ronoaer
384ce9853b added new drawer2type for replacing drawertype 2023-10-14 23:00:31 +08:00
pokamest
b6d2030041 Ru translation 2023-10-14 15:55:52 +01:00
pokamest
3ac09181c6 Text lables fixes 2023-10-14 15:55:07 +01:00
vladimir.kuznetsov
ffc9e5823a text corrections 2023-10-14 18:21:49 +05:00
vladimir.kuznetsov
f5448fed59 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into dev 2023-10-14 16:58:14 +05:00
vladimir.kuznetsov
8163e51434 fixes on page split tunneling according to the design layout 2023-10-14 16:52:22 +05:00
pokamest
3836836c72 Change easySetupOrder 2023-10-14 02:15:49 +01:00
vladimir.kuznetsov
a4624c7377 removed the close button for the app for mobile platforms 2023-10-12 10:57:13 +05:00
98 changed files with 4397 additions and 1806 deletions

View File

@@ -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
View 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 }}

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.0.8.1
project(${PROJECT} VERSION 4.1.0.1
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)

View File

@@ -74,7 +74,6 @@ 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()

View File

@@ -139,7 +139,8 @@ void AmneziaApplication::init()
&ConnectionController::openConnection);
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
&NotificationHandler::onTranslationsUpdated);
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
@@ -226,14 +227,13 @@ void AmneziaApplication::loadTranslator()
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";
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
@@ -279,13 +279,17 @@ 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(),
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, 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());
@@ -294,15 +298,7 @@ void AmneziaApplication::initModels()
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() {
if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard
&& m_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) {
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
emit m_pageController->showNotificationMessage(
tr("Split tunneling for WireGuard is not implemented, the option was disabled"));
}
});
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
@@ -328,6 +324,11 @@ void AmneziaApplication::initModels()
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, m_clientManagementModel.get(),
&ClientManagementModel::appendClient);
}
void AmneziaApplication::initControllers()
@@ -335,7 +336,8 @@ 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);
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());
@@ -352,12 +354,12 @@ void AmneziaApplication::initControllers()
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_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, 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) {
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}

View File

@@ -39,6 +39,7 @@
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@@ -94,6 +95,7 @@ private:
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;

View File

@@ -45,6 +45,7 @@
android:label="-- %%INSERT_APP_NAME%% --"
android:screenOrientation="unspecified"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<!-- android:theme="@style/splashScreenTheme"-->

View File

@@ -138,8 +138,8 @@ android {
resConfig "en"
minSdkVersion = 24
targetSdkVersion = 34
versionCode 36 // Change to a higher number
versionName "4.0.8" // 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()

View 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;
}
}

View 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();
}
}

View 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);
}
}

View File

@@ -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()
}

View File

@@ -564,6 +564,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
return parseData
}
/**
* Create a Wireguard [Config] from a [json] string -
* The [json] will be created in AndroidVpnProtocol.cpp
@@ -571,29 +572,67 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private fun buildWireguardConfig(obj: JSONObject, type: String): Config {
val confBuilder = Config.Builder()
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()
@@ -603,7 +642,7 @@ 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"])
@@ -624,14 +663,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
ifaceBuilder.parseH1("0")
ifaceBuilder.parseH2("0")
ifaceBuilder.parseH3("0")
ifaceBuilder.parseH4("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()
@@ -746,13 +784,13 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private fun startWireGuard(type: String) {
val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data")
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
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")

View File

@@ -10,50 +10,37 @@ AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *pa
{
}
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials,
DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode);
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
QString awgConfig = jsonConfig.value(config_key::config).toString();
ServerController serverController(m_settings);
QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, errorCode);
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
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) {
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
config.replace("$JUNK_PACKET_COUNT", serverConfigMap.value(config_key::junkPacketCount));
config.replace("$JUNK_PACKET_MIN_SIZE", serverConfigMap.value(config_key::junkPacketMinSize));
config.replace("$JUNK_PACKET_MAX_SIZE", serverConfigMap.value(config_key::junkPacketMaxSize));
config.replace("$INIT_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::initPacketJunkSize));
config.replace("$RESPONSE_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::responsePacketJunkSize));
config.replace("$INIT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::initPacketMagicHeader));
config.replace("$RESPONSE_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::responsePacketMagicHeader));
config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::underloadPacketMagicHeader));
config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::transportPacketMagicHeader));
jsonConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
jsonConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
jsonConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
jsonConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
jsonConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
jsonConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
jsonConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
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();
}

View File

@@ -12,7 +12,7 @@ public:
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
};
#endif // AWGCONFIGURATOR_H

View File

@@ -83,7 +83,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
}
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{
ServerController serverController(m_settings);
QString config =
@@ -113,6 +113,8 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
QJsonObject jConfig;
jConfig[config_key::config] = config;
clientId = connData.clientId;
return QJsonDocument(jConfig).toJson();
}
@@ -131,10 +133,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");

View File

@@ -24,7 +24,7 @@ public:
};
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig);
QString processConfigWithExportSettings(QString jsonConfig);

View File

@@ -28,11 +28,11 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *pa
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode)
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
{
switch (proto) {
case Proto::OpenVpn:
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, errorCode);
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::ShadowSocks:
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
@@ -40,10 +40,10 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
case Proto::WireGuard:
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode);
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Awg:
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode);
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);

View File

@@ -6,7 +6,6 @@
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator;
class ShadowSocksConfigurator;
class CloakConfigurator;
@@ -16,14 +15,15 @@ class SshConfigurator;
class AwgConfigurator;
// Retrieve connection settings from server
class VpnConfigurator : ConfiguratorBase
class VpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
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);
const QJsonObject &containerConfig, Proto proto, QString &clientId,
ErrorCode *errorCode = nullptr);
QPair<QString, QString> getDnsForConfig(int serverIndex);
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
@@ -32,8 +32,8 @@ public:
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
// workaround for containers which is not support normal configuration
void updateContainerConfigAfterInstallation(DockerContainer container,
QJsonObject &containerConfig, const QString &stdOut);
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut);
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
@@ -42,6 +42,10 @@ public:
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
std::shared_ptr<SshConfigurator> sshConfigurator;
std::shared_ptr<AwgConfigurator> awgConfigurator;
signals:
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials);
};
#endif // VPN_CONFIGURATOR_H

View File

@@ -177,7 +177,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode)
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{
ServerController serverController(m_settings);
QString scriptData = amnezia::scriptData(m_configTemplate, container);
@@ -205,6 +205,8 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey;
clientId = connData.clientPubKey;
return QJsonDocument(jConfig).toJson();
}

View File

@@ -26,7 +26,7 @@ public:
};
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(QString config);

View File

@@ -89,7 +89,6 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
//{DockerContainer::FileShare, QObject::tr("SMB file sharing service")},
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } };
}
@@ -108,9 +107,10 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ 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::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.") },
@@ -118,44 +118,108 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ 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::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")},
{ 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 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::WireGuard, QObject::tr("AmneziaWG container") },
{ DockerContainer::Ipsec, QObject::tr("IPsec container") },
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::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("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::Awg: 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)
@@ -171,7 +235,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
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;
}
@@ -233,8 +296,8 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::OpenVpn: return true;
default: return false;
}
}
@@ -243,8 +306,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return tr("Low");
case DockerContainer::Cloak: return tr("High");
case DockerContainer::OpenVpn: return tr("Medium");
case DockerContainer::Awg: return tr("Medium or High");
case DockerContainer::Cloak: return tr("Extreme");
default: return "";
}
}
@@ -252,9 +315,10 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return tr("I just want to increase the level of privacy");
case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked");
case DockerContainer::OpenVpn: return tr("Some foreign sites are blocked, but VPN providers are not blocked");
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 "";
}
}
@@ -262,9 +326,9 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return 1;
case DockerContainer::Cloak: return 3;
case DockerContainer::OpenVpn: return 2;
case DockerContainer::WireGuard: return 3;
case DockerContainer::Awg: return 2;
case DockerContainer::Cloak: return 1;
default: return 0;
}
}

View File

@@ -16,17 +16,16 @@ namespace amnezia
Q_NAMESPACE
enum DockerContainer {
None = 0,
OpenVpn,
ShadowSocks,
Cloak,
WireGuard,
Awg,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
// non-vpn
TorWebSite,
Dns,
// FileShare,
Sftp
};
Q_ENUM_NS(DockerContainer)

View File

@@ -36,7 +36,7 @@ enum ErrorCode
ServerPacketManagerError,
// Ssh connection errors
SshRequsetDeniedError, SshInterruptedError, SshInternalError,
SshRequestDeniedError, SshInterruptedError, SshInternalError,
SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
// Ssh sftp errors
@@ -47,7 +47,6 @@ enum ErrorCode
SshSftpNoMediaError,
// Local errors
FailedToSaveConfigData,
OpenVpnConfigMissing,
OpenVpnManagementServerError,
ConfigMissing,
@@ -67,7 +66,6 @@ enum ErrorCode
// 3rd party utils errors
OpenSslFailed,
OpenVpnExecutableCrashed,
ShadowSocksExecutableCrashed,
CloakExecutableCrashed,

View File

@@ -19,7 +19,7 @@ QString errorString(ErrorCode code){
case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo");
// Libssh errors
case(SshRequsetDeniedError): return QObject::tr("Ssh request was denied");
case(SshRequestDeniedError): return QObject::tr("Ssh request was denied");
case(SshInterruptedError): return QObject::tr("Ssh request was interrupted");
case(SshInternalError): return QObject::tr("Ssh internal error");
case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
@@ -42,7 +42,6 @@ QString errorString(ErrorCode code){
case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive");
// Local errors
case (FailedToSaveConfigData): return QObject::tr("Failed to save config to disk");
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");

View File

@@ -16,7 +16,6 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
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 "";
}

View File

@@ -167,11 +167,8 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer 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;
}
@@ -337,7 +334,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
!= newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort))
return true;
}
if (container == DockerContainer::Awg) {
return true;
}
@@ -490,8 +487,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
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 &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
Vars vars;
@@ -591,33 +587,21 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
// 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(protocols::awg::defaultJunkPacketCount) } });
vars.append({ { "$JUNK_PACKET_MIN_SIZE",
amneziaWireguarConfig.value(config_key::junkPacketMinSize)
.toString(protocols::awg::defaultJunkPacketMinSize) } });
vars.append({ { "$JUNK_PACKET_MAX_SIZE",
amneziaWireguarConfig.value(config_key::junkPacketMaxSize)
.toString(protocols::awg::defaultJunkPacketMaxSize) } });
vars.append({ { "$INIT_PACKET_JUNK_SIZE",
amneziaWireguarConfig.value(config_key::initPacketJunkSize)
.toString(protocols::awg::defaultInitPacketJunkSize) } });
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(protocols::awg::defaultResponsePacketJunkSize) } });
amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
vars.append({ { "$INIT_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::initPacketMagicHeader)
.toString(protocols::awg::defaultInitPacketMagicHeader) } });
amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::responsePacketMagicHeader)
.toString(protocols::awg::defaultResponsePacketMagicHeader) } });
amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader)
.toString(protocols::awg::defaultUnderloadPacketMagicHeader) } });
amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader)
.toString(protocols::awg::defaultTransportPacketMagicHeader) } });
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
QString serverIp = Utils::getIPAddress(credentials.hostName);
if (!serverIp.isEmpty()) {
@@ -847,6 +831,34 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential
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);

View File

@@ -333,7 +333,7 @@ namespace libssh {
switch (errorCode) {
case(SSH_NO_ERROR): return ErrorCode::NoError;
case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError;
case(SSH_REQUEST_DENIED): return ErrorCode::SshRequestDeniedError;
case(SSH_EINTR): return ErrorCode::SshInterruptedError;
case(SSH_FATAL): return ErrorCode::SshInternalError;
default: return ErrorCode::SshInternalError;

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="x, &#195;&#151;, close">
<path id="Vector" d="M18 6L6 18" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M6 6L18 18" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 374 B

View File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="search">
<path id="Vector" d="M11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M21.0004 20.9984L16.6504 16.6484" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -115,8 +115,12 @@ void LocalSocketController::daemonConnected() {
}
void LocalSocketController::activate(const QJsonObject &rawConfig) {
QString protocolName = rawConfig.value("protocol").toString();
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
QJsonObject json;
@@ -137,23 +141,79 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
QJsonArray jsAllowedIPAddesses;
QJsonObject range_ipv4;
range_ipv4.insert("address", "0.0.0.0");
range_ipv4.insert("range", 0);
range_ipv4.insert("isIpv6", false);
jsAllowedIPAddesses.append(range_ipv4);
QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).toArray();
QJsonArray defaultAllowedIP = QJsonArray::fromStringList(QString("0.0.0.0/0, ::/0").split(","));
QJsonObject range_ipv6;
range_ipv6.insert("address", "::");
range_ipv6.insert("range", 0);
range_ipv6.insert("isIpv6", true);
jsAllowedIPAddesses.append(range_ipv6);
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
// Use AllowedIP list from WG config bacouse of higer priority
for (auto v : plainAllowedIP) {
QString ipRange = v.toString();
qDebug() << "ipRange " << ipRange;
if (ipRange.split('/').size() > 1){
QJsonObject range;
range.insert("address", ipRange.split('/')[0]);
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
range.insert("isIpv6", false);
jsAllowedIPAddesses.append(range);
} else {
QJsonObject range;
range.insert("address",ipRange);
range.insert("range", 32);
range.insert("isIpv6", false);
jsAllowedIPAddesses.append(range);
}
}
} else {
// Use APP split tunnel
if (splitTunnelType == 0 || splitTunnelType == 2) {
QJsonObject range_ipv4;
range_ipv4.insert("address", "0.0.0.0");
range_ipv4.insert("range", 0);
range_ipv4.insert("isIpv6", false);
jsAllowedIPAddesses.append(range_ipv4);
QJsonObject range_ipv6;
range_ipv6.insert("address", "::");
range_ipv6.insert("range", 0);
range_ipv6.insert("isIpv6", true);
jsAllowedIPAddesses.append(range_ipv6);
}
if (splitTunnelType == 1) {
for (auto v : splitTunnelSites) {
QString ipRange = v.toString();
qDebug() << "ipRange " << ipRange;
if (ipRange.split('/').size() > 1){
QJsonObject range;
range.insert("address", ipRange.split('/')[0]);
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
range.insert("isIpv6", false);
jsAllowedIPAddesses.append(range);
} else {
QJsonObject range;
range.insert("address",ipRange);
range.insert("range", 32);
range.insert("isIpv6", false);
jsAllowedIPAddesses.append(range);
}
}
}
}
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
QJsonArray jsExcludedAddresses;
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
if (splitTunnelType == 2) {
for (auto v : splitTunnelSites) {
QString ipRange = v.toString();
jsExcludedAddresses.append(ipRange);
}
}
json.insert("excludedAddresses", jsExcludedAddresses);

View File

@@ -28,6 +28,8 @@ struct MessageKey
static const char *host;
static const char *port;
static const char *isOnDemand;
static const char *SplitTunnelType;
static const char *SplitTunnelSites;
};
class IosController : public QObject

View File

@@ -29,6 +29,9 @@ const char* MessageKey::errorCode = "errorCode";
const char* MessageKey::host = "host";
const char* MessageKey::port = "port";
const char* MessageKey::isOnDemand = "is-on-demand";
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
switch (status) {
@@ -351,6 +354,13 @@ void IosController::startTunnel()
{
m_rxBytes = 0;
m_txBytes = 0;
int STT = m_rawConfig["splitTunnelType"].toInt();
QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray();
QJsonDocument doc;
doc.setArray(splitTunnelSites);
QString STS(doc.toJson());
[m_currentTunnel setEnabled:YES];
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
@@ -376,8 +386,15 @@ void IosController::startTunnel()
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType];
NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT];
NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites];
NSString *SplitTunnelSitesValue = STS.toNSString();
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue,
SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue};
sendVpnExtensionMessage(message);

View File

@@ -15,7 +15,7 @@ struct Constants {
static let ovpnConfigKey = "ovpn"
static let wireGuardConfigKey = "wireguard"
static let loggerTag = "NET"
static let kActionStart = "start"
static let kActionRestart = "restart"
static let kActionStop = "stop"
@@ -29,6 +29,8 @@ struct Constants {
static let kMessageKeyHost = "host"
static let kMessageKeyPort = "port"
static let kMessageKeyOnDemand = "is-on-demand"
static let kMessageKeySplitTunnelType = "SplitTunnelType"
static let kMessageKeySplitTunnelSites = "SplitTunnelSites"
}
class PacketTunnelProvider: NEPacketTunnelProvider {
@@ -38,7 +40,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
wg_log(logLevel.osLogLevel, message: message)
}
}()
private lazy var ovpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
@@ -49,9 +51,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
private var openVPNConfig: Data? = nil
private var SplitTunnelType: String? = nil
private var SplitTunnelSites: String? = nil
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var protoType: TunnelProtoType = .none
@@ -63,26 +67,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
let tmpStr = String(data: messageData, encoding: .utf8)!
wg_log(.error, message: tmpStr)
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
Logger.global?.log(message: "Failed to serialize message from app")
return
}
guard let completionHandler = completionHandler else {
Logger.global?.log(message: "Missing message completion handler")
return
}
guard let action = message[Constants.kMessageKeyAction] as? String else {
Logger.global?.log(message: "Missing action key in app message")
completionHandler(nil)
return
}
if action == Constants.kActionStatus {
handleStatusAppMessage(messageData, completionHandler: completionHandler)
}
if action == Constants.kActionStart {
SplitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
SplitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
}
let callbackWrapper: (NSNumber?) -> Void = { errorCode in
//let tunnelId = self.tunnelConfig?.id ?? ""
let response: [String: Any] = [
@@ -90,11 +102,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
Constants.kMessageKeyTunnelId: 0
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
}
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
dispatchQueue.async {
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
@@ -118,8 +130,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
switch self.protoType {
case .wireguard:
self.startWireguard(activationAttemptId: activationAttemptId,
errorNotifier: errorNotifier,
completionHandler: completionHandler)
errorNotifier: errorNotifier,
completionHandler: completionHandler)
case .openvpn:
self.startOpenVPN(completionHandler: completionHandler)
case .shadowsocks:
@@ -156,7 +168,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
case .shadowsocks:
break
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
case .none:
break
@@ -168,12 +180,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
errorNotifier: ErrorNotifier,
completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start WireGuard config missing")
completionHandler(nil)
return
}
let providerConfiguration = protocolConfiguration.providerConfiguration,
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start WireGuard config missing")
completionHandler(nil)
return
}
let wgConfigStr = String(data: wgConfig, encoding: .utf8)!
@@ -182,7 +195,49 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler(nil)
return
}
if (tunnelConfiguration.peers.first!.allowedIPs.map { $0.stringRepresentation }.joined(separator: ", ") == "0.0.0.0/0, ::/0") {
if (SplitTunnelType == "1") {
for index in tunnelConfiguration.peers.indices {
tunnelConfiguration.peers[index].allowedIPs.removeAll()
var allowedIPs = [IPAddressRange]()
let STSdata = Data(SplitTunnelSites!.utf8)
do {
let STSArray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
for allowedIPString in STSArray {
if let allowedIP = IPAddressRange(from: allowedIPString) {
allowedIPs.append(allowedIP)
}
}
} catch {
wg_log(.error,message: "Parse JSONSerialization Error")
}
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
}
} else {
if (SplitTunnelType == "2")
{
for index in tunnelConfiguration.peers.indices {
var excludeIPs = [IPAddressRange]()
let STSdata = Data(SplitTunnelSites!.utf8)
do {
let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
for excludeIPString in STSarray {
if let excludeIP = IPAddressRange(from: excludeIPString) {
excludeIPs.append(excludeIP)
}
}
} catch {
wg_log(.error,message: "Parse JSONSerialization Error")
}
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
}
}
}
}
wg_log(.info, message: "Starting wireguard tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
// Start the tunnel
@@ -193,30 +248,30 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler(nil)
return
}
switch adapterError {
case .cannotLocateTunnelFileDescriptor:
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
case .dnsResolution(let dnsErrors):
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
.joined(separator: ", ")
wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
case .setNetworkSettings(let error):
wg_log(.error, message: "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
case .startWireGuardBackend(let errorCode):
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
case .invalidState:
// Must never happen
fatalError()
@@ -226,27 +281,27 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
// TODO: handle errors properly
wg_log(.error, message: "Can't start startOpenVPN()")
wg_log(.error, message: "Can't start startOpenVPN()")
return
}
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
}
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, staticMessage: "Stopping tunnel")
wgAdapter.stop { error in
ErrorNotifier.removeLastErrorFile()
if let error = error {
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
}
completionHandler()
#if os(macOS)
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
// Remove it when they finally fix this upstream and the fix has been rolled out to
@@ -263,7 +318,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
ovpnAdapter.disconnect()
}
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
wgAdapter.getRuntimeConfiguration { settings in
@@ -278,8 +333,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
for component in components{
let pair = component.components(separatedBy: "=")
if pair.count == 2 {
settingsDictionary[pair[0]] = pair[1]
}
settingsDictionary[pair[0]] = pair[1]
}
}
let response: [String: Any] = [
@@ -309,7 +364,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler(nil)
return
}
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
@@ -318,7 +373,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler(nil)
return
}
self.wgAdapter.getRuntimeConfiguration { settings in
var data: Data?
if let settings = settings {
@@ -334,76 +389,76 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler(nil)
}
}
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter.transportStatistics.bytesIn
let bytesout = ovpnAdapter.transportStatistics.bytesOut
let response: [String: Any] = [
"rx_bytes" : bytesin,
"tx_bytes" : bytesout
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
let bytesin = ovpnAdapter.transportStatistics.bytesIn
let bytesout = ovpnAdapter.transportStatistics.bytesOut
let response: [String: Any] = [
"rx_bytes" : bytesin,
"tx_bytes" : bytesout
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
// TODO review
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data, withShadowSocks viaSS: Bool = false, completionHandler: @escaping (Error?) -> Void) {
wg_log(.info, message: "setupAndlaunchOpenVPN")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if(str.contains("cloak")){
configuration.setPTCloak();
}
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try ovpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
if !evaluation.autologin {
wg_log(.info, message: "Implement login with user credentials")
}
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
ovpnAdapter.connect(using: packetFlow)
// let ifaces = Interface.allInterfaces()
// .filter { $0.family == .ipv4 }
// .map { iface in iface.name }
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
// let ifaces = Interface.allInterfaces()
// .filter { $0.family == .ipv4 }
// .map { iface in iface.name }
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
}
// MARK: -- Network observing methods
private func startListeningForNetworkChanges() {
stopListeningForNetworkChanges()
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
}
private func stopListeningForNetworkChanges() {
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return }
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
@@ -412,28 +467,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
return
}
return
}
DispatchQueue.main.async { [weak self] in
guard let `self` = self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
}
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
dispatchPrecondition(condition: .onQueue(dispatchQueue))
let emptyTunnelConfiguration = TunnelConfiguration(
name: nil,
interface: InterfaceConfiguration(privateKey: PrivateKey()),
peers: []
)
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
self.dispatchQueue.async {
if let error {
@@ -445,9 +500,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
}
}
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
self.setTunnelNetworkSettings(settings) { error in
completionHandler(error)
}
@@ -478,6 +533,50 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
if (SplitTunnelType == "1") {
var ipv4IncludedRoutes = [NEIPv4Route]()
let STSdata = Data(SplitTunnelSites!.utf8)
do {
let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
for allowedIPString in STSarray {
if let allowedIP = IPAddressRange(from: allowedIPString){
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(allowedIP.address)", subnetMask: "\(allowedIP.subnetMask())"))
}
}
} catch {
wg_log(.error,message: "Parse JSONSerialization Error")
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else {
if (SplitTunnelType == "2")
{
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
let STSdata = Data(SplitTunnelSites!.utf8)
do {
let STSarray = try JSONSerialization.jsonObject(with: STSdata) as! [String]
for excludeIPString in STSarray {
if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(destinationAddress: "\(excludeIP.address)", subnetMask: "\(excludeIP.subnetMask())"))
}
}
} catch {
wg_log(.error,message: "Parse JSONSerialization Error")
}
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0"){
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(allIPv4.address)", subnetMask: "\(allIPv4.subnetMask())"))
}
if let allIPv6 = IPAddressRange(from: "::/0") {
ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(allIPv6.address)", networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
@@ -538,7 +637,7 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
wg_log(.info, message: logMessage)
}
}
extension WireGuardLogLevel {
var osLogLevel: OSLogType {
switch self {

View File

@@ -158,15 +158,15 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
return false;
}
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 1);
}
if (rtm->rtm_type == RTN_THROW) {
int index = if_nametoindex(getgatewayandiface().toUtf8());
if (index <= 0) {
logger.error() << "if_nametoindex() failed:" << strerror(errno);
return false;
}
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_OIF, index);
struct in_addr ip4;
inet_pton(AF_INET, getgatewayandiface().toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST;
}
struct sockaddr_nl nladdr;
@@ -334,7 +334,7 @@ QString LinuxRouteMonitor::getgatewayandiface()
}
}
close(sock);
return interface;
return gateway_address;
}
static bool buildAllowedIp(wg_allowedip* ip,

View File

@@ -236,6 +236,17 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
}
}
if (!config.m_excludedAddresses.empty()) {
for (const QString& i : config.m_excludedAddresses) {
logger.debug() << "range: " << i;
if (!allowTrafficToRange(i, HIGH_WEIGHT,
"Allow Ecxlude route", config.m_serverPublicKey)) {
return false;
}
}
}
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
@@ -411,8 +422,8 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
}
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
int weight, const QString& title,
const QString& peer) {
int weight, const QString& title,
const QString& peer) {
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
GUID layerOut =
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
@@ -473,6 +484,57 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
return true;
}
bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight,
const QString& title,
const QString& peer) {
QString description("Allow traffic %1 %2 ");
auto lower = addr.address();
auto upper = addr.broadcastAddress();
const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol;
const GUID layerKeyOut =
isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
FWPM_FILTER_CONDITION0 cond[1] = {0};
FWP_RANGE0 ipRange;
QByteArray lowIpV6Buffer;
QByteArray highIpV6Buffer;
importAddress(lower, ipRange.valueLow, &lowIpV6Buffer);
importAddress(upper, ipRange.valueHigh, &highIpV6Buffer);
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
cond[0].matchType = FWP_MATCH_RANGE;
cond[0].conditionValue.type = FWP_RANGE_TYPE;
cond[0].conditionValue.rangeValue = &ipRange;
filter.numFilterConditions = 1;
filter.filterCondition = cond;
filter.layerKey = layerKeyOut;
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
peer)) {
return false;
}
filter.layerKey = layerKeyIn;
if (!enableFilter(&filter, title,
description.arg("from").arg(addr.toString()), peer)) {
return false;
}
return true;
}
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
// Allow outbound DHCPv4
{

View File

@@ -52,6 +52,9 @@ class WindowsFirewall final : public QObject {
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
const QString& title, const QString& peer = QString());
bool allowTrafficToRange(const IPAddress& addr, uint8_t weight,
const QString& title,
const QString& peer);
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
const QString& title);
bool allowDHCPTraffic(uint8_t weight, const QString& title);

View File

@@ -1,5 +1,7 @@
#include "protocols_defs.h"
#include <QRandomGenerator>
using namespace amnezia;
QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p)
@@ -66,13 +68,12 @@ QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
{ Proto::ShadowSocks, "ShadowSocks" },
{ Proto::Cloak, "Cloak" },
{ Proto::WireGuard, "WireGuard" },
{ Proto::WireGuard, "AmneziaWG" },
{ Proto::Awg, "AmneziaWG" },
{ Proto::Ikev2, "IKEv2" },
{ Proto::L2tp, "L2TP" },
{ Proto::TorWebSite, "Website in Tor network" },
{ Proto::Dns, "DNS Service" },
{ Proto::FileShare, "File Sharing Service" },
{ Proto::Sftp, QObject::tr("Sftp service") } };
}
@@ -90,28 +91,42 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
case Proto::ShadowSocks: return ServiceType::Vpn;
case Proto::WireGuard: return ServiceType::Vpn;
case Proto::Awg: return ServiceType::Vpn;
case Proto::Ikev2: return ServiceType::Vpn;
case Proto::TorWebSite: return ServiceType::Other;
case Proto::Dns: return ServiceType::Other;
case Proto::FileShare: return ServiceType::Other;
case Proto::Sftp: return ServiceType::Other;
default: return ServiceType::Other;
}
}
int ProtocolProps::getPortForInstall(Proto p)
{
switch (p) {
case Awg:
case WireGuard:
case ShadowSocks:
case OpenVpn:
return QRandomGenerator::global()->bounded(30000, 50000);
default:
return defaultPort(p);
}
}
int ProtocolProps::defaultPort(Proto p)
{
switch (p) {
case Proto::Any: return -1;
case Proto::OpenVpn: return 1194;
case Proto::Cloak: return 443;
case Proto::ShadowSocks: return 6789;
case Proto::WireGuard: return 51820;
case Proto::Awg: return 55424;
case Proto::OpenVpn: return QString(protocols::openvpn::defaultPort).toInt();
case Proto::Cloak: return QString(protocols::cloak::defaultPort).toInt();
case Proto::ShadowSocks: return QString(protocols::shadowsocks::defaultPort).toInt();
case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt();
case Proto::Awg: return QString(protocols::awg::defaultPort).toInt();
case Proto::Ikev2: return -1;
case Proto::L2tp: return -1;
case Proto::TorWebSite: return -1;
case Proto::Dns: return 53;
case Proto::FileShare: return 139;
case Proto::Sftp: return 222;
default: return -1;
}
@@ -129,10 +144,10 @@ bool ProtocolProps::defaultPortChangeable(Proto p)
case Proto::Ikev2: return false;
case Proto::L2tp: return false;
case Proto::TorWebSite: return true;
case Proto::TorWebSite: return false;
case Proto::Dns: return false;
case Proto::FileShare: return false;
default: return -1;
case Proto::Sftp: return true;
default: return false;
}
}
@@ -150,7 +165,6 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
// non-vpn
case Proto::TorWebSite: return TransportProto::Tcp;
case Proto::Dns: return TransportProto::Udp;
case Proto::FileShare: return TransportProto::Udp;
case Proto::Sftp: return TransportProto::Tcp;
}
}
@@ -169,7 +183,6 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p)
// non-vpn
case Proto::TorWebSite: return false;
case Proto::Dns: return false;
case Proto::FileShare: return false;
case Proto::Sftp: return false;
default: return false;
}

View File

@@ -199,7 +199,6 @@ namespace amnezia
// non-vpn
TorWebSite,
Dns,
FileShare,
Sftp
};
Q_ENUM_NS(Proto)
@@ -233,6 +232,8 @@ namespace amnezia
Q_INVOKABLE static ServiceType protocolService(Proto p);
Q_INVOKABLE static int getPortForInstall(Proto p);
Q_INVOKABLE static int defaultPort(Proto p);
Q_INVOKABLE static bool defaultPortChangeable(Proto p);

View File

@@ -16,8 +16,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
writeWireguardConfiguration(configuration);
// MZ
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
@@ -26,7 +24,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr);
#endif
}
WireguardProtocol::~WireguardProtocol()
@@ -37,68 +34,10 @@ WireguardProtocol::~WireguardProtocol()
void WireguardProtocol::stop()
{
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
stopMzImpl();
return;
#endif
if (!QFileInfo::exists(Utils::wireguardExecPath())) {
qCritical() << "Wireguard executable missing!";
setLastError(ErrorCode::ExecutableMissing);
return;
}
m_wireguardStopProcess = IpcClient::CreatePrivilegedProcess();
if (!m_wireguardStopProcess) {
qCritical() << "IpcProcess replica is not created!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return;
}
m_wireguardStopProcess->waitForSource(1000);
if (!m_wireguardStopProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return;
}
m_wireguardStopProcess->setProgram(PermittedProcess::Wireguard);
m_wireguardStopProcess->setArguments(stopArgs());
qDebug() << stopArgs().join(" ");
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error;
setConnectionState(Vpn::ConnectionState::Disconnected);
});
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this,
[this](QProcess::ProcessState newState) {
qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState;
});
#ifdef Q_OS_LINUX
if (IpcClient::Interface()) {
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->isWireguardRunning();
if (result.returnValue()) {
setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
} else {
qCritical() << "IPC client not initialized";
setConnectionState(Vpn::ConnectionState::Disconnected);
return;
}
#endif
m_wireguardStopProcess->start();
m_wireguardStopProcess->waitForFinished(10000);
setConnectionState(Vpn::ConnectionState::Disconnected);
}
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
ErrorCode WireguardProtocol::startMzImpl()
{
m_impl->activate(m_rawConfig);
@@ -110,7 +49,6 @@ ErrorCode WireguardProtocol::stopMzImpl()
m_impl->deactivate();
return ErrorCode::NoError;
}
#endif
void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration)
{
@@ -124,21 +62,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close();
#if 0
if (IpcClient::Interface()) {
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName());
if (result.returnValue()) {
qCritical() << "Failed to copy wireguard config";
return;
}
} else {
qCritical() << "IPC client not initialized";
return;
}
m_configFileName = "/etc/wireguard/wg99.conf";
#else
m_configFileName = m_configFile.fileName();
#endif
m_isConfigLoaded = true;
@@ -152,15 +77,9 @@ QString WireguardProtocol::configPath() const
return m_configFileName;
}
void WireguardProtocol::updateRouteGateway(QString line)
QString WireguardProtocol::serviceName() const
{
// TODO: fix for macos
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (!line.contains("/"))
return;
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
m_routeGateway.replace(" ", "");
qDebug() << "Set VPN route gateway" << m_routeGateway;
return "AmneziaVPN.WireGuard0";
}
ErrorCode WireguardProtocol::start()
@@ -170,112 +89,6 @@ ErrorCode WireguardProtocol::start()
return lastError();
}
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
return startMzImpl();
#endif
if (!QFileInfo::exists(Utils::wireguardExecPath())) {
setLastError(ErrorCode::ExecutableMissing);
return lastError();
}
if (IpcClient::Interface()) {
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->isWireguardConfigExists(configPath());
if (result.returnValue()) {
setLastError(ErrorCode::ConfigMissing);
return lastError();
}
} else {
qCritical() << "IPC client not initialized";
setLastError(ErrorCode::InternalError);
return lastError();
}
setConnectionState(Vpn::ConnectionState::Connecting);
m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess();
if (!m_wireguardStartProcess) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_wireguardStartProcess->waitForSource(1000);
if (!m_wireguardStartProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_wireguardStartProcess->setProgram(PermittedProcess::Wireguard);
m_wireguardStartProcess->setArguments(startArgs());
qDebug() << startArgs().join(" ");
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error;
setConnectionState(Vpn::ConnectionState::Disconnected);
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this,
[this](QProcess::ProcessState newState) {
qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState;
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this,
[this]() { setConnectionState(Vpn::ConnectionState::Connected); });
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() {
QRemoteObjectPendingReply<QByteArray> reply = m_wireguardStartProcess->readAll();
reply.waitForFinished(1000);
qDebug() << "WireguardProtocol::WireguardProtocol readyRead" << reply.returnValue();
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardOutput, this, [this]() {
QRemoteObjectPendingReply<QByteArray> reply = m_wireguardStartProcess->readAllStandardOutput();
reply.waitForFinished(1000);
qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardOutput" << reply.returnValue();
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyReadStandardError, this, [this]() {
QRemoteObjectPendingReply<QByteArray> reply = m_wireguardStartProcess->readAllStandardError();
reply.waitForFinished(10);
qDebug() << "WireguardProtocol::WireguardProtocol readAllStandardError" << reply.returnValue();
});
m_wireguardStartProcess->start();
m_wireguardStartProcess->waitForFinished(10000);
return ErrorCode::NoError;
}
void WireguardProtocol::updateVpnGateway(const QString &line)
{
}
QString WireguardProtocol::serviceName() const
{
return "AmneziaVPN.WireGuard0";
}
QStringList WireguardProtocol::stopArgs()
{
#ifdef Q_OS_WIN
return { "--remove", configPath() };
#elif defined Q_OS_LINUX
return { "down", "wg99" };
#else
return {};
#endif
}
QStringList WireguardProtocol::startArgs()
{
#ifdef Q_OS_WIN
return { "--add", configPath() };
#elif defined Q_OS_LINUX
return { "up", "wg99" };
#else
return {};
#endif
}

View File

@@ -8,7 +8,6 @@
#include <QTimer>
#include "vpnprotocol.h"
#include "core/ipcclient.h"
#include "mozilla/controllerimpl.h"
@@ -23,33 +22,21 @@ public:
ErrorCode start() override;
void stop() override;
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
ErrorCode startMzImpl();
ErrorCode stopMzImpl();
#endif
private:
QString configPath() const;
void writeWireguardConfiguration(const QJsonObject &configuration);
void updateRouteGateway(QString line);
void updateVpnGateway(const QString &line);
QString serviceName() const;
QStringList stopArgs();
QStringList startArgs();
private:
QString m_configFileName;
QFile m_configFile;
QSharedPointer<PrivilegedProcess> m_wireguardStartProcess;
QSharedPointer<PrivilegedProcess> m_wireguardStopProcess;
bool m_isConfigLoaded = false;
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
QScopedPointer<ControllerImpl> m_impl;
#endif
};
#endif // WIREGUARDPROTOCOL_H

View File

@@ -222,5 +222,8 @@
<file>server_scripts/awg/configure_container.sh</file>
<file>server_scripts/awg/run_container.sh</file>
<file>server_scripts/awg/Dockerfile</file>
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>images/controls/close.svg</file>
<file>images/controls/search.svg</file>
</qresource>
</RCC>

View File

@@ -1,5 +1,6 @@
if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\
elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi
if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi

View File

@@ -1,19 +1,20 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); docker_pkg="docker"; dist="centos";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="--noconfirm -S"; check_pkgs="> /dev/null 2>&1"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Docker pkg: $docker_pkg";\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm update -yq; $pm install -yq sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm install -yq psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm install -yq lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then sudo $pm update -yq; sudo $pm install -yq $docker_pkg;\
if [ "$dist" = "fedora" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
if [ "$dist" = "fedora" ] || [ "$dist" = "centos" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\
fi;\
if [ "$dist" = "debian" ]; then \
docker_service=$(systemctl list-units --full --all | grep docker.service | grep -v inactive | grep -v dead | grep -v failed);\
if [ -z "$docker_service" ]; then sudo $pm update -yq; sudo $pm install -yq curl $docker_pkg; fi;\
if [ -z "$docker_service" ]; then sudo $pm $check_pkgs; sudo $pm $silent_inst curl $docker_pkg; fi;\
sleep 3 && sudo systemctl start docker && sleep 3;\
fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker";exit 1;fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker"; exit 1; fi;\
docker --version

View File

@@ -233,10 +233,6 @@ QString Settings::routeModeString(RouteMode mode) const
Settings::RouteMode Settings::routeMode() const
{
// TODO implement for mobiles
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
return RouteMode::VpnAllSites;
#endif
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,9 @@
#include <QImage>
#include <QStandardPaths>
#include "configurators/cloak_configurator.h"
#include "configurators/openvpn_configurator.h"
#include "configurators/shadowsocks_configurator.h"
#include "configurators/wireguard_configurator.h"
#include "core/errorstrings.h"
#include "systemController.h"
@@ -19,11 +21,13 @@
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent)
: QObject(parent),
m_serversModel(serversModel),
m_containersModel(containersModel),
m_clientManagementModel(clientManagementModel),
m_settings(settings),
m_configurator(configurator)
{
@@ -75,13 +79,12 @@ void ExportController::generateFullAccessConfigAndroid()
}
#endif
void ExportController::generateConnectionConfig()
void ExportController::generateConnectionConfig(const QString &clientName)
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
@@ -93,17 +96,25 @@ void ExportController::generateConnectionConfig()
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol);
QString vpnConfig =
m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode);
QString clientId;
QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol,
clientId, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
protocolConfig.insert(config_key::last_config, vpnConfig);
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
if (protocol == Proto::OpenVpn || protocol == Proto::Awg || protocol == Proto::WireGuard) {
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
}
}
QJsonObject config = m_settings->server(serverIndex);
QJsonObject config = m_settings->server(serverIndex); // todo change to servers_model
if (!errorCode) {
config.remove(config_key::userName);
config.remove(config_key::password);
@@ -126,7 +137,127 @@ void ExportController::generateConnectionConfig()
emit exportConfigChanged();
}
void ExportController::generateOpenVpnConfig()
void ExportController::generateOpenVpnConfig(const QString &clientName)
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
QJsonObject containerConfig =
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
ErrorCode errorCode = ErrorCode::NoError;
QString clientId;
QString config = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig,
clientId, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config);
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : lines) {
m_config.append(line + "\n");
}
m_qrCodes = generateQrCodeImageSeries(m_config.toUtf8());
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
emit exportConfigChanged();
}
void ExportController::generateWireGuardConfig(const QString &clientName)
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials();
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
QJsonObject containerConfig =
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
QString clientId;
ErrorCode errorCode = ErrorCode::NoError;
QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig,
clientId, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config);
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : lines) {
m_config.append(line + "\n");
}
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_config.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
errorCode = m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
emit exportConfigChanged();
}
void ExportController::generateShadowSocksConfig()
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
QJsonObject containerConfig =
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
ErrorCode errorCode = ErrorCode::NoError;
QString config = m_configurator->shadowSocksConfigurator->genShadowSocksConfig(credentials, container,
containerConfig, &errorCode);
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::ShadowSocks, config);
QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object();
QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n");
for (const QString &line : lines) {
m_config.append(line + "\n");
}
m_nativeConfigString =
QString("%1:%2@%3:%4")
.arg(configJson.value("method").toString(), configJson.value("password").toString(),
configJson.value("server").toString(), configJson.value("server_port").toString());
m_nativeConfigString = "ss://" + m_nativeConfigString.toUtf8().toBase64();
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(m_nativeConfigString.toUtf8(), qrcodegen::QrCode::Ecc::LOW);
m_qrCodes << svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged();
}
void ExportController::generateCloakConfig()
{
clearPreviousConfig();
@@ -142,47 +273,19 @@ void ExportController::generateOpenVpnConfig()
ErrorCode errorCode = ErrorCode::NoError;
QString config =
m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &errorCode);
m_configurator->cloakConfigurator->genCloakConfig(credentials, container, containerConfig, &errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config);
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Cloak, config);
QJsonObject configJson = QJsonDocument::fromJson(config.toUtf8()).object();
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
for (const QString &line : lines) {
m_config.append(line + "\n");
}
configJson.remove(config_key::transport_proto);
configJson.insert("ProxyMethod", "shadowsocks");
emit exportConfigChanged();
}
void ExportController::generateWireGuardConfig()
{
clearPreviousConfig();
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
ServerCredentials credentials =
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getCurrentlyProcessedContainerIndex());
QModelIndex containerModelIndex = m_containersModel->index(container);
QJsonObject containerConfig =
qvariant_cast<QJsonObject>(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole));
containerConfig.insert(config_key::container, ContainerProps::containerToString(container));
ErrorCode errorCode = ErrorCode::NoError;
QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig,
&errorCode);
if (errorCode) {
emit exportErrorOccurred(errorString(errorCode));
return;
}
config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config);
auto configJson = QJsonDocument::fromJson(config.toUtf8()).object();
QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n");
QStringList lines = QString(QJsonDocument(configJson).toJson()).replace("\r", "").split("\n");
for (const QString &line : lines) {
m_config.append(line + "\n");
}
@@ -195,6 +298,11 @@ QString ExportController::getConfig()
return m_config;
}
QString ExportController::getNativeConfigString()
{
return m_nativeConfigString;
}
QList<QString> ExportController::getQrCodes()
{
return m_qrCodes;
@@ -205,6 +313,30 @@ void ExportController::exportConfig(const QString &fileName)
SystemController::saveFile(fileName, m_config);
}
void ExportController::updateClientManagementModel(const DockerContainer container, ServerCredentials credentials)
{
ErrorCode errorCode = m_clientManagementModel->updateModel(container, credentials);
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode));
}
}
void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials)
{
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials);
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode));
}
}
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
{
ErrorCode errorCode = m_clientManagementModel->renameClient(row, clientName, container, credentials);
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode));
}
}
QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &data)
{
double k = 850;
@@ -219,7 +351,7 @@ QList<QString> ExportController::generateQrCodeImageSeries(const QByteArray &dat
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
QString svg = QString::fromStdString(toSvgString(qr, 0));
QString svg = QString::fromStdString(toSvgString(qr, 1));
chunks.append(svgToBase64(svg));
}
@@ -239,5 +371,6 @@ int ExportController::getQrCodesCount()
void ExportController::clearPreviousConfig()
{
m_config.clear();
m_nativeConfigString.clear();
m_qrCodes.clear();
}

View File

@@ -6,6 +6,7 @@
#include "configurators/vpn_configurator.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/clientManagementModel.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/authResultReceiver.h"
#endif
@@ -16,27 +17,36 @@ class ExportController : public QObject
public:
explicit ExportController(const QSharedPointer<ServersModel> &serversModel,
const QSharedPointer<ContainersModel> &containersModel,
const QSharedPointer<ClientManagementModel> &clientManagementModel,
const std::shared_ptr<Settings> &settings,
const std::shared_ptr<VpnConfigurator> &configurator, QObject *parent = nullptr);
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY exportConfigChanged)
Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged)
Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged)
Q_PROPERTY(QString nativeConfigString READ getNativeConfigString NOTIFY exportConfigChanged)
public slots:
void generateFullAccessConfig();
#if defined(Q_OS_ANDROID)
void generateFullAccessConfigAndroid();
#endif
void generateConnectionConfig();
void generateOpenVpnConfig();
void generateWireGuardConfig();
void generateConnectionConfig(const QString &clientName);
void generateOpenVpnConfig(const QString &clientName);
void generateWireGuardConfig(const QString &clientName);
void generateShadowSocksConfig();
void generateCloakConfig();
QString getConfig();
QString getNativeConfigString();
QList<QString> getQrCodes();
void exportConfig(const QString &fileName);
void updateClientManagementModel(const DockerContainer container, ServerCredentials credentials);
void revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials);
void renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials);
signals:
void generateConfig(int type);
void exportErrorOccurred(const QString &errorMessage);
@@ -55,10 +65,12 @@ private:
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
QString m_config;
QString m_nativeConfigString;
QList<QString> m_qrCodes;
#ifdef Q_OS_ANDROID

View File

@@ -144,8 +144,6 @@ void ImportController::importConfig()
if (credentials.isValid() || m_config.contains(config_key::containers)) {
m_serversModel->addServer(m_config);
m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1);
emit importFinished();
} else {
qDebug() << "Failed to import profile";
@@ -263,11 +261,8 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
// return QJsonObject();
// }
auto allowedIps = configMap.value("AllowedIPs").split(",");
QJsonArray allowedIpsJsonArray;
for (const auto &allowedIp : allowedIps) {
allowedIpsJsonArray.append(allowedIp);
}
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(","));
lastConfig[config_key::allowed_ips] = allowedIpsJsonArray;
QString protocolName = "wireguard";

View File

@@ -5,6 +5,7 @@
#include <QEventLoop>
#include <QJsonObject>
#include <QStandardPaths>
#include <QRandomGenerator>
#include "core/errorstrings.h"
#include "core/servercontroller.h"
@@ -73,6 +74,38 @@ void InstallController::install(DockerContainer container, int port, TransportPr
containerConfig.insert(config_key::transport_proto,
ProtocolProps::transportProtoToString(transportProto, protocol));
if (container == DockerContainer::Awg) {
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10));
QString junkPacketMinSize = QString::number(50);
QString junkPacketMaxSize = QString::number(1000);
QString initPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150));
QString responsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150));
QSet<QString> headersValue;
while (headersValue.size() != 4) {
auto max = (std::numeric_limits<qint32>::max)();
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, max)));
}
auto headersValueList = headersValue.values();
QString initPacketMagicHeader = headersValueList.at(0);
QString responsePacketMagicHeader = headersValueList.at(1);
QString underloadPacketMagicHeader = headersValueList.at(2);
QString transportPacketMagicHeader = headersValueList.at(3);
containerConfig[config_key::junkPacketCount] = junkPacketCount;
containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize;
containerConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize;
containerConfig[config_key::initPacketJunkSize] = initPacketJunkSize;
containerConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize;
containerConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader;
containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader;
containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader;
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
}
if (container == DockerContainer::Sftp) {
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
containerConfig.insert(config_key::password, Utils::getRandomString(10));
@@ -132,7 +165,6 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co
server.insert(config_key::defaultContainer, ContainerProps::containerToString(container));
m_serversModel->addServer(server);
m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1);
emit installServerFinished(finishMessage);
return;
@@ -183,7 +215,6 @@ void InstallController::installContainer(DockerContainer container, QJsonObject
"All installed containers have been added to the application");
}
m_containersModel->setData(m_containersModel->index(0, 0), container, ContainersModel::Roles::IsDefaultRole);
emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other);
return;
}
@@ -473,8 +504,9 @@ void InstallController::addEmptyServer()
server.insert(config_key::port, m_currentlyInstalledServerCredentials.port);
server.insert(config_key::description, m_settings->nextAvailableServerName());
server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None));
m_serversModel->addServer(server);
m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1);
emit installServerFinished(tr("Server added successfully"));
}

View File

@@ -51,7 +51,9 @@ namespace PageLoader
PageProtocolWireGuardSettings,
PageProtocolAwgSettings,
PageProtocolIKev2Settings,
PageProtocolRaw
PageProtocolRaw,
PageShareFullAccess
};
Q_ENUM_NS(PageEnum)

View File

@@ -1,104 +1,373 @@
#include "clientManagementModel.h"
#include <QJsonDocument>
#include <QJsonObject>
ClientManagementModel::ClientManagementModel(QObject *parent) : QAbstractListModel(parent)
{
#include "core/servercontroller.h"
#include "logger.h"
}
void ClientManagementModel::clearData()
namespace
{
beginResetModel();
m_content.clear();
endResetModel();
}
Logger logger("ClientManagementModel");
void ClientManagementModel::setContent(const QVector<QVariant> &data)
{
beginResetModel();
m_content = data;
endResetModel();
}
QJsonObject ClientManagementModel::getContent(amnezia::Proto protocol)
{
QJsonObject clientsTable;
for (const auto &item : m_content) {
if (protocol == amnezia::Proto::OpenVpn) {
clientsTable[item.toJsonObject()["openvpnCertId"].toString()] = item.toJsonObject();
} else if (protocol == amnezia::Proto::WireGuard) {
clientsTable[item.toJsonObject()["wireguardPublicKey"].toString()] = item.toJsonObject();
}
namespace configKey {
constexpr char clientId[] = "clientId";
constexpr char clientName[] = "clientName";
constexpr char container[] = "container";
constexpr char userData[] = "userData";
}
return clientsTable;
}
ClientManagementModel::ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent)
: m_settings(settings), QAbstractListModel(parent)
{
}
int ClientManagementModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return static_cast<int>(m_content.size());
return static_cast<int>(m_clientsTable.size());
}
QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0
|| index.row() >= static_cast<int>(m_content.size())) {
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_clientsTable.size())) {
return QVariant();
}
if (role == NameRole) {
return m_content[index.row()].toJsonObject()["clientName"].toString();
} else if (role == OpenVpnCertIdRole) {
return m_content[index.row()].toJsonObject()["openvpnCertId"].toString();
} else if (role == OpenVpnCertDataRole) {
return m_content[index.row()].toJsonObject()["openvpnCertData"].toString();
} else if (role == WireGuardPublicKey) {
return m_content[index.row()].toJsonObject()["wireguardPublicKey"].toString();
auto client = m_clientsTable.at(index.row()).toObject();
auto userData = client.value(configKey::userData).toObject();
switch (role) {
case ClientNameRole: return userData.value(configKey::clientName).toString();
}
return QVariant();
}
void ClientManagementModel::setData(const QModelIndex &index, QVariant data, int role)
ErrorCode ClientManagementModel::updateModel(DockerContainer container, ServerCredentials credentials)
{
if (!index.isValid() || index.row() < 0
|| index.row() >= static_cast<int>(m_content.size())) {
return;
beginResetModel();
m_clientsTable = QJsonArray();
ServerController serverController(m_settings);
ErrorCode error = ErrorCode::NoError;
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
const QByteArray clientsTableString =
serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the clientsTable file from the server";
endResetModel();
return error;
}
auto client = m_content[index.row()].toJsonObject();
if (role == NameRole) {
client["clientName"] = data.toString();
} else if (role == OpenVpnCertIdRole) {
client["openvpnCertId"] = data.toString();
} else if (role == OpenVpnCertDataRole) {
client["openvpnCertData"] = data.toString();
} else if (role == WireGuardPublicKey) {
client["wireguardPublicKey"] = data.toString();
} else {
return;
}
if (m_content[index.row()] != client) {
m_content[index.row()] = client;
emit dataChanged(index, index);
m_clientsTable = QJsonDocument::fromJson(clientsTableString).array();
if (m_clientsTable.isEmpty()) {
int count = 0;
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
error = getOpenVpnClients(serverController, container, credentials, count);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
error = getWireGuardClients(serverController, container, credentials, count);
}
if (error != ErrorCode::NoError) {
endResetModel();
return error;
}
const QByteArray newClientsTableString = QJsonDocument(m_clientsTable).toJson();
if (clientsTableString != newClientsTableString) {
error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString,
clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
}
}
}
endResetModel();
return error;
}
bool ClientManagementModel::removeRows(int row)
ErrorCode ClientManagementModel::getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count)
{
ErrorCode error = ErrorCode::NoError;
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
const QString getOpenVpnClientsList =
"sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'";
QString script = serverController.replaceVars(getOpenVpnClientsList,
serverController.genVarsForScript(credentials, container));
error = serverController.runScript(credentials, script, cbReadStdOut);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to retrieve the list of issued certificates on the server";
return error;
}
if (!stdOut.isEmpty()) {
QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts);
certsIds.removeAll("AmneziaReq.crt");
for (auto &openvpnCertId : certsIds) {
openvpnCertId.replace(".crt", "");
if (!isClientExists(openvpnCertId)) {
QJsonObject client;
client[configKey::clientId] = openvpnCertId;
QJsonObject userData;
userData[configKey::clientName] = QString("Client %1").arg(count);
client[configKey::userData] = userData;
m_clientsTable.push_back(client);
count++;
}
}
}
return error;
}
ErrorCode ClientManagementModel::getWireGuardClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count)
{
ErrorCode error = ErrorCode::NoError;
const QString wireGuardConfigFile =
QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
const QString wireguardConfigString =
serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the wg conf file from the server";
return error;
}
auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts);
QStringList wireguardKeys;
for (const auto &line : configLines) {
auto configPair = line.split(" = ", Qt::SkipEmptyParts);
if (configPair.front() == "PublicKey") {
wireguardKeys.push_back(configPair.back());
}
}
for (auto &wireguardKey : wireguardKeys) {
if (!isClientExists(wireguardKey)) {
QJsonObject client;
client[configKey::clientId] = wireguardKey;
QJsonObject userData;
userData[configKey::clientName] = QString("Client %1").arg(count);
client[configKey::userData] = userData;
m_clientsTable.push_back(client);
count++;
}
}
return error;
}
bool ClientManagementModel::isClientExists(const QString &clientId)
{
for (const QJsonValue &value : qAsConst(m_clientsTable)) {
if (value.isObject()) {
QJsonObject obj = value.toObject();
if (obj.contains(configKey::clientId) && obj[configKey::clientId].toString() == clientId) {
return true;
}
}
}
return false;
}
ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QString &clientName,
const DockerContainer container, ServerCredentials credentials)
{
ErrorCode error;
error = updateModel(container, credentials);
if (error != ErrorCode::NoError) {
return error;
}
for (int i = 0; i < m_clientsTable.size(); i++) {
if (m_clientsTable.at(i).toObject().value(configKey::clientId) == clientId) {
return renameClient(i, clientName, container, credentials);
}
}
beginResetModel();
QJsonObject client;
client[configKey::clientId] = clientId;
QJsonObject userData;
userData[configKey::clientName] = clientName;
client[configKey::userData] = userData;
m_clientsTable.push_back(client);
endResetModel();
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings);
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
}
return error;
}
ErrorCode ClientManagementModel::renameClient(const int row, const QString &clientName, const DockerContainer container,
ServerCredentials credentials)
{
auto client = m_clientsTable.at(row).toObject();
auto userData = client[configKey::userData].toObject();
userData[configKey::clientName] = clientName;
client[configKey::userData] = userData;
m_clientsTable.replace(row, client);
emit dataChanged(index(row, 0), index(row, 0));
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings);
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
ErrorCode error =
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
}
return error;
}
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
ServerCredentials credentials)
{
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
return revokeOpenVpn(row, container, credentials);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
return revokeWireGuard(row, container, credentials);
}
return ErrorCode::NoError;
}
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
ServerCredentials credentials)
{
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '"
"cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\"
"cp pki/crl.pem .'")
.arg(clientId);
ServerController serverController(m_settings);
const QString script =
serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container));
ErrorCode error = serverController.runScript(credentials, script);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to revoke the certificate";
return error;
}
beginRemoveRows(QModelIndex(), row, row);
m_content.removeAt(row);
m_clientsTable.removeAt(row);
endRemoveRows();
return true;
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
return ErrorCode::NoError;
}
ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerContainer container,
ServerCredentials credentials)
{
ErrorCode error;
ServerController serverController(m_settings);
const QString wireGuardConfigFile =
QString("/opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg");
const QString wireguardConfigString =
serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the wg conf file from the server";
return error;
}
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts);
for (auto &section : configSections) {
if (section.contains(clientId)) {
configSections.removeOne(section);
break;
}
}
QString newWireGuardConfig = configSections.join("[");
newWireGuardConfig.insert(0, "[");
error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig, wireGuardConfigFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the wg conf file to the server";
return error;
}
beginRemoveRows(QModelIndex(), row, row);
m_clientsTable.removeAt(row);
endRemoveRows();
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
return error;
}
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'";
error = serverController.runScript(
credentials,
serverController.replaceVars(script.arg(wireGuardConfigFile),
serverController.genVarsForScript(credentials, container)));
if (error != ErrorCode::NoError) {
logger.error() << "Failed to execute the command 'wg syncconf' on the server";
return error;
}
return ErrorCode::NoError;
}
QHash<int, QByteArray> ClientManagementModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "clientName";
roles[OpenVpnCertIdRole] = "openvpnCertId";
roles[OpenVpnCertDataRole] = "openvpnCertData";
roles[WireGuardPublicKey] = "wireguardPublicKey";
roles[ClientNameRole] = "clientName";
return roles;
}

View File

@@ -2,36 +2,48 @@
#define CLIENTMANAGEMENTMODEL_H
#include <QAbstractListModel>
#include <QJsonArray>
#include "protocols/protocols_defs.h"
#include "core/servercontroller.h"
#include "settings.h"
class ClientManagementModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ClientRoles {
NameRole = Qt::UserRole + 1,
OpenVpnCertIdRole,
OpenVpnCertDataRole,
WireGuardPublicKey,
enum Roles {
ClientNameRole = Qt::UserRole + 1,
};
ClientManagementModel(QObject *parent = nullptr);
ClientManagementModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
void clearData();
void setContent(const QVector<QVariant> &data);
QJsonObject getContent(amnezia::Proto protocol);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void setData(const QModelIndex &index, QVariant data, int role = Qt::DisplayRole);
bool removeRows(int row);
public slots:
ErrorCode updateModel(DockerContainer container, ServerCredentials credentials);
ErrorCode appendClient(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
ServerCredentials credentials);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QVector<QVariant> m_content;
bool isClientExists(const QString &clientId);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);
ErrorCode getWireGuardClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);
QJsonArray m_clientsTable;
std::shared_ptr<Settings> m_settings;
};
#endif // CLIENTMANAGEMENTMODEL_H

View File

@@ -22,10 +22,6 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i
DockerContainer container = ContainerProps::allContainers().at(index.row());
switch (role) {
case NameRole:
// return ContainerProps::containerHumanNames().value(container);
case DescriptionRole:
// return ContainerProps::containerDescriptions().value(container);
case ConfigRole: {
m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject());
m_containers = m_settings->containers(m_currentlyProcessedServerIndex);
@@ -35,19 +31,15 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i
break;
}
}
case ServiceTypeRole:
// return ContainerProps::containerService(container);
case DockerContainerRole:
// return container;
case IsInstalledRole:
// return m_settings->containers(m_currentlyProcessedServerIndex).contains(container);
case IsDefaultRole: {
case IsDefaultRole: { //todo remove
m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container);
m_defaultContainerIndex = container;
emit defaultContainerChanged();
}
default: break;
}
emit containersModelUpdated();
emit dataChanged(index, index);
return true;
}
@@ -117,6 +109,14 @@ QString ContainersModel::getDefaultContainerName()
return ContainerProps::containerHumanNames().value(m_defaultContainerIndex);
}
void ContainersModel::setDefaultContainer(int index)
{
auto container = static_cast<DockerContainer>(index);
m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container);
m_defaultContainerIndex = container;
emit defaultContainerChanged();
}
int ContainersModel::getCurrentlyProcessedContainerIndex()
{
return m_currentlyProcessedContainerIndex;

View File

@@ -46,6 +46,7 @@ public:
public slots:
DockerContainer getDefaultContainer();
QString getDefaultContainerName();
void setDefaultContainer(int index);
void setCurrentlyProcessedServerIndex(const int index);
@@ -72,6 +73,7 @@ protected:
signals:
void defaultContainerChanged();
void containersModelUpdated();
private:
QMap<DockerContainer, QJsonObject> m_containers;

View File

@@ -78,12 +78,11 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const
case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings;
case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings;
case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings;
case Proto::L2tp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings;
// non-vpn
case Proto::TorWebSite: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
case Proto::Dns: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
case Proto::FileShare: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
case Proto::Sftp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings;
case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings;
case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings;
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
}
}

View File

@@ -96,7 +96,7 @@ void ServersModel::setDefaultServerIndex(const int index)
{
m_settings->setDefaultServer(index);
m_defaultServerIndex = m_settings->defaultServerIndex();
emit defaultServerIndexChanged();
emit defaultServerIndexChanged(m_defaultServerIndex);
}
const int ServersModel::getDefaultServerIndex()
@@ -145,6 +145,11 @@ QString ServersModel::getCurrentlyProcessedServerHostName()
return qvariant_cast<QString>(data(m_currentlyProcessedServerIndex, HostNameRole));
}
const ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials()
{
return serverCredentials(m_currentlyProcessedServerIndex);
}
bool ServersModel::isDefaultServerCurrentlyProcessed()
{
return m_defaultServerIndex == m_currentlyProcessedServerIndex;
@@ -193,6 +198,12 @@ bool ServersModel::isDefaultServerConfigContainsAmneziaDns()
return primaryDns == protocols::dns::amneziaDnsIp;
}
void ServersModel::updateContainersConfig()
{
auto server = m_settings->server(m_currentlyProcessedServerIndex);
m_servers.replace(m_currentlyProcessedServerIndex, server);
}
QHash<int, QByteArray> ServersModel::roleNames() const
{
QHash<int, QByteArray> roles;

View File

@@ -53,18 +53,21 @@ public slots:
int getCurrentlyProcessedServerIndex();
QString getCurrentlyProcessedServerHostName();
const ServerCredentials getCurrentlyProcessedServerCredentials();
void addServer(const QJsonObject &server);
void removeServer();
bool isDefaultServerConfigContainsAmneziaDns();
void updateContainersConfig();
protected:
QHash<int, QByteArray> roleNames() const override;
signals:
void currentlyProcessedServerIndexChanged(const int index);
void defaultServerIndexChanged();
void defaultServerIndexChanged(const int index);
void defaultServerNameChanged();
private:

View File

@@ -3,7 +3,14 @@
SitesModel::SitesModel(std::shared_ptr<Settings> settings, QObject *parent)
: QAbstractListModel(parent), m_settings(settings)
{
m_currentRouteMode = m_settings->routeMode();
auto routeMode = m_settings->routeMode();
if (routeMode == Settings::RouteMode::VpnAllSites) {
m_isSplitTunnelingEnabled = false;
m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites;
} else {
m_isSplitTunnelingEnabled = true;
m_currentRouteMode = routeMode;
}
fillSites();
}
@@ -93,6 +100,21 @@ void SitesModel::setRouteMode(int routeMode)
emit routeModeChanged();
}
bool SitesModel::isSplitTunnelingEnabled()
{
return m_isSplitTunnelingEnabled;
}
void SitesModel::toggleSplitTunneling(bool enabled)
{
if (enabled) {
setRouteMode(m_currentRouteMode);
} else {
m_settings->setRouteMode(Settings::RouteMode::VpnAllSites);
}
m_isSplitTunnelingEnabled = enabled;
}
QVector<QPair<QString, QString> > SitesModel::getCurrentSites()
{
return m_sites;

View File

@@ -31,6 +31,9 @@ public slots:
int getRouteMode();
void setRouteMode(int routeMode);
bool isSplitTunnelingEnabled();
void toggleSplitTunneling(bool enabled);
QVector<QPair<QString, QString>> getCurrentSites();
signals:
@@ -44,6 +47,7 @@ private:
std::shared_ptr<Settings> m_settings;
bool m_isSplitTunnelingEnabled;
Settings::RouteMode m_currentRouteMode;
QVector<QPair<QString, QString>> m_sites;

View File

@@ -142,6 +142,7 @@ Button {
PageController.setTriggeredBtConnectButton(true)
ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex()
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardEasy)
return

View File

@@ -20,16 +20,14 @@ DrawerType {
anchors.right: parent.right
spacing: 0
Header2TextType {
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 32
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 16
text: qsTr("Connection data")
wrapMode: Text.WordWrap
headerText: qsTr("Add new connection")
}
LabelWithButtonType {
@@ -37,7 +35,7 @@ DrawerType {
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Server IP, login and password")
text: qsTr("Configure your server")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
@@ -51,7 +49,7 @@ DrawerType {
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("QR code, key or configuration file")
text: qsTr("Open config file, key or QR code")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {

View File

@@ -50,34 +50,26 @@ ListView {
imageSource: "qrc:/images/controls/download.svg"
showImage: !isInstalled
checkable: isInstalled
checkable: isInstalled && !ConnectionController.isConnected && isSupported
checked: isDefault
onPressed: function(mouse) {
if (!isSupported) {
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))
}
}
onClicked: {
if (checked) {
var needReconnected = false
if (!isDefault) {
needReconnected = true
}
if (ConnectionController.isConnected && isInstalled) {
PageController.showNotificationMessage(qsTr("Unable change protocol while there is an active connection"))
return
}
if (checked) {
isDefault = true
menuContent.currentIndex = index
containersDropDown.menuVisible = false
if (needReconnected && (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) {
PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name)
PageController.goToPageHome()
ConnectionController.openConnection()
}
} else {
if (!isSupported && isInstalled) {
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))
return
}
ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index))
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)

View File

@@ -17,9 +17,11 @@ DrawerType {
property var noButtonFunction
width: parent.width
height: parent.height * 0.5
height: content.implicitHeight + 32
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right

View File

@@ -112,6 +112,30 @@ DrawerType {
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 8
visible: nativeConfigString.text !== ""
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
borderWidth: 1
text: qsTr("Copy config string")
imageSource: "qrc:/images/controls/copy.svg"
onClicked: {
nativeConfigString.selectAll()
nativeConfigString.copy()
nativeConfigString.select(0, 0)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
@@ -170,6 +194,12 @@ DrawerType {
}
TextField {
id: nativeConfigString
visible: false
text: ExportController.nativeConfigString
}
TextArea {
id: configText
Layout.fillWidth: true
@@ -213,7 +243,6 @@ DrawerType {
Image {
anchors.fill: parent
anchors.margins: 2
smooth: false
source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : ""

View File

@@ -26,4 +26,16 @@ Item {
}
return false
}
TextEdit{
id: clipboard
visible: false
}
function copyToClipBoard(text) {
clipboard.text = text
clipboard.selectAll()
clipboard.copy()
clipboard.select(0, 0)
}
}

View File

@@ -81,6 +81,7 @@ RadioButton {
Text {
text: root.headerText
wrapMode: Text.WordWrap
color: "#D7D8DB"
font.pixelSize: 25
font.weight: 700
@@ -110,6 +111,7 @@ RadioButton {
Text {
text: root.footerText
wrapMode: Text.WordWrap
visible: root.footerText !== ""
color: "#878B91"
font.pixelSize: 13

View File

@@ -1,6 +1,8 @@
import QtQuick
import QtQuick.Controls
import "../Config"
Drawer {
id: drawer
property bool needCloseButton: true
@@ -39,6 +41,18 @@ Drawer {
border.color: "#2C2D30"
border.width: 1
Rectangle {
visible: GC.isMobile()
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: 20
height: 2
color: "#2C2D30"
}
}
Overlay.modal: Rectangle {

View File

@@ -20,7 +20,9 @@ Item {
property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3
property string textColor: "#d7d8db"
property string textDisabledColor: "#878B91"
property string descriptionColor: "#878B91"
property string descriptionDisabledColor: "#494B50"
property real textOpacity: 1.0
property string rightImageColor: "#d7d8db"
@@ -71,7 +73,14 @@ Item {
ListItemTitleType {
text: root.text
color: root.descriptionOnTop ? root.descriptionColor : root.textColor
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.descriptionColor : root.textColor
} else {
return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor
}
}
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
@@ -96,7 +105,13 @@ Item {
id: description
text: root.descriptionText
color: root.descriptionOnTop ? root.textColor : root.descriptionColor
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.textColor : root.descriptionColor
} else {
return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor
}
}
opacity: root.textOpacity
@@ -157,7 +172,7 @@ Item {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
hoverEnabled: root.enabled
onEntered: {
if (rightImageSource) {

View File

@@ -30,17 +30,13 @@ Switch {
property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08)
property string defaultIndicatorBackgroundColor: "transparent"
implicitWidth: content.implicitWidth + switcher.implicitWidth
implicitHeight: content.implicitHeight
hoverEnabled: enabled ? true : false
indicator: Rectangle {
id: switcher
anchors.left: content.right
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 4
implicitWidth: 52
implicitHeight: 32
@@ -90,11 +86,12 @@ Switch {
contentItem: ColumnLayout {
id: content
anchors.fill: parent
anchors.rightMargin: switcher.implicitWidth
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
ListItemTitleType {
Layout.fillWidth: true
rightPadding: indicator.width
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
@@ -104,6 +101,7 @@ Switch {
id: description
Layout.fillWidth: true
rightPadding: indicator.width
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor

View File

@@ -32,7 +32,7 @@ PageType {
function onRestorePageHomeState(isContainerInstalled) {
buttonContent.state = "expanded"
if (isContainerInstalled) {
containersDropDown.menuVisible = true
containersDropDown.rootButtonClickedFunction()
}
}
function onForceCloseDrawer() {
@@ -241,8 +241,18 @@ PageType {
}
]
DividerType {
Layout.topMargin: 10
Layout.fillWidth: false
Layout.preferredWidth: 20
Layout.preferredHeight: 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility)
}
RowLayout {
Layout.topMargin: 24
Layout.topMargin: 14
Layout.leftMargin: 24
Layout.rightMargin: 24
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
@@ -305,7 +315,7 @@ PageType {
Header1TextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.topMargin: 14
Layout.leftMargin: 16
Layout.rightMargin: 16
@@ -471,10 +481,16 @@ PageType {
}
checked: index === serversMenuContent.currentIndex
checkable: !ConnectionController.isConnected
ButtonGroup.group: serversRadioButtonGroup
onClicked: {
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection"))
return
}
serversMenuContent.currentIndex = index
ServersModel.currentlyProcessedIndex = index

View File

@@ -276,7 +276,7 @@ PageType {
onClicked: {
questionDrawer.headerText = qsTr("Remove AmneziaWG from server?")
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")

View File

@@ -5,6 +5,7 @@ import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerEnum 1.0
import "./"
import "../Controls2"
@@ -252,6 +253,8 @@ PageType {
ColumnLayout {
id: checkboxLayout
anchors.fill: parent
CheckBoxType {
Layout.fillWidth: true
@@ -351,6 +354,8 @@ PageType {
Layout.leftMargin: -8
implicitHeight: 32
visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.OpenVpn
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
@@ -360,7 +365,7 @@ PageType {
onClicked: {
questionDrawer.headerText = qsTr("Remove OpenVpn from server?")
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")

View File

@@ -169,12 +169,14 @@ PageType {
width: parent.width
visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess()
text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName()
textColor: "#EB5757"
clickedFunction: function() {
questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName())
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")

View File

@@ -96,7 +96,7 @@ PageType {
rightImageColor: "#D7D8DB"
clickedFunction: function() {
col.copyToClipBoard(descriptionText)
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
@@ -113,7 +113,7 @@ PageType {
rightImageColor: "#D7D8DB"
clickedFunction: function() {
col.copyToClipBoard(descriptionText)
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
@@ -130,7 +130,7 @@ PageType {
rightImageColor: "#D7D8DB"
clickedFunction: function() {
col.copyToClipBoard(descriptionText)
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
@@ -147,23 +147,11 @@ PageType {
rightImageColor: "#D7D8DB"
clickedFunction: function() {
col.copyToClipBoard(descriptionText)
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
TextEdit{
id: clipboard
visible: false
}
function copyToClipBoard(text) {
clipboard.text = text
clipboard.selectAll()
clipboard.copy()
clipboard.select(0, 0)
}
BasicButtonType {
visible: !GC.isMobile()

View File

@@ -78,23 +78,11 @@ PageType {
rightImageColor: "#D7D8DB"
clickedFunction: function() {
content.copyToClipBoard(descriptionText)
GC.copyToClipBoard(descriptionText)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
TextEdit{
id: clipboard
visible: false
}
function copyToClipBoard(text) {
clipboard.text = text
clipboard.selectAll()
clipboard.copy()
clipboard.select(0, 0)
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 40
@@ -121,7 +109,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("When configuring WordPress set the domain as this onion address.")
text: qsTr("When configuring WordPress set the this onion address as domain.")
}
BasicButtonType {

View File

@@ -110,6 +110,7 @@ PageType {
DividerType {}
LabelWithButtonType {
visible: GC.isDesktop()
Layout.fillWidth: true
Layout.preferredHeight: about.height
@@ -122,7 +123,9 @@ PageType {
}
}
DividerType {}
DividerType {
visible: GC.isDesktop()
}
}
}
}

View File

@@ -70,7 +70,7 @@ PageType {
Layout.margins: 16
text: qsTr("Auto start")
descriptionText: qsTr("Launch the application every time %1 starts").arg(Qt.platform.os)
descriptionText: qsTr("Launch the application every time the device is starts")
checked: SettingsController.isAutoStartEnabled()
onCheckedChanged: {

View File

@@ -103,6 +103,7 @@ PageType {
PageController.showBusyIndicator(true)
SettingsController.backupAppConfig(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Backup file saved"))
}
}
}

View File

@@ -94,6 +94,8 @@ PageType {
DividerType {}
LabelWithButtonType {
visible: true
Layout.fillWidth: true
text: qsTr("Site-based split tunneling")
@@ -105,12 +107,15 @@ PageType {
}
}
DividerType {}
DividerType {
visible: GC.isDesktop()
}
LabelWithButtonType {
Layout.fillWidth: true
visible: false
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: qsTr("Allows you to use the VPN only for certain applications")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
@@ -119,7 +124,9 @@ PageType {
}
}
// DividerType {}
DividerType {
visible: false
}
}
}
}

View File

@@ -46,6 +46,7 @@ PageType {
}
ParagraphTextType {
Layout.fillWidth: true
text: qsTr("If AmneziaDNS is not used or installed")
}

View File

@@ -115,6 +115,7 @@ PageType {
PageController.showBusyIndicator(true)
SettingsController.exportLogsFile(fileName)
PageController.showBusyIndicator(false)
PageController.showNotificationMessage(qsTr("Logs file saved"))
}
}
}

View File

@@ -114,7 +114,7 @@ PageType {
clickedFunction: function() {
questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName())
questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.")
questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")

View File

@@ -20,6 +20,10 @@ import "../Components"
PageType {
id: root
property bool pageEnabled: {
return !ConnectionController.isConnected
}
Connections {
target: SitesController
@@ -78,6 +82,8 @@ PageType {
RowLayout {
HeaderType {
enabled: root.pageEnabled
Layout.fillWidth: true
Layout.leftMargin: 16
@@ -87,20 +93,15 @@ PageType {
SwitcherType {
id: switcher
property int lastActiveRouteMode: routeMode.onlyForwardSites
enabled: root.pageEnabled
Layout.fillWidth: true
Layout.rightMargin: 16
checked: SitesModel.routeMode !== routeMode.allSites
onToggled: {
if (checked) {
SitesModel.routeMode = lastActiveRouteMode
} else {
lastActiveRouteMode = SitesModel.routeMode
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
SitesModel.routeMode = routeMode.allSites
}
checked: SitesModel.isSplitTunnelingEnabled()
onToggled: {
SitesModel.toggleSplitTunneling(checked)
selector.text = root.routeModesModel[getRouteModesModelIndex()].name
}
}
}
@@ -115,7 +116,7 @@ PageType {
drawerHeight: 0.4375
enabled: switcher.checked
enabled: root.pageEnabled
headerText: qsTr("Mode")
@@ -155,9 +156,9 @@ PageType {
FlickableType {
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin
contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin
enabled: switcher.checked
enabled: root.pageEnabled
Column {
id: col
@@ -221,8 +222,17 @@ PageType {
}
}
Rectangle {
anchors.fill: addSiteButton
anchors.bottomMargin: -24
color: "#0E0E11"
opacity: 0.8
}
RowLayout {
id: connectButton
id: addSiteButton
enabled: root.pageEnabled
anchors.bottom: parent.bottom
anchors.left: parent.left

View File

@@ -41,7 +41,7 @@ PageType {
HeaderType {
Layout.fillWidth: true
headerText: qsTr("Server connection")
headerText: qsTr("Configure your server")
}
TextFieldWithHeaderType {
@@ -54,7 +54,7 @@ PageType {
regularExpression: InstallController.ipAddressPortRegExp()
}
onTextFieldTextChanged: {
onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '');
}
}
@@ -81,6 +81,10 @@ PageType {
clickedFunc: function() {
hidePassword = !hidePassword
}
onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '');
}
}
BasicButtonType {
@@ -90,6 +94,7 @@ PageType {
text: qsTr("Continue")
onClicked: function() {
forceActiveFocus()
if (!isCredentialsFilled()) {
return
}
@@ -107,6 +112,13 @@ PageType {
PageController.goToPage(PageEnum.PageSetupWizardEasy)
}
}
LabelTextType {
Layout.fillWidth: true
Layout.topMargin: 12
text: qsTr("All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties")
}
}
}

View File

@@ -24,23 +24,25 @@ PageType {
target: InstallController
function onInstallContainerFinished(finishedMessage, isServiceInstall) {
PageController.goToStartPage()
if (!ConnectionController.isConnected && !isServiceInstall) {
ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex())
}
PageController.closePage() // close installing page
PageController.closePage() // close protocol settings page
if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) {
PageController.restorePageHomeState(true)
} else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) {
PageController.goToPage(PageEnum.PageSettingsServersList, false)
PageController.goToPage(PageEnum.PageSettingsServerInfo, false)
if (isServiceInstall) {
PageController.goToPageSettingsServerServices()
}
} else {
PageController.goToPage(PageEnum.PageHome)
}
PageController.showNotificationMessage(finishedMessage)
}
function onInstallServerFinished(finishedMessage) {
if (!ConnectionController.isConnected) {
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
}
PageController.goToStartPage()
if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) {
PageController.replaceStartPage()
@@ -59,8 +61,8 @@ PageType {
function onServerIsBusy(isBusy) {
if (isBusy) {
root.progressBarText = qsTr("Amnesia has detected that your server is currently ") +
qsTr("busy installing other software. Amnesia installation ") +
root.progressBarText = qsTr("Amnezia has detected that your server is currently ") +
qsTr("busy installing other software. Amnezia installation ") +
qsTr("will pause until the server finishes installing other software")
root.isTimerRunning = false
} else {

View File

@@ -144,33 +144,16 @@ PageType {
headerText: name
}
TextField {
implicitWidth: parent.width
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.bottomMargin: 16
padding: 0
leftPadding: 0
height: 24
color: "#D7D8DB"
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
text: detailedDescription
wrapMode: Text.WordWrap
readOnly: true
background: Rectangle {
anchors.fill: parent
color: "transparent"
}
textFormat: Text.MarkdownText
}
Rectangle {
Layout.fillHeight: true
color: "transparent"
@@ -241,7 +224,7 @@ PageType {
if (ProtocolProps.defaultPort(defaultContainerProto) < 0) {
port.visible = false
} else {
port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto)
port.textFieldText = ProtocolProps.getPortForInstall(defaultContainerProto)
}
transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto)

View File

@@ -134,7 +134,7 @@ PageType {
text: qsTr("I have nothing")
onClicked: Qt.openUrlExternally("https://ru-docs.amnezia.org/guides/hosting-instructions")
onClicked: Qt.openUrlExternally("https://amnezia.org/instructions/0_starter-guide")
}
}

View File

@@ -24,6 +24,10 @@ PageType {
}
function onImportFinished() {
if (!ConnectionController.isConnected) {
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
}
PageController.goToStartPage()
if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) {
PageController.replaceStartPage()

View File

@@ -18,15 +18,28 @@ PageType {
enum ConfigType {
AmneziaConnection,
AmneziaFullAccess,
OpenVpn,
WireGuard
WireGuard,
ShadowSocks,
Cloak
}
signal revokeConfig(int index)
onRevokeConfig: function(index) {
PageController.showBusyIndicator(true)
ExportController.revokeConfig(index,
ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
}
Connections {
target: ExportController
function onGenerateConfig(type) {
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.needCloseButton = false
shareConnectionDrawer.open()
@@ -34,28 +47,34 @@ PageType {
PageController.showBusyIndicator(true)
switch (type) {
case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break;
case PageShare.ConfigType.AmneziaFullAccess: {
if (Qt.platform.os === "android") {
ExportController.generateFullAccessConfigAndroid();
} else {
ExportController.generateFullAccessConfig();
}
break;
}
case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(clientNameTextField.textFieldText); break;
case PageShare.ConfigType.OpenVpn: {
ExportController.generateOpenVpnConfig();
ExportController.generateOpenVpnConfig(clientNameTextField.textFieldText)
shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config")
shareConnectionDrawer.configExtension = ".ovpn"
shareConnectionDrawer.configFileName = "amnezia_for_openvpn"
break;
break
}
case PageShare.ConfigType.WireGuard: {
ExportController.generateWireGuardConfig();
ExportController.generateWireGuardConfig(clientNameTextField.textFieldText)
shareConnectionDrawer.configCaption = qsTr("Save WireGuard config")
shareConnectionDrawer.configExtension = ".conf"
shareConnectionDrawer.configFileName = "amnezia_for_wireguard"
break;
break
}
case PageShare.ConfigType.ShadowSocks: {
ExportController.generateShadowSocksConfig()
shareConnectionDrawer.configCaption = qsTr("Save ShadowSocks config")
shareConnectionDrawer.configExtension = ".json"
shareConnectionDrawer.configFileName = "amnezia_for_shadowsocks"
break
}
case PageShare.ConfigType.Cloak: {
ExportController.generateCloakConfig()
shareConnectionDrawer.configCaption = qsTr("Save Cloak config")
shareConnectionDrawer.configExtension = ".json"
shareConnectionDrawer.configFileName = "amnezia_for_cloak"
break
}
}
@@ -73,8 +92,7 @@ PageType {
}
}
property string fullConfigServerSelectorText
property string connectionServerSelectorText
property bool isSearchBarVisible: false
property bool showContent: false
property bool shareButtonEnabled: true
property list<QtObject> connectionTypesModel: [
@@ -96,6 +114,16 @@ PageType {
property string name: qsTr("WireGuard native format")
property var type: PageShare.ConfigType.WireGuard
}
QtObject {
id: shadowSocksConnectionFormat
property string name: qsTr("ShadowSocks native format")
property var type: PageShare.ConfigType.ShadowSocks
}
QtObject {
id: cloakConnectionFormat
property string name: qsTr("Cloak native format")
property var type: PageShare.ConfigType.Cloak
}
FlickableType {
anchors.top: parent.top
@@ -118,7 +146,52 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 24
headerText: qsTr("VPN Access")
headerText: qsTr("Share VPN Access")
actionButtonImage: "qrc:/images/controls/more-vertical.svg"
actionButtonFunction: function() {
shareFullAccessDrawer.open()
}
DrawerType {
id: shareFullAccessDrawer
width: root.width
height: root.height * 0.45
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
spacing: 0
Header2Type {
Layout.fillWidth: true
Layout.bottomMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("Share full access to the server and VPN")
descriptionText: qsTr("Use for your own devices, or share with those you trust to manage the server.")
}
LabelWithButtonType {
Layout.fillWidth: true
text: qsTr("Share")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageShareFullAccess)
shareFullAccessDrawer.close()
}
}
}
}
}
Rectangle {
@@ -147,20 +220,21 @@ PageType {
onClicked: {
accessTypeSelector.currentIndex = 0
serverSelector.text = root.connectionServerSelectorText
}
}
HorizontalRadioButton {
checked: root.currentIndex === 1
checked: accessTypeSelector.currentIndex === 1
implicitWidth: (root.width - 32) / 2
text: qsTr("Full access")
text: qsTr("Users")
onClicked: {
accessTypeSelector.currentIndex = 1
serverSelector.text = root.fullConfigServerSelectorText
root.shareButtonEnabled = true
PageController.showBusyIndicator(true)
ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
}
}
}
@@ -171,16 +245,30 @@ PageType {
Layout.topMargin: 24
Layout.bottomMargin: 24
text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") :
qsTr("Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings.")
visible: accessTypeSelector.currentIndex === 0
text: qsTr("Share VPN access without the ability to manage the server")
color: "#878B91"
}
TextFieldWithHeaderType {
id: clientNameTextField
Layout.fillWidth: true
Layout.topMargin: 16
visible: accessTypeSelector.currentIndex === 0
headerText: qsTr("User name")
textFieldText: "New client"
checkEmptyText: true
}
DropDownType {
id: serverSelector
signal severSelectorIndexChanged
property int currentIndex: 0
property int currentIndex: -1
Layout.fillWidth: true
Layout.topMargin: 16
@@ -207,8 +295,6 @@ PageType {
]
}
currentIndex: 0
clickedFunction: function() {
handler()
@@ -217,22 +303,17 @@ PageType {
serverSelector.severSelectorIndexChanged()
}
if (accessTypeSelector.currentIndex !== 0) {
shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
}
serverSelector.menuVisible = false
}
Component.onCompleted: {
handler()
serverSelector.severSelectorIndexChanged()
serverSelectorListView.currentIndex = ServersModel.isDefaultServerHasWriteAccess() ?
proxyServersModel.mapFromSource(ServersModel.defaultIndex) : 0
serverSelectorListView.triggerCurrentItem()
}
function handler() {
serverSelector.text = selectedText
root.fullConfigServerSelectorText = selectedText
root.connectionServerSelectorText = selectedText
ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex)
}
}
@@ -241,8 +322,6 @@ PageType {
DropDownType {
id: protocolSelector
visible: accessTypeSelector.currentIndex === 0
Layout.fillWidth: true
Layout.topMargin: 16
@@ -275,22 +354,18 @@ PageType {
currentIndex: 0
clickedFunction: function() {
protocolSelectorListView.currentItem.y
handler()
protocolSelector.menuVisible = false
}
Component.onCompleted: {
if (accessTypeSelector.currentIndex === 0) {
handler()
}
}
Connections {
target: serverSelector
function onSeverSelectorIndexChanged() {
protocolSelectorListView.currentIndex = 0
protocolSelectorListView.currentIndex = proxyContainersModel.mapFromSource(ContainersModel.getDefaultContainer())
protocolSelectorListView.triggerCurrentItem()
}
}
@@ -304,13 +379,17 @@ PageType {
}
protocolSelector.text = selectedText
root.connectionServerSelectorText = serverSelector.text
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex))
fillConnectionTypeModel()
if (accessTypeSelector.currentIndex === 1) {
PageController.showBusyIndicator(true)
ExportController.updateClientManagementModel(ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
}
}
function fillConnectionTypeModel() {
@@ -320,8 +399,15 @@ PageType {
if (index === ContainerProps.containerFromString("amnezia-openvpn")) {
root.connectionTypesModel.push(openVpnConnectionFormat)
} else if (index === ContainerProps.containerFromString("amnezia-awg")) {
} else if (index === ContainerProps.containerFromString("amnezia-wireguard")) {
root.connectionTypesModel.push(wireGuardConnectionFormat)
} else if (index === ContainerProps.containerFromString("amnezia-shadowsocks")) {
root.connectionTypesModel.push(openVpnConnectionFormat)
root.connectionTypesModel.push(shadowSocksConnectionFormat)
} else if (index === ContainerProps.containerFromString("amnezia-openvpn-cloak")) {
root.connectionTypesModel.push(openVpnConnectionFormat)
root.connectionTypesModel.push(shadowSocksConnectionFormat)
root.connectionTypesModel.push(cloakConnectionFormat)
}
}
}
@@ -378,18 +464,235 @@ PageType {
Layout.topMargin: 40
enabled: shareButtonEnabled
visible: accessTypeSelector.currentIndex === 0
text: qsTr("Share")
imageSource: "qrc:/images/controls/share-2.svg"
onClicked: {
if (accessTypeSelector.currentIndex === 0) {
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
} else {
ExportController.generateConfig(PageShare.ConfigType.AmneziaFullAccess)
ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type)
}
}
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 16
visible: accessTypeSelector.currentIndex === 1 && !root.isSearchBarVisible
headerText: qsTr("Users")
actionButtonImage: "qrc:/images/controls/search.svg"
actionButtonFunction: function() {
root.isSearchBarVisible = true
}
}
RowLayout {
Layout.topMargin: 24
Layout.bottomMargin: 16
visible: accessTypeSelector.currentIndex === 1 && root.isSearchBarVisible
TextFieldWithHeaderType {
id: searchTextField
Layout.fillWidth: true
textFieldPlaceholderText: qsTr("Search")
}
ImageButtonType {
image: "qrc:/images/controls/close.svg"
imageColor: "#D7D8DB"
onClicked: function() {
root.isSearchBarVisible = false
searchTextField.textFieldText = ""
}
}
}
ListView {
id: clientsListView
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
visible: accessTypeSelector.currentIndex === 1
model: SortFilterProxyModel {
id: proxyClientManagementModel
sourceModel: ClientManagementModel
filters: RegExpFilter {
roleName: "clientName"
pattern: ".*" + searchTextField.textFieldText + ".*"
caseSensitivity: Qt.CaseInsensitive
}
}
clip: true
interactive: false
delegate: Item {
implicitWidth: clientsListView.width
implicitHeight: delegateContent.implicitHeight
ColumnLayout {
id: delegateContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: -16
anchors.leftMargin: -16
LabelWithButtonType {
Layout.fillWidth: true
text: clientName
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
clientInfoDrawer.open()
}
}
DividerType {}
DrawerType {
id: clientInfoDrawer
width: root.width
height: root.height * 0.5
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 8
Header2Type {
Layout.fillWidth: true
Layout.bottomMargin: 24
headerText: clientName
descriptionText: serverSelector.text
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
borderWidth: 1
text: qsTr("Rename")
onClicked: function() {
clientNameEditDrawer.open()
}
DrawerType {
id: clientNameEditDrawer
width: root.width
height: root.height * 0.35
onVisibleChanged: {
if (clientNameEditDrawer.visible) {
clientNameEditor.textField.forceActiveFocus()
}
}
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
TextFieldWithHeaderType {
id: clientNameEditor
Layout.fillWidth: true
headerText: qsTr("Client name")
textFieldText: clientName
textField.maximumLength: 30
}
BasicButtonType {
Layout.fillWidth: true
text: qsTr("Save")
onClicked: {
if (clientNameEditor.textFieldText !== clientName) {
PageController.showBusyIndicator(true)
ExportController.renameClient(index,
clientNameEditor.textFieldText,
ContainersModel.getCurrentlyProcessedContainerIndex(),
ServersModel.getCurrentlyProcessedServerCredentials())
PageController.showBusyIndicator(false)
clientNameEditDrawer.close()
}
}
}
}
}
}
BasicButtonType {
Layout.fillWidth: true
defaultColor: "transparent"
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
pressedColor: Qt.rgba(1, 1, 1, 0.12)
disabledColor: "#878B91"
textColor: "#D7D8DB"
borderWidth: 1
text: qsTr("Revoke")
onClicked: function() {
questionDrawer.headerText = qsTr("Revoke the config for a user - ") + clientName + "?"
questionDrawer.descriptionText = qsTr("The user will no longer be able to connect to your server.")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")
questionDrawer.yesButtonFunction = function() {
questionDrawer.close()
clientInfoDrawer.close()
root.revokeConfig(index)
}
questionDrawer.noButtonFunction = function() {
questionDrawer.close()
}
questionDrawer.open()
}
}
}
}
}
}
}
QuestionDrawer {
id: questionDrawer
}
}
}
MouseArea {
anchors.fill: parent
onPressed: function(mouse) {
forceActiveFocus()
mouse.accepted = false
}
}
}

View File

@@ -0,0 +1,155 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Components"
PageType {
id: root
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
}
FlickableType {
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
contentHeight: content.height
ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 16
anchors.leftMargin: 16
spacing: 0
HeaderType {
Layout.fillWidth: true
Layout.topMargin: 24
headerText: qsTr("Full access to the server and VPN")
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
text: qsTr("We recommend that you use full access to the server only for your own additional devices.\n") +
qsTr("If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. ")
color: "#878B91"
}
DropDownType {
id: serverSelector
signal severSelectorIndexChanged
property int currentIndex: 0
Layout.fillWidth: true
Layout.topMargin: 16
drawerHeight: 0.4375
descriptionText: qsTr("Server")
headerText: qsTr("Server")
listView: ListViewWithRadioButtonType {
id: serverSelectorListView
rootWidth: root.width
imageSource: "qrc:/images/controls/check.svg"
model: SortFilterProxyModel {
id: proxyServersModel
sourceModel: ServersModel
filters: [
ValueFilter {
roleName: "hasWriteAccess"
value: true
}
]
}
currentIndex: 0
clickedFunction: function() {
handler()
if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) {
serverSelector.currentIndex = serverSelectorListView.currentIndex
}
shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text
serverSelector.menuVisible = false
}
Component.onCompleted: {
handler()
}
function handler() {
serverSelector.text = selectedText
ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex)
}
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 40
text: qsTr("Share")
imageSource: "qrc:/images/controls/share-2.svg"
onClicked: function() {
shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text
shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text
shareConnectionDrawer.needCloseButton = false
shareConnectionDrawer.open()
shareConnectionDrawer.contentVisible = false
PageController.showBusyIndicator(true)
if (Qt.platform.os === "android") {
ExportController.generateFullAccessConfigAndroid();
} else {
ExportController.generateFullAccessConfig();
}
PageController.showBusyIndicator(false)
shareConnectionDrawer.needCloseButton = true
PageController.showTopCloseButton(true)
shareConnectionDrawer.contentVisible = true
}
}
ShareConnectionDrawer {
id: shareConnectionDrawer
}
}
}
}

View File

@@ -44,9 +44,9 @@ PageType {
tabBar.enabled = !visible
}
function onShowTopCloseButton(visible) {
topCloseButton.visible = visible
}
// function onShowTopCloseButton(visible) {
// topCloseButton.visible = visible
// }
function onEnableTabBar(enabled) {
tabBar.enabled = enabled
@@ -137,10 +137,10 @@ PageType {
tabBarStackView.push(pagePath, { "objectName" : pagePath })
}
onWidthChanged: {
topCloseButton.x = tabBarStackView.x + tabBarStackView.width -
topCloseButton.buttonWidth - topCloseButton.rightPadding
}
// onWidthChanged: {
// topCloseButton.x = tabBarStackView.x + tabBarStackView.width -
// topCloseButton.buttonWidth - topCloseButton.rightPadding
// }
}
TabBar {
@@ -234,11 +234,12 @@ PageType {
z: 1
}
TopCloseButtonType {
id: topCloseButton
x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding
z: 1
}
// TopCloseButtonType {
// id: topCloseButton
// x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding
// z: 1
// }
ConnectionTypeSelectionDrawer {
id: connectionTypeSelection

View File

@@ -68,7 +68,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
}
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
@@ -227,12 +227,15 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser
configData = lastVpnConfig.value(proto);
configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData);
} else {
configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, errorCode);
QString clientId;
configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, clientId, errorCode);
if (errorCode && *errorCode) {
return "";
}
emit m_configurator->newVpnConfigCreated(clientId, "unnamed client", container, credentials);
QString configDataBeforeLocalProcessing = configData;
configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData);
@@ -323,7 +326,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
ErrorCode e = ErrorCode::NoError;
m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e);
emit newVpnConfigurationCreated();
if (e) {
emit connectionStateChanged(Vpn::ConnectionState::Error);
return;
@@ -375,6 +378,12 @@ void VpnConnection::appendSplitTunnelingConfig()
sitesJsonArray.append(site);
}
// Allow traffic to Amezia DNS
if (routeMode == Settings::VpnOnlyForwardSites){
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
}
m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode);
m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray);
}

View File

@@ -79,8 +79,6 @@ signals:
void serviceIsNotReady();
void newVpnConfigurationCreated();
protected slots:
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void onConnectionStateChanged(Vpn::ConnectionState state);

View File

@@ -96,16 +96,16 @@ if [ "${MAC_CERT_PW+x}" ]; then
security find-identity -p codesigning
echo "Signing App bundle..."
/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $BUNDLE_DIR
/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $BUNDLE_DIR
/usr/bin/codesign --verify -vvvv $BUNDLE_DIR || true
spctl -a -vvvv $BUNDLE_DIR || true
if [ "${NOTARIZE_APP+x}" ]; then
echo "Notarizing App bundle..."
/usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip
xcrun altool --notarize-app -f $PROJECT_DIR/Bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD
xcrun notarytool submit $PROJECT_DIR/Bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD
rm $PROJECT_DIR/Bundle_to_notarize.zip
sleep 600
sleep 300
xcrun stapler staple $BUNDLE_DIR
xcrun stapler validate $BUNDLE_DIR
spctl -a -vvvv $BUNDLE_DIR || true
@@ -130,15 +130,15 @@ $QIF_BIN_DIR/binarycreator --offline-only -v -c $BUILD_DIR/installer/config/maco
if [ "${MAC_CERT_PW+x}" ]; then
echo "Signing installer bundle..."
security unlock-keychain -p $TEMP_PASS $KEYCHAIN
/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $INSTALLER_BUNDLE_DIR
/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $INSTALLER_BUNDLE_DIR
/usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true
if [ "${NOTARIZE_APP+x}" ]; then
echo "Notarizing installer bundle..."
/usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip
xcrun altool --notarize-app -f $PROJECT_DIR/Installer_bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD
xcrun notarytool submit $PROJECT_DIR/Installer_bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD
rm $PROJECT_DIR/Installer_bundle_to_notarize.zip
sleep 600
sleep 300
xcrun stapler staple $INSTALLER_BUNDLE_DIR
xcrun stapler validate $INSTALLER_BUNDLE_DIR
spctl -a -vvvv $INSTALLER_BUNDLE_DIR || true
@@ -151,13 +151,13 @@ hdiutil create -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app
if [ "${MAC_CERT_PW+x}" ]; then
echo "Signing DMG installer..."
security unlock-keychain -p $TEMP_PASS $KEYCHAIN
/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $DMG_FILENAME
/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $DMG_FILENAME
/usr/bin/codesign --verify -vvvv $DMG_FILENAME || true
if [ "${NOTARIZE_APP+x}" ]; then
echo "Notarizing DMG installer..."
xcrun altool --notarize-app -f $DMG_FILENAME -t osx --primary-bundle-id $APP_DOMAIN -u $APPLE_DEV_EMAIL -p $APPLE_DEV_PASSWORD
sleep 600
xcrun notarytool submit $DMG_FILENAME --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD
sleep 300
xcrun stapler staple $DMG_FILENAME
xcrun stapler validate $DMG_FILENAME
fi