Compare commits

...

95 Commits

Author SHA1 Message Date
Yaroslav 2ee61a040b fix: iOS appstore publish fix (#1922) 2025-11-04 12:10:30 +08:00
vkamn 741b5cc0f9 fix: qt6 9 support (#1973)
* Fix qt 6.9 support

* add support android sdk 36

* feat: add support SafeMargins from Android

* Fix black screen

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2025-11-04 11:43:36 +08:00
MrMirDan aaf0e070dc fix: hide description (#1959) 2025-11-03 10:27:01 +08:00
vkamn e0e126eda8 chore: bump version (#1969) 2025-11-03 10:26:33 +08:00
vkamn 236daf6b3b feat: ad label (#1966)
* refactor: ad label desing refatroing

* feat: add ad label settings processing

* chore: fix ru translations

* chore: minor fixes
2025-11-03 10:26:22 +08:00
vkamn f1481b1b1f feat: add async post in gateway controller (#1963) 2025-10-29 23:24:24 +08:00
vkamn f6e7d3ccf1 fix: minor ui fixes (#1917)
* feat: improve storage processing

* fix: minor ui fixes
2025-10-09 23:22:58 +08:00
Mitternacht822 a754a11913 fix: added displaying vpn_key field added in older version of the app (#1873)
* fix(api_key): added displaying vpn_key field added in older version of the app

* revert changes

* fix: implemented generation of api key text for PremiumV2

* fix: deleted unnecessary code

* saving apikey text when generating

* added method for vpn key export, fixed wrong saving file
2025-10-07 23:16:28 +08:00
vkamn 4d25e3b6f6 chore: minor bugfixes (#1915) 2025-10-07 23:15:06 +08:00
MrMirDan 1fac280497 fix: main app info added after clearing logs (#1913) 2025-10-06 21:07:04 +08:00
Yaroslav c886c5e6a7 feat: enhance OpenVPN configuration handling and logging for iOS plat… (#1910)
* feat: enhance OpenVPN configuration handling and logging for iOS platform

* refactor: remove $OPENVPN_TA_KEY_SANITIZED and use $OPENVPN_TA_KEY instead
2025-10-06 21:04:49 +08:00
aiamnezia cd7f78b9ca feat: news and notifications page (#1660)
* Add news and notifications

* Add localization for news and notifications

* Remove news caching

* Add fetching news befor openning news page

* Fix not updating news page

* Delete debug output

* Remove news and notificztions with only self-hosted servers

* Add stack filters to fetching news request

* Add fetching news with changing stack in the client

* small refactoring

* polishing

* Rename newsModel files and fix naming in code

* fix: remove custom signals; fetch news only on stack expansion

* chore: delete unnecessary code

* chore: code style fixes

* fix: fixed memory leak in gateway controller

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-10-06 12:06:36 +08:00
vkamn a587d3230f fix: again fixed site link for features field (#1908) 2025-10-06 11:38:57 +08:00
MrMirDan 93e7b45136 fix: removed 'clear site list' button icon (#1909) 2025-10-06 11:37:42 +08:00
vkamn e024f71ce1 fix: allow remove expired api configs (#1907) 2025-10-03 14:45:12 +08:00
MrMirDan 50d1be7b4a chore: update for RU translation (#1893) 2025-10-02 20:59:45 +08:00
MrMirDan 3ec6d8973b fix: warning visible only on windows (#1900) 2025-10-02 20:59:23 +08:00
Yaroslav Gurov 3ea47d31a9 fix: restore dns after using xray (#1902) 2025-10-02 20:58:53 +08:00
vkamn 30c8cc4548 feat: add isConnectEvent field to api request (#1896) 2025-09-30 12:10:27 +08:00
vkamn 98586d2dd9 fix: fixed site link (#1897) 2025-09-30 12:07:27 +08:00
vkamn c66d8ecca0 chore: bump version (#1892) 2025-09-29 11:07:27 +08:00
vkamn db535f7e7d chore: increase default values (#1891) 2025-09-29 11:05:30 +08:00
vkamn 89f30d8c31 fix: fixed native wg obfuscation (#1890) 2025-09-29 10:58:44 +08:00
Yaroslav 8bce432824 fix: enable paste from clipboard on ios in addition to android (#1868) 2025-09-29 10:56:41 +08:00
MrMirDan f3539b2632 fix: proper wl name on connection key page (#1867)
* fix: proper wl name on connection key page

* some changings

* little change

* added missing import

* fix: proper wl default filename
2025-09-29 10:55:53 +08:00
MrMirDan 7a96c212f3 fix: rename user in search (#1847) 2025-09-29 10:51:52 +08:00
MrMirDan 2d5dc54e0f fix: keyboard navigation for text fields (#1879) 2025-09-29 10:50:57 +08:00
MrMirDan cef4c262e9 fix: keyboard fix for api 'connection key' buttons (#1872) 2025-09-29 10:50:18 +08:00
MrMirDan 34309261a8 fix: scrollbar always visible (#1877)
* fix: scrollbar always visible

* fix: scrollbar always visible on app split tunneling page
2025-09-29 10:49:19 +08:00
MrMirDan 657eeb40c7 fix: mirror error code link (#1863)
* fix: mirror error code link

* remake
2025-09-29 10:48:36 +08:00
MrMirDan b4938c2cc9 fix: default lang matching between app and OS (#1855)
* fix: default lang matching between app and OS

* remake

* fix: set default lang value
2025-09-29 10:47:54 +08:00
MrMirDan 524fefc5cb feat: warning on app split tunneling for windows (#1880) 2025-09-29 10:45:14 +08:00
Yaroslav 73f13404bb feat: add support for multiple scenes and handle URL contexts in iOS 13+ (#1889) 2025-09-29 10:40:58 +08:00
MrMirDan 5fc68cca83 fix: split tunneling restoration from backup (#1835) 2025-09-15 10:55:18 +08:00
Mitternacht822 fcb7b8fa8d fix: save/restore AmneziaDNS state (#1833) 2025-09-15 10:54:34 +08:00
aiamnezia a81e32ff95 fix: clean service/client logs in uninstall scripts (#1846)
- Windows (x64/x86):
  - Remove delegation to `AmneziaVPN.exe -c`
  - Delete `%ProgramData%\AmneziaVPN\log\AmneziaVPN-service.log`
  - Delete current user logs at `%AppData%\AmneziaVPN.ORG\AmneziaVPN\log`
  - Remove empty parent dirs (app/org, log)

- Linux:
  - Delete only `/var/log/AmneziaVPN/AmneziaVPN-service.log` (preserve `post-uninstall.log`)
  - Delete current user logs at `$HOME/.local/share/AmneziaVPN.ORG/AmneziaVPN/log`
2025-09-15 10:53:51 +08:00
albexk c897052107 chore: bump version (#1850) 2025-09-10 19:28:36 +08:00
vkamn 4d0efc7ea5 fix: remove duplicate m_vpnConnection delete from AmneziaApplication destructor (#1848) 2025-09-10 15:01:52 +08:00
Ivan a77842c9e3 feat: add server diagnostics script (#1837)
Co-authored-by: Ivan Istomin <istomin-ms@yandex.ru>
2025-09-09 19:33:35 +08:00
Mitternacht822 0ded9db780 refactor: use QCommandLineOption members for autostart/cleanup (#1820)
* refactor(app options): use QCommandLineOption members for autostart/cleanup

* fix(app): initialize QCommandLineOption members in ctor/field to avoid no-default-ctor build failures
2025-09-03 12:03:45 +08:00
Mitternacht822 58d480fcb5 fix: moved startMinimized to Q_Property (#1819) 2025-09-03 12:03:10 +08:00
aiamnezia 7154428d26 fix: sharing QR code size (#1830) 2025-09-03 11:58:36 +08:00
MrMirDan 02a52d0169 fix: full config default filename (#1831) 2025-09-03 11:57:30 +08:00
MrMirDan ec60764072 fix: rename/revoke user while in search on share page (#1787)
* fix: revoke user config

* fix: user renaming

* fix: revoke signal

* some fixes

* remaded fix
2025-09-03 11:56:08 +08:00
MrMirDan 17d2fa5532 fix: premium key duplication (#1818)
* ru translation fix

* crc saving

* little fix

* updated crc saving

* fix: added comparison by key

* remaded fix
2025-09-03 11:54:11 +08:00
MrMirDan 3ca8b534e8 fix: go to home page after first protocol manual installation (#1829) 2025-09-03 11:52:45 +08:00
MrMirDan e88f7c5e46 fix: index assignment (#1821) 2025-09-02 13:03:05 +08:00
MrMirDan 3ac5d7bd1f chore: ru translation update (#1815) 2025-08-27 18:37:43 +08:00
vkamn 19cad00a00 fix: minor ui fixes (#1817)
* fix: minor ui fixes with services list

* fix: fix page share connection headers and config description
2025-08-27 16:42:28 +08:00
vkamn 1ea716a163 fix: fix page share connection headers and config description 2025-08-27 16:41:20 +08:00
vkamn 4551659c2a fix: minor ui fixes with services list 2025-08-27 15:15:53 +08:00
MrMirDan c568bf8c24 chore: ru translation update (#1812)
* ru translation update

* fixes
2025-08-26 20:32:00 +08:00
vkamn a412d91105 feat: subscription expiration processing (#1814) 2025-08-26 20:31:41 +08:00
vkamn ad01f23bbe feat: add service description customization (#1811) 2025-08-26 12:17:37 +08:00
vkamn 656070b132 feat: add request id (#1809) 2025-08-25 22:05:00 +08:00
MrMirDan c907f5ca36 fix: removed service logs section for mobile platforms (#1810) 2025-08-25 22:04:48 +08:00
Mykola Baibuz 94a13b2b54 fix: set guid for windows tun2socks tun interface (#1808) 2025-08-25 11:03:42 +08:00
MrMirDan 169f11d9c7 chore: added trimming I's and J's params on save (#1774)
* trimming params on save

* removed unused code
2025-08-21 12:29:22 +08:00
vkamn 816dc3af95 feat: add ping before request to proxy (#1805) 2025-08-21 12:28:03 +08:00
Mykola Baibuz b802863de5 fix: check for empty secondary DNS (#1799) 2025-08-20 14:19:22 +08:00
vkamn 8dc2a4b76c fix: fixed switcher behavior (#1801) 2025-08-20 13:01:09 +08:00
vkamn beb1c6dbf2 feat: added cache for proxy bypass (#1797) 2025-08-20 13:00:35 +08:00
vkamn 3eb06916c7 chore: bump version (#1802)
* chore: bump version

* fix: fixed ios build
2025-08-20 13:00:20 +08:00
Cyril Anisimov 30d0f84a4f fix: fixed focus view and reverse focus change in headers (#1791)
* fix: add view movement on changing the focus in backwards direction

* fix: return value in isFirstFocusItemInHeader function
2025-08-20 12:59:57 +08:00
Mykola Baibuz 251f2aa5db fix: remove double disconnect for Win IPSec (#1800) 2025-08-20 12:58:39 +08:00
Nethius 16d92ddb7c fix: UI fixes after merge with d20ed4a (#1779)
* fix: ui fixes after merge with d20ed4a

* update OpenVPN settings page

* chore: page settings dns margins

---------

Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-08-11 13:40:28 +08:00
Cyril Anisimov e9d4fd8482 fix checkbox switch (#1777) 2025-08-10 11:13:58 +08:00
Yaroslav 9fdcf5ab13 feat: macos with network extension Implementation (#1468)
* There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package
ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339

* Cmake related changes

* Source code changes

* Various entitlements

* Ci-cd config update

* Resources changes

* Submodules updated

* Remove me

* QtWidget exclusion omitted

* Distribution errors fixed

* Outdated files deleted

* macos_ne cmake fixed

* fix: update provisioning profile specifiers for macOS network extension

* fix: update provisioning profile specifiers and code sign flags for macOS build

* Revert me
(temporary 3rd-build commit pointer)

* fix: Welcome screen fix

* fix: ci/cd hanging forever fix

* fix: Fixed error popup on macos on file save

* refactor: rename networkextension target to AmneziaVPNNetworkExtension in macos build configuration

* feat: add autostart support for Mac App Store builds on macOS

Fixes: QA-8

* feat: add debug logging to Autostart functionality on macOS

* Revert "feat: add autostart support for Mac App Store builds on macOS"

This reverts commit 3bd25656fb.

* feat: add platform-specific close window behavior for macOS App Store build with Network Extension

Closes: QA-12

* When the application starts with "Start minimized" enabled on macOS (especially the
sandboxed App-Store build compiled with MACOS_NE), fully hiding the window prevents it
from being restored by clicking the Dock icon. The proper behaviour is to start the
window in the *minimized* state instead. That way the window is still part of the
window list and the system automatically brings it back when the user clicks the Dock
icon, replicating the native experience.

On the other platforms we keep the old behaviour (hide the window completely and rely
on the tray icon), therefore we switch at runtime by checking the current OS.

Closes: QA-7

Closes: QA-8

* Revert "When the application starts with "Start minimized" enabled on macOS (especially the"

This reverts commit 7b0d17987c.

* feat: MACOS_NE systray menu support

* feat: add macOS notification handler and install event filter on main window

* feat: implement custom close behavior for Amnezia application on different platforms

* fix: update provisioning profile specifiers for macos builds

* fix: Fatal error in logs

CLI-216

* fix: disabled unavailable on macos ne service logs

* fix: dock icon now hides only when window is closed; menubar icon shows always

Initial state of the docker icon to be presented follows "Start minimized" setting in app settings.

* temp-fix: temporary disable all OpenVPN options of VPN on MACOS_NE since it's not working yet.

* fix: build script updated

* feat: add macOS NE build workflow to GitHub Actions

* fix: Not working Auto start toggle is hidden

* fix: Log spamming during xray connection fixed

* 3rd-prebuild points to commit that stores macos_ne universal binaries.

* fix: missing native dependency on linking stage fixed

* chore: update link to submodule

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-10 11:12:19 +08:00
serj95reg a6e6de33c8 feat: updated xray version in dockerfile to 25.8.3 (#1771) 2025-08-08 10:34:51 +08:00
Mitternacht822 53c7fd4d81 fix: android build (#1768)
* added signal-slot connection between corecontroller and systemtraynofificationhandler updating websiteurl

* cleared up the commented lines

* fixed andorid includes for systemtraynotificationhandler
2025-08-07 11:12:09 +08:00
Nethius 2608ea4367 chore: fix typo (#1769) 2025-08-06 11:00:43 +08:00
Cyril Anisimov d20ed4ad01 refactoring: improved stability of focus controller (#1464)
* change position view mode

* remove `parentFlickable` from `PageShare`

* replace `FlickableType` with `ListViewType` in `PageSettings`

* reorganize `PageSettingsAbout` for improved structure

* replace `Flickable` with `ListViewType` in drawer in `PageSettingsApiNativeConfigs`

* replace `FlickableType` with `ListViewType` in `PageSettingsApplication` and update layout structure

* replace `FlickableType` with `ListViewType` in `PageSettingsAppSplitTunneling` and adjust layout for better structure

* replace `FlickableType` with `ListViewType` in `PageSettingsBackup`

* replace `FlickableType` with `ListViewType` in `PageSettingsConnection`

* replace `FlickableType` with `ListViewType` in `PageSettingsDns`

* replace `FlickableType` with `ListViewType` in `PageSettingsLogging`

* replace `FlickableType` with `ListViewType` in `PageSettingsServerData`

* update structure of `PageSettingsServerProtocol`

* update `PageSettingsServersList`

* replace `ListView` with `ListViewType` in `PageSettingsSplitTunneling`

* replace `FlickableType` with `ListViewType` in `PageServiceDnsSettings`

* update `PageServiceSftpSettings`

* update `PageServiceSocksProxySettings`

* replace `FlickableType` with `ListViewType` in `PageServiceTorWebsiteSettings`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardApiServiceInfo`

* update `PageSetupWizardApiServicesList`

* replace `ListView` with `ListViewType` in `PageSetupWizardConfigSource`

* replace `ListView` with `ListViewType` in `PageSetupWizardCredentials`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardEasy`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardInstalling`

* replace `ListView` with `ListViewType` in `PageSetupWizardProtocols`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardProtocolSettings`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardTextKey`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardViewConfig`

* update `PageProtocolAwgClientSettings`

* update `PageProtocolAwgSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolCloakSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolRaw`

* replace `FlickableType` with `ListViewType` in `PageProtocolShadowSocksSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolWireGuardClientSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolWireGuardSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolXraySettings`

* replace `FlickableType` with `ListViewType` in `PageShareFullAccess`

* replace `FlickableType` with `ListViewType` in `PageDeinstalling`

* update `PageDevMenu`

* remove `Flickable` references in `LabelWithButtonType`

* remove useless key navigation handlers from `ListViewType`

* replace `ListView` with `ListViewType` in `ListViewWithRadioButtonType.qml` and remove unnecessary properties

* remove references to `Flickable` in `TextAreaType.qml`

* remove references to `Flickable` in `TextAreaWithFooterType`

* remove references to `FlickableType` in `TextFieldWithHeaderType`

* remove references to `FlickableType` in `SwitcherType`

* remove references to `FlickableType` in `CheckBoxType`

* remove references to `FlickableType` in `CardWithIconsType.qml`

* remove references to `FlickableType` in `BasicButtonType.qml`

* update `ServersListView`

* update `SettingsContainersListView`

* update `InstalledAppsDrawer`

* update `SelectLanguageDrawer`

* update `HomeContainersListView`

* update `HomeSplitTunnelingDrawer`

* fix `PageSetupWizardApiServicesList`

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-06 10:35:51 +08:00
KsZnak eae2936449 Update README links.md [no ci]
Update README links.md
2025-08-04 19:35:45 +01:00
KsZnak da8ad1f6ba UTM added.md [no ci]
Update README_RU.md
2025-08-04 19:34:12 +01:00
Mitternacht822 5472347969 feature: added warning label when config files have changed in premium configuration files menu (#1718)
* added warning label when config files have changed in premium configuration files display

* moved warning display from PageSettingsApiNativeConfigs.qml to PageSettingsApiServerInfo.qml
2025-08-04 14:13:22 +08:00
Mitternacht822 a43f7a6926 feat: added vpn key to subscription settings page (#1717)
* added subscription key display element to subscription management page

* refactrored KeySubscription item to a new page

* minor fix

* changed PageShareDrawer into PageShareConnection

* added back button

* Removed deprecated ShareConnectionDrawer and migrated to PageShareConnection

* fixed issue when show-connection settings button was not working

* deleted empty lines

* minor fix
2025-08-01 21:02:12 +08:00
Cyril Anisimov 47f917de0b refactoring: change logs time to UTC (#1578)
* update logger to show utc

* add logger to `FocusController`

* add utc timestamps to android logs
2025-08-01 11:56:16 +08:00
Cyril Anisimov dbeb7edd7a refactor: update ScrollBar policy to use AsNeeded for better usability (#1579)
* refactor: update `ScrollBarType` policy to use
`AsNeeded` for better usability

* add selecting of location settings with Enter

* add handlers to enter push
2025-08-01 11:56:02 +08:00
Mitternacht822 6cede712f5 fix: backup contains platform specific variables (#1646)
* fixed issue with restoring wrong platform specific variables in backup

* fixed wrong line

* fixed issue when restong app split tunneling mode not intended for windows platform

* added field containing application platform to backup file, added feature to clear appsSplitTunneling list from backup file if backup was made on other platform
2025-08-01 11:54:58 +08:00
Mitternacht822 d328739192 fix: add update model after clear profile (#1674)
* fixed issue when ui was not getting update about clearing profile cache right after it

* fixed the problem of not clearing the profile

* refactored reload function in protolocolsModel

* refactored the issue with signal connect in corecontroller
2025-08-01 11:52:11 +08:00
Mitternacht822 d15c0bd962 fix: fixed system tray open site link (#1686)
* added signal-slot connection between corecontroller and systemtraynofificationhandler updating websiteurl

* cleared up the commented lines
2025-08-01 11:50:31 +08:00
Mitternacht822 d53c794936 fix: fixed language load after settings reset (#1735) 2025-08-01 11:47:43 +08:00
Mitternacht822 e5dcb25a4a fix: removed the ability to change location while making connection (#1736) 2025-08-01 11:45:19 +08:00
Mitternacht822 f9002b4f43 refactoring: made start-minimized-option available only when autostart-option is truned on (#1740) 2025-08-01 11:38:36 +08:00
Nethius 0531508a75 feat: added 'clear site list' button (#1747) (#1753)
* feat: added 'clear site list' button (#1747)

* chore: rename 'Export/Import Sites' to 'Additional options'

---------

Co-authored-by: MrMirDan <58086007+MrMirDan@users.noreply.github.com>
2025-08-01 11:37:56 +08:00
Mitternacht822 174e85a20a fix: not restoring parameters for open vpn after scanning server (#1759)
* added lines for restoring settings when scanning server for OpenVPN, OpenVPN over Cloak and OpenVPN over SS protocols

* minor fix

* added functionality to restore config for multiprotocol configsCloak and Shadowsocks
2025-08-01 11:36:52 +08:00
MrMirDan e9abb6f1e2 fix: mirror links (#1760)
* Instructions links

* amnezia free feature link

* trying fix api instructions page issue

* androidTV link fix

* tv link fix 2
2025-08-01 11:36:30 +08:00
Nethius 5be44f9596 chore: bump version (#1757)
* chore: update link to submodule

* chore: bump version
2025-07-29 12:20:43 +08:00
vladimir.kuznetsov 90efaaff92 chore: bump version 2025-07-29 12:19:54 +08:00
vladimir.kuznetsov 99b554e7c3 chore: update link to submodule 2025-07-29 12:19:27 +08:00
Nethius ac0ce8a6f6 chore: bump version (#1746) 2025-07-25 23:21:18 +08:00
Yaroslav 9f9da885b7 fix: bundle version added, icon returned (#1745) 2025-07-25 23:03:11 +08:00
Nethius f51fd2bf3e chore: update link to submodule (#1738) 2025-07-24 10:13:14 +08:00
Nethius c8378fd32d chore: update link to submodule (#1733) 2025-07-22 19:50:57 +08:00
Nethius d767214f10 chore: fixed amneziavpn-service version (#1726) 2025-07-17 15:22:21 +08:00
235 changed files with 11828 additions and 8323 deletions
+64 -3
View File
@@ -358,7 +358,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -402,14 +402,75 @@ jobs:
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-Android:
runs-on: ubuntu-latest
env:
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.7.3
ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.9.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
+1
View File
@@ -9,6 +9,7 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
+11 -5
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.9.0)
set(AMNEZIAVPN_VERSION 4.8.11.1)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2090)
set(APP_ANDROID_VERSION_CODE 2096)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -32,13 +32,19 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
+5 -5
View File
@@ -9,17 +9,17 @@
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
+4 -4
View File
@@ -6,16 +6,16 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
+18 -8
View File
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -53,6 +52,9 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
@@ -110,6 +112,15 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
@@ -139,7 +150,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -158,7 +169,6 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
endif()
if(LINUX AND NOT ANDROID)
@@ -166,8 +176,7 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -178,7 +187,9 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND NOT IOS)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
@@ -199,7 +210,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -214,7 +225,6 @@ if(NOT IOS AND NOT ANDROID)
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
+48 -19
View File
@@ -12,6 +12,7 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -21,8 +22,12 @@
#include "platforms/ios/QRCodeReaderBase.h"
#include "protocols/qml_register_protocols.h"
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs"))
{
setQuitOnLastWindowClosed(false);
@@ -49,7 +54,6 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
@@ -63,15 +67,28 @@ void AmneziaApplication::init()
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -94,7 +111,7 @@ void AmneziaApplication::init()
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet("a"))
if (m_parser.isSet(m_optAutostart))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
@@ -162,15 +179,12 @@ bool AmneziaApplication::parseCommands()
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
if (m_parser.isSet(m_optCleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
@@ -179,9 +193,8 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
@@ -198,6 +211,22 @@ void AmneziaApplication::startLocalServer()
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
+10 -5
View File
@@ -7,9 +7,9 @@
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#include <QGuiApplication>
#else
#include <QApplication>
#include <QApplication>
#endif
#include <QClipboard>
@@ -20,9 +20,9 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
@@ -56,10 +56,15 @@ private:
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H
+1
View File
@@ -46,6 +46,7 @@
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:enableOnBackInvokedCallback="false"
android:exported="true">
<intent-filter>
+3
View File
@@ -6,6 +6,9 @@
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
</style>
<style name="Translucent" parent="NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
@@ -35,6 +35,7 @@ import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
@@ -170,10 +171,9 @@ class AmneziaActivity : QtActivity() {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity")
loadLibs()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
// Configure window for edge-to-edge display
configureWindowForEdgeToEdge()
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto
@@ -265,6 +265,49 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
postDelayed({
sendTouch(1f, 1f)
}, 100)
postDelayed({
sendTouch(2f, 2f)
}, 200)
postDelayed({
requestLayout()
invalidate()
}, 250)
}
}
}
private fun configureWindowForEdgeToEdge() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
statusBarColor = android.graphics.Color.TRANSPARENT
navigationBarColor = android.graphics.Color.TRANSPARENT
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
}
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
@@ -666,6 +709,43 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
@Suppress("unused")
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
@Suppress("unused")
fun getStatusBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun getNavigationBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun startQrCodeReader() {
Log.v(TAG, "Start camera")
@@ -38,15 +38,15 @@ object AppListProvider {
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0
val icon: Boolean = (ai?.icon ?: 0) != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
val name = ai?.loadLabel(pm)?.toString()
this.name = name?.takeIf { it != packageName }
}
override fun compareTo(other: App): Int {
+4 -2
View File
@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
import java.time.ZoneOffset
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -135,8 +137,8 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
+9 -3
View File
@@ -27,9 +27,15 @@ if(WIN32)
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif()
elseif(APPLE AND NOT IOS)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
+3 -3
View File
@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 26)
set(APP_ANDROID_MIN_SDK 28)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE)
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
QT_ANDROID_TARGET_SDK_VERSION 34
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
QT_ANDROID_TARGET_SDK_VERSION 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
+1
View File
@@ -46,6 +46,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
)
+8 -3
View File
@@ -14,11 +14,15 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USERNOTIFICATIONS}
${FW_USER_NOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
)
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
@@ -31,6 +35,8 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -49,4 +55,3 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
+168
View File
@@ -0,0 +1,168 @@
message("Client ==> MacOS NE build")
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"
)
+16 -2
View File
@@ -39,7 +39,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -89,12 +89,26 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/macos_util.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
+21 -3
View File
@@ -83,12 +83,30 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
return "";
}
auto sanitizeStaticKey = [](const QString &key) {
QStringList lines = key.split('\n');
QStringList filtered;
filtered.reserve(lines.size());
for (const QString &line : lines) {
const QString trimmed = line.trimmed();
if (trimmed.startsWith('#')) {
continue;
}
filtered.append(line);
}
QString result = filtered.join('\n');
if (!result.endsWith('\n')) {
result.append('\n');
}
return result;
};
config.replace("$OPENVPN_CA_CERT", connData.caCert);
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
if (config.contains("$OPENVPN_TA_KEY")) {
config.replace("$OPENVPN_TA_KEY", connData.taKey);
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
} else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
@@ -117,7 +135,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
@@ -131,7 +149,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
#endif
+5 -4
View File
@@ -8,7 +8,7 @@
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX)
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
+18 -1
View File
@@ -261,6 +261,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
return true;
#elif defined(Q_OS_IOS)
// Standard iOS build (without Network Extension limitations)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
@@ -269,7 +270,23 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Cloak: return true;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default: return false;
default:
return false;
}
#elif defined(MACOS_NE)
// macOS build using Network Extension hide OpenVPN-based containers
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::OpenVpn:
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks:
return false;
default:
return false;
}
#elif defined(Q_OS_MAC)
switch (c) {
+12
View File
@@ -47,12 +47,14 @@ namespace apiDefs
constexpr QLatin1String serverCountryName("server_country_name");
constexpr QLatin1String osVersion("os_version");
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count");
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
constexpr QLatin1String issuedConfigs("issued_configs");
constexpr QLatin1String subscriptionDescription("subscription_description");
constexpr QLatin1String supportInfo("support_info");
constexpr QLatin1String email("email");
@@ -64,6 +66,16 @@ namespace apiDefs
constexpr QLatin1String id("id");
constexpr QLatin1String orderId("order_id");
constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String userCountryCode("user_country_code");
constexpr QLatin1String serviceInfo("service_info");
constexpr QLatin1String isAdVisible("is_ad_visible");
constexpr QLatin1String adHeader("ad_header");
constexpr QLatin1String adDescription("ad_description");
constexpr QLatin1String adEndpoint("ad_endpoint");
}
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
+61 -13
View File
@@ -23,7 +23,7 @@ namespace
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTime();
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now;
}
@@ -82,7 +82,9 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
}
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody)
{
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
@@ -90,21 +92,19 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
return amnezia::ErrorCode::ApiConfigSslError;
} else if (reply->error() == QNetworkReply::NoError) {
} else if (replyError == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << reply->error();
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << replyError;
return amnezia::ErrorCode::ApiConfigTimeoutError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << reply->error();
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << replyError;
return amnezia::ErrorCode::ApiUpdateRequestError;
} else {
QString err = reply->errorString();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << replyErrorString;
qDebug() << httpStatusCode;
if (httpStatusCode == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError;
@@ -162,3 +162,51 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
}
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
return {};
}
QString vpnKeyText = "";
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
QString vpnKeyStr = "{";
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
vpnKeyStr += "}, ";
vpnKeyStr += "\"auth_data\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
vpnKeyStr += "}";
vpnKeyStr += "}";
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
vpnKeyCompressed = vpnKeyCompressed.mid(4);
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
return vpnKeyText;
}
+4 -1
View File
@@ -18,9 +18,12 @@ namespace apiUtils
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody);
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
}
#endif // APIUTILS_H
+21 -4
View File
@@ -26,9 +26,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
initNotificationHandler();
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
updateTranslator(m_settings->getAppLanguage());
}
void CoreController::initModels()
@@ -100,6 +99,9 @@ void CoreController::initModels()
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
m_newsModel.reset(new NewsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
}
void CoreController::initControllers()
@@ -120,6 +122,9 @@ void CoreController::initControllers()
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
connect(m_installController.get(), &InstallController::profileCleared,
m_protocolsModel.get(), &ProtocolsModel::updateModel);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
@@ -151,6 +156,9 @@ void CoreController::initControllers()
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
}
void CoreController::initAndroidController()
@@ -230,7 +238,7 @@ void CoreController::initSignalHandlers()
void CoreController::initNotificationHandler()
{
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -242,7 +250,10 @@ void CoreController::initNotificationHandler()
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -279,6 +290,7 @@ void CoreController::updateTranslator(const QLocale &locale)
m_engine->retranslate();
emit translationsUpdated();
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
}
void CoreController::initErrorMessagesHandler()
@@ -310,6 +322,11 @@ void CoreController::initContainerModelUpdateHandler()
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
if (m_serversModel->hasServersFromGatewayApi()) {
m_apiNewsController->fetchNews();
}
});
m_serversModel->resetModel();
}
+11 -2
View File
@@ -5,9 +5,14 @@
#include <QQmlContext>
#include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/systemtray_notificationhandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
#include "ui/controllers/connectionController.h"
@@ -43,8 +48,9 @@
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/newsModel.h"
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/notificationhandler.h"
#endif
@@ -61,6 +67,7 @@ public:
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
private:
void initModels();
@@ -92,7 +99,7 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
@@ -113,6 +120,7 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
@@ -120,6 +128,7 @@ private:
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<NewsModel> m_newsModel;
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
+189 -116
View File
@@ -7,6 +7,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPromise>
#include <QUrl>
#include "QBlockCipher.h"
@@ -50,85 +51,25 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
{
}
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
{
EncryptedRequestData encRequestData;
encRequestData.errorCode = ErrorCode::NoError;
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
QNetworkReply *reply;
reply = amnApp->networkManager()->get(request);
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
responseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
auto requestFunction = [&request, &responseBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->get(request);
};
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
const QList<QSslError> &nestedSslErrors) {
responseBody = nestedReply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
sslErrors = nestedSslErrors;
reply = nestedReply;
return true;
}
return false;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
return errorCode;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(endpoint.arg(m_gatewayEndpoint));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
@@ -137,14 +78,14 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
#endif
QSimpleCrypto::QBlockCipher blockCipher;
QByteArray key = blockCipher.generatePrivateSalt(32);
QByteArray iv = blockCipher.generatePrivateSalt(32);
QByteArray salt = blockCipher.generatePrivateSalt(8);
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
encRequestData.salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(key.toBase64());
keyPayload[configKey::aesIv] = QString(iv.toBase64());
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
@@ -159,24 +100,37 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
return ErrorCode::ApiMissingAgwPublicKey;
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto?
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv, "", encRequestData.salt);
} catch (...) {
Utils::logException();
qCritical() << "error when encrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
return encRequestData;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
return encRequestData.errorCode;
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
@@ -186,35 +140,44 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
wait.exec();
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
reply->deleteLater();
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
};
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = nestedReply->readAll();
reply = nestedReply;
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString();
replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
sslErrors = nestedSslErrors;
return false;
}
return true;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
if (errorCode) {
return errorCode;
}
try {
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
@@ -223,7 +186,58 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
}
}
QStringList GatewayController::getProxyUrls()
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
{
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
promise->start();
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
promise->finish();
return promise->future();
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) {
*sslErrors = errors;
});
connect(reply, &QNetworkReply::finished, reply, [=]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto errorCode = apiUtils::checkNetworkReplyErrors(*sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
QSimpleCrypto::QBlockCipher blockCipher;
try {
QByteArray responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
promise->finish();
} catch (...) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
}
});
return promise->future();
}
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
@@ -233,15 +247,26 @@ QStringList GatewayController::getProxyUrls()
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList proxyStorageUrls;
QStringList baseUrls;
if (m_isDevEnvironment) {
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QStringList proxyStorageUrls;
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
}
}
for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request);
@@ -286,7 +311,10 @@ QStringList GatewayController::getProxyUrls()
}
return endpoints;
} else {
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << replyError;
qDebug() << httpStatusCode;
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
@@ -295,33 +323,33 @@ QStringList GatewayController::getProxyUrls()
return {};
}
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody,
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt)
{
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred";
qDebug() << reply->error();
qDebug() << replyError;
return true;
} else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag";
return true;
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
qDebug() << reply->error();
qDebug() << replyError;
return true;
}
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << reply->error();
qDebug() << replyError;
return true;
}
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
qDebug() << reply->error();
} else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError;
return true;
} else if (checkEncryption) {
try {
@@ -335,29 +363,74 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
return false;
}
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
{
QStringList proxyUrls = getProxyUrls();
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QEventLoop wait;
QList<QSslError> sslErrors;
QByteArray responseBody;
for (const QString &proxyUrl : proxyUrls) {
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
QEventLoop wait;
QList<QSslError> sslErrors;
qDebug() << "go to the next proxy endpoint";
reply->deleteLater(); // delete the previous reply
reply = requestFunction(endpoint.arg(proxyUrl));
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (replyProcessingFunction(reply, sslErrors)) {
auto result = replyProcessingFunction(reply, sslErrors);
reply->deleteLater();
return result;
};
if (m_proxyUrl.isEmpty()) {
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
for (const QString &proxyUrl : proxyUrls) {
request.setUrl(proxyUrl + "lmbd-health");
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) {
reply->deleteLater();
m_proxyUrl = proxyUrl;
if (!m_proxyUrl.isEmpty()) {
break;
}
} else {
reply->deleteLater();
}
}
}
if (!m_proxyUrl.isEmpty()) {
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
return;
}
}
for (const QString &proxyUrl : proxyUrls) {
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
m_proxyUrl = proxyUrl;
break;
}
}
+21 -5
View File
@@ -1,8 +1,10 @@
#ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H
#include <QFuture>
#include <QNetworkReply>
#include <QObject>
#include <QPair>
#include "core/defs.h"
@@ -18,20 +20,34 @@ public:
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
private:
QStringList getProxyUrls();
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
struct EncryptedRequestData {
QNetworkRequest request;
QByteArray requestBody;
QByteArray key;
QByteArray iv;
QByteArray salt;
amnezia::ErrorCode errorCode;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
inline static QString m_proxyUrl;
};
#endif // GATEWAYCONTROLLER_H
+1
View File
@@ -120,6 +120,7 @@ namespace amnezia
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
// QFile errors
OpenError = 1200,
+1
View File
@@ -77,6 +77,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+2 -2
View File
@@ -23,7 +23,7 @@
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
@@ -390,7 +390,7 @@ QString NetworkUtilities::getGatewayAndIface()
close(sock);
return gateway_address;
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};
+2 -2
View File
@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_primaryDnsServer.isNull()) {
if (!m_primaryDnsServer.isEmpty()) {
QStringList dnsServers;
dnsServers.append(m_primaryDnsServer);
if (!m_secondaryDnsServer.isNull()) {
if (!m_secondaryDnsServer.isEmpty()) {
dnsServers.append(m_secondaryDnsServer);
}
// If the DNS is not the Gateway, it's a user defined DNS
+14
View File
@@ -0,0 +1,14 @@
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4_34)">
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
</g>
<defs>
<clipPath id="clip0_4_34">
<rect width="74" height="74" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 982 B

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<!-- Основа газеты -->
<rect x="4" y="4" width="16" height="16" rx="2"/>
<!-- Линии текста -->
<line x1="7" y1="8" x2="17" y2="8"/>
<line x1="7" y1="12" x2="17" y2="12"/>
<line x1="7" y1="16" x2="13" y2="16"/>
</svg>

After

Width:  |  Height:  |  Size: 410 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
</svg>

After

Width:  |  Height:  |  Size: 188 B

+26 -2
View File
@@ -32,17 +32,41 @@
<false/>
<key>UILaunchStoryboardName</key>
<string>AmneziaVPNLaunchScreen</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>QIOSWindowSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array/>
<key>UIRequiresFullScreen</key>
<true/>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array/>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>com.wireguard.ios.app_group_id</key>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+65 -3
View File
@@ -1,6 +1,68 @@
{
"info" : {
"author" : "xcode",
"version" : 1
"images": [
{
"idiom": "mac",
"size": "16x16",
"scale": "1x",
"filename": "16.png"
},
{
"idiom": "mac",
"size": "16x16",
"scale": "2x",
"filename": "16@2x.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "1x",
"filename": "32.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "2x",
"filename": "32@2x.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "1x",
"filename": "128.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "2x",
"filename": "128@2x.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "1x",
"filename": "256.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "2x",
"filename": "256@2x.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "1x",
"filename": "512.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "2x",
"filename": "512@2x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}
-50
View File
@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSMultipleInstancesProhibited</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>
+172
View File
@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>NSCameraUsageDescription</key>
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIcons</key>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Amnezia VPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>WireGuard config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>conf</string>
<string>cfg</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>OpenVPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>ovpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>AmneziaVPN backup file</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.backup-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>backup</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Amnezia VPN config</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<string>org.amnezia.AmneziaVPN.backup-config</string>
</array>
</dict>
</array>
<key>NSExtensions</key>
<array>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</array>
</dict>
</plist>
+27 -21
View File
@@ -2,34 +2,40 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-settings</string>
<string>relay</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
@@ -2,41 +2,30 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-settings</string>
<string>relay</string>
<string>packet-tunnel-provider</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
<string>app-proxy-provider</string>
</array>
<key>keychain-access-groups</key>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
<string>allow-vpn</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.private.network.socket-delegate</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,138 @@
enable_language(Swift)
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
# MACOSX_BUNDLE YES
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
)
if(DEPLOY)
message("DEPLOY is ON")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
)
else()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
target_sources(AmneziaVPNNetworkExtension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
message(${CLIENT_ROOT_DIR})
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
@@ -3,27 +3,32 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>org.amnezia.AmneziaVPN.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<string>${APPLE_PROJECT_VERSION}</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
@@ -31,5 +36,11 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
</dict>
</plist>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
@@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
// init function definition in C
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);
@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION
+2 -2
View File
@@ -15,7 +15,7 @@
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
bool isAnotherInstanceRunning()
{
QLocalSocket socket;
@@ -45,7 +45,7 @@ int main(int argc, char *argv[])
AmneziaApplication app(argc, argv);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
return app.exec();
+15 -15
View File
@@ -264,13 +264,13 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
/* && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
@@ -278,27 +278,27 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()*/) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
// json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
// json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
// json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
// json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
// json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
// json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
// json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
// json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
// json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
// json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
// json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
}
write(json);
@@ -202,6 +202,21 @@ bool AndroidController::isOnTv()
return callActivityMethod<jboolean>("isOnTv", "()Z");
}
bool AndroidController::isEdgeToEdgeEnabled()
{
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
}
int AndroidController::getStatusBarHeight()
{
return callActivityMethod<jint>("getStatusBarHeight", "()I");
}
int AndroidController::getNavigationBarHeight()
{
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
}
void AndroidController::startQrReaderActivity()
{
callActivityMethod("startQrCodeReader", "()V");
@@ -39,6 +39,9 @@ public:
QString getFileName(const QString &uri);
bool isCameraPresent();
bool isOnTv();
bool isEdgeToEdgeEnabled();
int getStatusBarHeight();
int getNavigationBarHeight();
void startQrReaderActivity();
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
@@ -0,0 +1,82 @@
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#include <dispatch/dispatch.h>
#include <QByteArray>
#include <QFile>
#include <QString>
#include "ios_controller.h"
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr;
static void amnezia_handleURL(NSURL *url)
{
if (!url || !url.isFileURL) {
return;
}
QString filePath(url.path.UTF8String);
if (filePath.isEmpty()) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (filePath.contains("backup")) {
IosController::Instance()->importBackupFromOutside(filePath);
return;
}
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
const QByteArray data = file.readAll();
IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data));
});
}
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
{
if (g_originalSceneOpenURLContexts) {
g_originalSceneOpenURLContexts(self, _cmd, scene, contexts);
}
if (!contexts || contexts.count == 0) {
return;
}
if (@available(iOS 13.0, *)) {
for (UIOpenURLContext *context in contexts) {
amnezia_handleURL(context.URL);
}
}
}
@interface AmneziaSceneDelegateHooks : NSObject
@end
@implementation AmneziaSceneDelegateHooks
+ (void)load
{
Class cls = objc_getClass("QIOSWindowSceneDelegate");
if (!cls) {
return;
}
SEL selector = @selector(scene:openURLContexts:);
Method method = class_getInstanceMethod(cls, selector);
if (method) {
g_originalSceneOpenURLContexts = reinterpret_cast<SceneOpenURLContexts>(method_getImplementation(method));
method_setImplementation(method, reinterpret_cast<IMP>(amnezia_scene_openURLContexts));
} else {
const char *types = "v@:@@";
class_addMethod(cls, selector, reinterpret_cast<IMP>(amnezia_scene_openURLContexts), types);
}
}
@end
+35 -3
View File
@@ -2,7 +2,8 @@ import Foundation
import os.log
struct Log {
static let osLog = Logger()
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
static var isLoggingEnabled: Bool {
@@ -77,9 +78,40 @@ struct Log {
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
NSLog("\(title) \(message)")
guard isLoggingEnabled else { return }
switch type {
case .debug:
if title.isEmpty {
osLog.debug("\(message, privacy: .public)")
} else {
osLog.debug("\(title, privacy: .public) \(message, privacy: .public)")
}
case .info:
if title.isEmpty {
osLog.info("\(message, privacy: .public)")
} else {
osLog.info("\(title, privacy: .public) \(message, privacy: .public)")
}
case .error:
if title.isEmpty {
osLog.error("\(message, privacy: .public)")
} else {
osLog.error("\(title, privacy: .public) \(message, privacy: .public)")
}
case .fault:
if title.isEmpty {
osLog.fault("\(message, privacy: .public)")
} else {
osLog.fault("\(title, privacy: .public) \(message, privacy: .public)")
}
default:
if title.isEmpty {
osLog.log("\(message, privacy: .public)")
} else {
osLog.log("\(title, privacy: .public) \(message, privacy: .public)")
}
}
osLog.log(level: type, "\(title) \(message)")
guard isLoggingEnabled else { return }
let date = Date()
let level = Record.Level(from: type)
+55 -1
View File
@@ -1,22 +1,76 @@
import Foundation
import os.log
private let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
private let wireGuardSystemLogger = Logger(subsystem: subsystemIdentifier, category: "WireGuard")
private let openVPNSystemLogger = Logger(subsystem: subsystemIdentifier, category: "OpenVPN")
private let xraySystemLogger = Logger(subsystem: subsystemIdentifier, category: "Xray")
private let networkExtensionLogger = Logger(subsystem: subsystemIdentifier, category: "NetworkExtension")
private func logToSystem(_ logger: Logger, type: OSLogType, prefix: String, title: String, message: String) {
let combinedTitle: String
if title.isEmpty {
combinedTitle = prefix
} else {
combinedTitle = "\(prefix): \(title)"
}
switch type {
case .debug:
if combinedTitle.isEmpty {
logger.debug("\(message, privacy: .public)")
} else {
logger.debug("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
case .info:
if combinedTitle.isEmpty {
logger.info("\(message, privacy: .public)")
} else {
logger.info("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
case .error:
if combinedTitle.isEmpty {
logger.error("\(message, privacy: .public)")
} else {
logger.error("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
case .fault:
if combinedTitle.isEmpty {
logger.fault("\(message, privacy: .public)")
} else {
logger.fault("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
default:
if combinedTitle.isEmpty {
logger.log("\(message, privacy: .public)")
} else {
logger.log("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
}
}
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
let stringMessage = String(describing: staticMessage)
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
neLog(type, title: "WG: \(title)", message: stringMessage)
}
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: message)
neLog(type, title: "WG: \(title)", message: message)
}
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
logToSystem(openVPNSystemLogger, type: type, prefix: "OVPN", title: title, message: message)
neLog(type, title: "OVPN: \(title)", message: message)
}
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
logToSystem(xraySystemLogger, type: type, prefix: "XRAY", title: title, message: message)
neLog(type, title: "XRAY: \(title)", message: message)
}
public func neLog(_ type: OSLogType, title: String = "", message: String) {
logToSystem(networkExtensionLogger, type: type, prefix: "NE", title: title, message: message)
Log.log(type, title: "NE: \(title)", message: message)
}
@@ -1,6 +1,7 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
import CryptoKit
struct OpenVPNConfig: Decodable {
let config: String
@@ -27,26 +28,83 @@ extension PacketTunnelProvider {
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
}
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
return
}
}
private func logOpenVPNError(_ error: NSError) {
let fatalFlag = (error.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false
var lines: [String] = []
lines.append("domain=\(error.domain) code=\(error.code) fatal=\(fatalFlag)")
if let adapterMessage = error.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !adapterMessage.isEmpty {
lines.append("message=\(adapterMessage)")
}
let userInfoKeys = error.userInfo.keys.map { String(describing: $0) }.sorted()
if !userInfoKeys.isEmpty {
lines.append("userInfoKeys=[\(userInfoKeys.joined(separator: ","))]")
}
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? NSError {
lines.append("underlying=\(underlying.domain)#\(underlying.code) fatal=\((underlying.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false)")
if let underlyingMessage = underlying.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !underlyingMessage.isEmpty {
lines.append("underlyingMessage=\(underlyingMessage)")
} else if !underlying.localizedDescription.isEmpty {
lines.append("underlyingLocalized=\(underlying.localizedDescription)")
}
} else if let underlying = error.userInfo[NSUnderlyingErrorKey] {
lines.append("underlyingRaw=\(underlying)")
}
let formatted = lines.joined(separator: "\n ")
ovpnLog(.error, title: "Error", message: formatted)
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
withShadowSocks viaSS: Bool = false,
completionHandler: @escaping (Error?) -> Void) {
ovpnLog(.info, message: "Setup and launch")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
var configString = String(decoding: ovpnConfiguration, as: UTF8.self)
let digest = SHA256.hash(data: ovpnConfiguration)
let digestString = digest.map { String(format: "%02x", $0) }.joined()
ovpnLog(.info, title: "ConfigDigest", message: digestString)
let hasTlsAuthOpen = configString.contains("<tls-auth>")
let hasTlsAuthClose = configString.contains("</tls-auth>")
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
let lines = configString.split(separator: "\n")
let head = lines.prefix(10).joined(separator: "\n")
let tail = lines.suffix(10).joined(separator: "\n")
ovpnLog(.debug, title: "ConfigHead", message: head)
ovpnLog(.debug, title: "ConfigTail", message: tail)
if let start = configString.range(of: "<tls-auth>"),
let end = configString.range(of: "</tls-auth>", range: start.upperBound..<configString.endIndex) {
let keyBody = String(configString[start.upperBound..<end.lowerBound])
ovpnLog(.debug, title: "TLSAuthInline", message: keyBody)
let sanitizedLines = keyBody
.split(whereSeparator: { $0.isNewline })
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.filter { !$0.hasPrefix("#") }
let sanitizedKey = sanitizedLines.joined(separator: "\n")
ovpnLog(.debug, title: "TLSAuthSanitized", message: sanitizedKey)
let sanitizedBlock = "<tls-auth>\n\(sanitizedKey)\n</tls-auth>"
configString.replaceSubrange(start.lowerBound..<end.upperBound, with: sanitizedBlock)
}
let normalizedConfig = configString.replacingOccurrences(of: "\r\n", with: "\n")
let sanitizedData = Data(normalizedConfig.utf8)
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.fileContent = sanitizedData
if configString.contains("cloak") {
configuration.setPTCloak()
}
@@ -57,6 +115,8 @@ extension PacketTunnelProvider {
evaluation = try ovpnAdapter?.apply(configuration: configuration)
} catch {
let nsError = error as NSError
ovpnLog(.error, title: "ApplyConfig", message: "domain=\(nsError.domain) code=\(nsError.code) info=\(nsError.userInfo)")
completionHandler(error)
return
}
@@ -73,7 +133,7 @@ extension PacketTunnelProvider {
startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow)
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter?.transportStatistics.bytesIn
@@ -208,8 +268,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
let nsError = error as NSError
logOpenVPNError(nsError)
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
guard let fatal = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
@@ -112,9 +112,19 @@ extension PacketTunnelProvider {
}
}
let lastHandshakeString = settingsDictionary["last_handshake_time_sec"]
let lastHandshake: Int64
if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) {
lastHandshake = handshakeValue
} else {
lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec`
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0",
"last_handshake_time_sec": lastHandshake
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
+17
View File
@@ -1,3 +1,4 @@
#if !MACOS_NE
#include "QRCodeReaderBase.h"
#import <UIKit/UIKit.h>
@@ -108,3 +109,19 @@ void QRCodeReader::startReading() {
void QRCodeReader::stopReading() {
[m_qrCodeReader stopReading];
}
#else
#include "QRCodeReaderBase.h"
QRCodeReader::QRCodeReader()
{
}
QRect QRCodeReader::cameraSize() {
return QRect();
}
void QRCodeReader::startReading() {}
void QRCodeReader::stopReading() {}
void QRCodeReader::setCameraSize(QRect) {}
#endif
+2 -1
View File
@@ -1,5 +1,6 @@
#if !MACOS_NE
#import <UIKit/UIKit.h>
#endif
@interface QIOSApplicationDelegate
@end
+2 -2
View File
@@ -5,7 +5,7 @@
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
#if !MACOS_NE
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
@@ -57,5 +57,5 @@
}
return NO;
}
#endif
@end
@@ -1,3 +1,13 @@
#if MACOS_NE
public func toggleScreenshots(_ isEnabled: Bool) {
}
class ScreenProtection {
}
#else
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
@@ -90,3 +100,4 @@ struct ProtectionPair {
textField.removeFromSuperview()
}
}
#endif
+1
View File
@@ -46,6 +46,7 @@ public:
void disconnectVpn();
void vpnStatusDidChange(void *pNotification);
void vpnConfigurationDidChange(void *pNotification);
void getBackendLogs(std::function<void(const QString &)> &&callback);
+62 -8
View File
@@ -27,15 +27,51 @@ const char* MessageKey::isOnDemand = "is-on-demand";
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
#if !MACOS_NE
static UIViewController* getViewController() {
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
UIApplication *application = [UIApplication sharedApplication];
if (@available(iOS 13.0, *)) {
for (UIScene *scene in application.connectedScenes) {
if (scene.activationState != UISceneActivationStateForegroundActive) {
continue;
}
if (![scene isKindOfClass:[UIWindowScene class]]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
if (window.isKeyWindow && window.rootViewController) {
return window.rootViewController;
}
}
for (UIWindow *window in windowScene.windows) {
if (!window.isHidden && window.rootViewController) {
return window.rootViewController;
}
}
}
}
for (UIWindow *window in application.windows) {
if (window.isKeyWindow && window.rootViewController) {
return window.rootViewController;
}
}
for (UIWindow *window in application.windows) {
if (window.rootViewController) {
return window.rootViewController;
}
}
return nil;
}
#endif
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
switch (status) {
@@ -249,6 +285,21 @@ void IosController::checkStatus()
sendVpnExtensionMessage(message, [&](NSDictionary* response){
uint64_t txBytes = [response[@"tx_bytes"] intValue];
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
uint64_t last_handshake_time_sec = 0;
#if !MACOS_NE
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
} else {
qDebug() << "Key last_handshake_time_sec is missing or null";
}
if (last_handshake_time_sec < 0) {
disconnectVpn();
qDebug() << "Invalid handshake time, disconnecting VPN.";
}
#endif
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
@@ -803,14 +854,14 @@ bool IosController::shareText(const QStringList& filesToSend) {
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
[sharingItems addObject:logFileUrl];
}
#if !MACOS_NE
UIViewController *qtController = getViewController();
if (!qtController) return;
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
#endif
__block bool isAccepted = false;
#if !MACOS_NE
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
isAccepted = completed;
emit finished();
@@ -823,6 +874,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
popController.sourceRect = CGRectMake(100, 100, 100, 100);
}
#endif
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();
@@ -831,6 +883,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
}
QString IosController::openFile() {
#if !MACOS_NE
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
@@ -841,8 +894,9 @@ QString IosController::openFile() {
[qtController presentViewController:documentPicker animated:YES completion:nil];
#endif
__block QString filePath;
#if !MACOS_NE
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
if (path) {
filePath = QString::fromUtf8(path.UTF8String);
@@ -851,7 +905,7 @@ QString IosController::openFile() {
}
emit finished();
};
#endif
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();
@@ -1,7 +1,11 @@
#import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NETunnelProviderSession.h>
#import <Foundation/Foundation.h>
#if !MACOS_NE
#include <UIKit/UIKit.h>
#endif
#include <Security/Security.h>
class IosController;
@@ -17,9 +21,10 @@ class IosController;
@end
typedef void (^DocumentPickerClosedCallback)(NSString *path);
#if !MACOS_NE
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
@end
#endif
@@ -26,7 +26,8 @@
@end
@implementation DocumentPickerDelegate
#if !MACOS_NE
@implementation DocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
for (NSURL *url in urls) {
@@ -42,4 +43,5 @@
}
}
@end
@end
#endif
@@ -6,6 +6,8 @@
#import <UserNotifications/UserNotifications.h>
#import <Foundation/Foundation.h>
#if !MACOS_NE
#import <UIKit/UIKit.h>
@interface IOSNotificationDelegate
@@ -87,3 +89,86 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
}
}];
}
#else
// Removed the UIResponder and UIApplicationDelegate references as these are not available in macOS
@interface IOSNotificationDelegate
: NSObject <UNUserNotificationCenterDelegate> {
IOSNotificationHandler* m_iosNotificationHandler;
}
@end
@implementation IOSNotificationDelegate
- (id)initWithObject:(IOSNotificationHandler*)notification {
self = [super init]; // Removed `super init` as it refers to UIResponder, which is iOS specific
if (self) {
m_iosNotificationHandler = notification;
}
return self;
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler {
Q_UNUSED(center)
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
didReceiveNotificationResponse:(UNNotificationResponse*)response
withCompletionHandler:(void (^)())completionHandler {
Q_UNUSED(center)
Q_UNUSED(response)
completionHandler();
}
@end
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError* _Nullable error) {
Q_UNUSED(granted);
if (!error) {
m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this];
}
}];
}
IOSNotificationHandler::~IOSNotificationHandler() { }
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
if (!m_delegate) {
return;
}
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = title.toNSString();
content.body = message.toNSString();
content.sound = [UNNotificationSound defaultSound];
int timerSec = timerMsec / 1000;
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:trigger];
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = (id<UNUserNotificationCenterDelegate>)m_delegate;
[center addNotificationRequest:request
withCompletionHandler:^(NSError* _Nullable error) {
if (error) {
NSLog(@"Local Notification failed");
}
}];
}
#endif
@@ -30,7 +30,6 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent)
Ikev2Protocol::~Ikev2Protocol()
{
qDebug() << "IpsecProtocol::~IpsecProtocol()";
disconnect_vpn();
Ikev2Protocol::stop();
}
@@ -38,7 +37,7 @@ void Ikev2Protocol::stop()
{
setConnectionState(Vpn::ConnectionState::Disconnecting);
{
if (! disconnect_vpn() ){
if (!disconnect_vpn()){
qDebug()<<"We don't disconnect";
setConnectionState(Vpn::ConnectionState::Error);
}
@@ -311,7 +310,9 @@ bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::disconnect_vpn(){
if ( hRasConn != nullptr ){
if ( RasHangUp(hRasConn) != ERROR_SUCCESS)
auto ret = RasHangUp(hRasConn);
qDebug() << "RasHangUp " << ret;
if (ret != ERROR_SUCCESS)
return false;
}
QThread::msleep(3000);
+2 -2
View File
@@ -192,7 +192,7 @@ namespace amnezia
constexpr char defaultPort[] = "51820";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";
@@ -212,7 +212,7 @@ namespace amnezia
namespace awg
{
constexpr char defaultPort[] = "55424";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";
+2 -2
View File
@@ -4,7 +4,7 @@
#include "core/errorstrings.h"
#include "vpnprotocol.h"
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#include "openvpnovercloakprotocol.h"
#include "openvpnprotocol.h"
#include "shadowsocksvpnprotocol.h"
@@ -109,7 +109,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
+1
View File
@@ -169,6 +169,7 @@ void XrayProtocol::stop()
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch();
IpcClient::Interface()->StartRoutingIpv6();
IpcClient::Interface()->restoreResolvers();
#endif
qDebug() << "XrayProtocol::stop()";
m_xrayProcess.disconnect();
+8 -1
View File
@@ -35,6 +35,9 @@
<file>images/controls/mail.svg</file>
<file>images/controls/map-pin.svg</file>
<file>images/controls/more-vertical.svg</file>
<file>images/controls/news.svg</file>
<file>images/controls/news-unread.svg</file>
<file>images/controls/unread-dot.svg</file>
<file>images/controls/plus.svg</file>
<file>images/controls/qr-code.svg</file>
<file>images/controls/radio-button-inner-circle-pressed.png</file>
@@ -49,6 +52,7 @@
<file>images/controls/server.svg</file>
<file>images/controls/settings-2.svg</file>
<file>images/controls/settings.svg</file>
<file>images/controls/settings-news.svg</file>
<file>images/controls/share-2.svg</file>
<file>images/controls/split-tunneling.svg</file>
<file>images/controls/tag.svg</file>
@@ -127,7 +131,6 @@
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Components/AddSitePanel.qml</file>
<file>ui/qml/Config/GlobalConfig.qml</file>
@@ -212,6 +215,8 @@
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
<file>ui/qml/Pages2/PageSettingsNewsNotifications.qml</file>
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
@@ -228,6 +233,7 @@
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
<file>ui/qml/Pages2/PageShare.qml</file>
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>ui/qml/Pages2/PageShareConnection.qml</file>
<file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Components/RenameServerDrawer.qml</file>
<file>ui/qml/Controls2/ListViewType.qml</file>
@@ -240,6 +246,7 @@
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
</qresource>
<qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file>
+519
View File
@@ -0,0 +1,519 @@
#!/bin/sh
LOG_DATE=$(date -u +'%Y%m%d-%H%M%S')
SCRIPT_DIR=$(dirname "$0")
LOG_FILE="${SCRIPT_DIR}/server-diagnostics-${LOG_DATE}.log"
# Logging function (sh compatible)
log_and_display() {
if [ "$1" = "-n" ]; then
shift
printf "%s" "$*" | tee -a "$LOG_FILE"
else
echo "$1" | tee -a "$LOG_FILE"
fi
}
# Redirect stderr to stdout for logging
exec 2>&1
header() {
log_and_display ""
log_and_display "=== $1 ==="
}
# Pause for cancellation
log_and_display ""
log_and_display "VPN Server Diagnostics will start in 9s. Press Ctrl+C to cancel."
sleep 9
log_and_display ""
header "STARTING VPN SERVER DIAGNOSTICS"
log_and_display ""
# ------------------------------------------------------------------------------
# 1. Basic system information
# ------------------------------------------------------------------------------
header "System Information"
# Uptime
UPTIME_STR=$(awk '{printf "%d:%02d:%02d", int($1/3600), int(($1%3600)/60), int($1%60)}' /proc/uptime 2>/dev/null || echo "unknown")
log_and_display "Uptime (H:M:S): $UPTIME_STR"
# Date/time UTC
DATE_UTC=$(date -u +'%d %b %Y|%T' 2>/dev/null || echo "unknown")
log_and_display "Date|Time (UTC): $DATE_UTC"
# Init system (PID 1)
INIT_NAME=$(cat /proc/1/status 2>/dev/null | head -1 | awk '{print $2}' 2>/dev/null || echo "unknown")
log_and_display "Init system (PID 1): $INIT_NAME"
# Locale
if echo "$LANG" | grep -E '^(en_US.UTF-8|C.UTF-8|C)$' >/dev/null 2>&1; then
log_and_display "Locale: $LANG"
else
log_and_display "Locale: $LANG (not en_US.UTF-8, C.UTF-8 or C)"
fi
# ------------------------------------------------------------------------------
# 2. Package manager detection
# ------------------------------------------------------------------------------
header "Package Manager Information"
if command -v apt-get >/dev/null 2>&1; then
log_and_display "Package Manager: APT"
PM="apt-get"
PM_VER_OPT="--version"
DOCKER_PKG="docker.io"
elif command -v dnf >/dev/null 2>&1; then
log_and_display "Package Manager: DNF"
PM="dnf"
PM_VER_OPT="--version"
DOCKER_PKG="docker"
elif command -v yum >/dev/null 2>&1; then
log_and_display "Package Manager: YUM"
PM="yum"
PM_VER_OPT="--version"
DOCKER_PKG="docker"
elif command -v zypper >/dev/null 2>&1; then
log_and_display "Package Manager: ZYPPER"
PM="zypper"
PM_VER_OPT="--version"
DOCKER_PKG="docker"
elif command -v pacman >/dev/null 2>&1; then
log_and_display "Package Manager: PACMAN"
PM="pacman"
PM_VER_OPT="--version"
DOCKER_PKG="docker"
elif command -v opkg >/dev/null 2>&1; then
log_and_display "Package Manager: OPKG - Not supported on this platform"
PM="opkg"
PM_VER_OPT="--version"
DOCKER_PKG="docker"
else
log_and_display "Package Manager: Unknown"
# fallback
PM="uname"
PM_VER_OPT="-a"
DOCKER_PKG="docker"
fi
# Check package versions
log_and_display ""
log_and_display "Package versions:"
# Check sudo
if [ "$PM" = "apt-get" ]; then
sudo_version=$(dpkg -s "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
sudo_version=$(rpm -q "sudo" 2>/dev/null || echo "not installed")
elif [ "$PM" = "pacman" ]; then
sudo_version=$(pacman -Q "sudo" 2>/dev/null || echo "not installed")
elif [ "$PM" = "opkg" ]; then
sudo_version=$(opkg info "sudo" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
else
sudo_version="unknown"
fi
log_and_display " sudo: $sudo_version"
# Check Docker package
if [ "$PM" = "apt-get" ]; then
docker_pkg_version=$(dpkg -s "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
docker_pkg_version=$(rpm -q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
elif [ "$PM" = "pacman" ]; then
docker_pkg_version=$(pacman -Q "$DOCKER_PKG" 2>/dev/null || echo "not installed")
elif [ "$PM" = "opkg" ]; then
docker_pkg_version=$(opkg info "$DOCKER_PKG" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
else
docker_pkg_version="unknown"
fi
log_and_display " $DOCKER_PKG: $docker_pkg_version"
# Check lsof
if [ "$PM" = "apt-get" ]; then
lsof_version=$(dpkg -s "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
elif [ "$PM" = "dnf" ] || [ "$PM" = "yum" ] || [ "$PM" = "zypper" ]; then
lsof_version=$(rpm -q "lsof" 2>/dev/null || echo "not installed")
elif [ "$PM" = "pacman" ]; then
lsof_version=$(pacman -Q "lsof" 2>/dev/null || echo "not installed")
elif [ "$PM" = "opkg" ]; then
lsof_version=$(opkg info "lsof" 2>/dev/null | grep '^Version:' | awk '{print $2}' || echo "not installed")
else
lsof_version="unknown"
fi
log_and_display " lsof: $lsof_version"
# ------------------------------------------------------------------------------
# 3. Additional system information (hostnamectl / /proc/version)
# ------------------------------------------------------------------------------
header "OS / Kernel Information"
if command -v hostnamectl >/dev/null 2>&1; then
hostnamectl 2>/dev/null | grep -E 'Operating System:|Virtualization:|Kernel:|Architecture:' | sed 's/^[ \t]*//;s/:/: /' | while read line; do
log_and_display " $line"
done
else
log_and_display "Operating System: $(cat /proc/version 2>/dev/null || echo 'unknown')"
fi
# CPU threads
CPU_THREADS=$(nproc 2>/dev/null || grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo "unknown")
log_and_display " CPU threads: $CPU_THREADS"
# ------------------------------------------------------------------------------
# 4. Memory (RAM) check
# ------------------------------------------------------------------------------
header "Memory Information"
if command -v free >/dev/null 2>&1; then
# Remove extra spaces in header
free -h 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
elif command -v vmstat >/dev/null 2>&1; then
vmstat -S M -s 2>/dev/null | grep -iE 'total memory|total swap' | sed 's/ *//' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
else
grep -iE 'MemTotal|SwapTotal' /proc/meminfo 2>/dev/null | sed 's/ \+/ /' | tee -a "$LOG_FILE" || log_and_display " Error getting memory info"
fi
if command -v free >/dev/null 2>&1; then
log_and_display ""
log_and_display "Detailed Memory Info:"
free -h 2>/dev/null | awk 'NR==2{printf " Used: %s / %s (%.1f%%)\n", $3, $2, $3/$2*100}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating memory usage"
free -h 2>/dev/null | awk 'NR==3{printf " Swap: %s / %s (%.1f%%)\n", $3, $2, $2>0 ? $3/$2*100 : 0}' 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error calculating swap usage"
fi
# Disk usage
header "Disk Usage"
df -h 2>/dev/null | awk '
BEGIN {print " Filesystem Size Used Avail Use% Mounted"}
NR>1 {printf " %-10s %5s %5s %5s %4s %s\n", $1, $2, $3, $4, $5, $6}' | tee -a "$LOG_FILE" || log_and_display " Error getting disk usage"
# ------------------------------------------------------------------------------
# 5. Current user and sudo check
# ------------------------------------------------------------------------------
header "User Check"
CUR_USER=$(whoami 2>/dev/null || echo ~ | sed 's/.*\///')
USER_GROUP=$(groups "$CUR_USER" 2>/dev/null || echo "")
USER_GOOD=0
log_and_display -n "Current user: $CUR_USER => "
if [ "$CUR_USER" = "root" ]; then
log_and_display "passed.. (is root)"
USER_GOOD="r" # root
else
if echo "$USER_GROUP" | grep -qE '(^|[[:space:]])sudo($|[[:space:]])'; then
log_and_display "passed.. (in sudo group)"
USER_GOOD=1
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])wheel($|[[:space:]])'; then
log_and_display "passed.. (in wheel group)"
USER_GOOD=1
elif echo "$USER_GROUP" | grep -qE '(^|[[:space:]])docker($|[[:space:]])'; then
log_and_display "failed.. (only in docker group)"
USER_GOOD="d"
else
log_and_display "failed.. (not a member of the sudo or wheel groups)"
USER_GOOD=0
fi
fi
# Check if password is required for sudo
if [ "$USER_GOOD" = "0" ] || [ "$USER_GOOD" = "d" ]; then
log_and_display -n "Passwd request: "
log_and_display "check skipped (not sudoer)"
else
if command -v sudo >/dev/null 2>&1; then
# Try sudo without password - more thorough check
PASSWD_REQUEST=$(sudo -K 2>&1 && sudo -nu $CUR_USER $PM $PM_VER_OPT 2>&1 >/dev/null && sudo -n $PM $PM_VER_OPT 2>&1 >/dev/null)
if [ -n "$PASSWD_REQUEST" ]; then
USER_GOOD=0
log_and_display -n "Passwd request: "
log_and_display "failed.. ($PASSWD_REQUEST)" \
| sed "s/$CUR_USER/User/g;s/$(hostname 2>/dev/null || echo 'Server')/Server/g;s/ user / /g"
else
log_and_display -n "Passwd request: "
log_and_display "passed.. (not required)"
fi
else
if [ "$USER_GOOD" = "r" ]; then
log_and_display -n "Passwd request: "
log_and_display "check skipped (sudo not installed, but root user)"
else
log_and_display "Warning! The sudo package must be pre-installed!"
USER_GOOD=0
fi
fi
fi
# Home directory check
log_and_display -n "Home dir: "
if cd ~ 2>/dev/null; then
log_and_display "passed.. (accessible)"
else
log_and_display "failed.. (not accessible)"
fi
log_and_display "Default shell: $SHELL"
# ------------------------------------------------------------------------------
# 6. Important components check (sudo, lsof, fuser, apparmor)
# ------------------------------------------------------------------------------
header "Component Checks"
log_and_display -n " sudo: "
if command -v sudo >/dev/null 2>&1; then
log_and_display "passed.. (installed)"
else
log_and_display "not installed"
fi
log_and_display -n " lsof: "
if command -v lsof >/dev/null 2>&1; then
log_and_display "passed.. (installed)"
else
log_and_display "not installed"
fi
log_and_display -n " fuser: "
if command -v fuser >/dev/null 2>&1; then
log_and_display "passed.. (installed)"
else
log_and_display "psmisc not installed"
fi
log_and_display -n "apparmor: "
AA_ENABLED=$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null || echo "N")
if [ "$AA_ENABLED" = "Y" ]; then
if command -v apparmor_parser >/dev/null 2>&1; then
log_and_display "passed.. (used)"
else
log_and_display "failed.. (installation required)"
fi
else
if command -v apparmor_parser >/dev/null 2>&1; then
log_and_display "passed.. (not used)"
else
log_and_display "passed.. (not required)"
fi
fi
# ------------------------------------------------------------------------------
# 7. SELinux check
# ------------------------------------------------------------------------------
header "SELinux Check"
if command -v getenforce >/dev/null 2>&1; then
SELINUX_STATUS=$(getenforce 2>/dev/null || echo "unknown")
if [ "$SELINUX_STATUS" = "Enforcing" ]; then
log_and_display "SELinux status: $SELINUX_STATUS (strict mode)"
elif [ "$SELINUX_STATUS" = "Permissive" ]; then
log_and_display "SELinux status: $SELINUX_STATUS (permissive mode)"
else
log_and_display "SELinux status: $SELINUX_STATUS (disabled)"
fi
else
log_and_display "SELinux: not found (or not applicable)"
fi
# ------------------------------------------------------------------------------
# 8. Docker + Docker/Podman service check
# ------------------------------------------------------------------------------
header "Docker / Podman Status"
CHECK_CONTAINERS=0
if ! command -v docker >/dev/null 2>&1; then
log_and_display "Docker: $DOCKER_PKG not installed"
else
# If user is in sudoers, use sudo without password
if [ "$USER_GOOD" = "1" ]; then
SUD="sudo -n"
elif [ "$USER_GOOD" = "r" ]; then
SUD="" # root
else
SUD=""
fi
DOCKER_VERSION=$($SUD docker -v 2>/dev/null || echo 'docker -v error')
log_and_display "Installed: $DOCKER_VERSION"
# Check for podman
if echo "$DOCKER_VERSION" | grep -qi "podman"; then
log_and_display " WARNING: Podman detected - not supported at the moment!"
log_and_display " Podman (podman-docker) is not supported and is installed by mistake"
docker_service="podman.socket"
else
docker_service="docker.service"
fi
log_and_display " service: $docker_service"
# Check status
if command -v systemctl >/dev/null 2>&1; then
docker_status=$(systemctl is-active "$docker_service" 2>/dev/null || echo "unknown")
docker_loading=$(systemctl is-enabled "$docker_service" 2>/dev/null || echo "unknown")
else
docker_status="unknown (systemctl not found)"
docker_loading="unknown"
fi
if [ "$docker_status" = "active" ]; then
log_and_display " status: passed.. ($docker_status)"
CHECK_CONTAINERS=1
else
log_and_display " status: incorrect.. ($docker_status)"
CHECK_CONTAINERS=0
fi
if [ "$docker_loading" = "enabled" ]; then
log_and_display " loading: good (startup $docker_loading)"
else
log_and_display " loading: bad (startup $docker_loading)"
fi
fi
# ------------------------------------------------------------------------------
# 9. Docker pull test + container check with improved Docker Hub verification
# ------------------------------------------------------------------------------
header "Docker Hub: pull hello-world test"
if [ "$CHECK_CONTAINERS" = "1" ] && [ "$USER_GOOD" != "0" ]; then
# First check Docker Hub availability
log_and_display "Checking Docker Hub connectivity..."
# Try to execute docker pull with timeout
if timeout 30 $SUD docker pull docker.io/library/hello-world >/dev/null 2>&1; then
log_and_display "Docker Hub: available"
# Start container for testing
if $SUD docker run --rm docker.io/library/hello-world >/dev/null 2>&1; then
log_and_display "Hello-world container: successfully started and completed"
else
log_and_display "Hello-world container: startup error"
fi
else
log_and_display "Docker Hub: unavailable or blocked (possibly exceeded download limit)"
log_and_display "Docker Hub has download limits, try again later"
fi
log_and_display ""
total_cont=$($SUD docker ps -aq 2>/dev/null | wc -l || echo "0")
active_cont=$($SUD docker ps -q 2>/dev/null | wc -l || echo "0")
amnezia_cont=$($SUD docker ps -a 2>/dev/null | grep -c amnezia || echo "0")
log_and_display "Containers check: Total $total_cont / Active $active_cont / Amnezia $amnezia_cont"
$SUD docker ps -a --format "{{.Names}} ({{.Image}}) ({{.Status}}) ({{.Ports}})" 2>/dev/null | grep amnezia || true
# Peers check
if $SUD docker ps 2>/dev/null | grep -qE '\<(amnezia-awg|amnezia-wireguard)\>'; then
log_and_display ""
log_and_display "Peers check (beta):"
if $SUD docker ps 2>/dev/null | grep -q amnezia-awg; then
AMNEZIA_WG_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-awg | awk '{print $1}' | head -1)
if [ -n "$AMNEZIA_WG_CONTAINER" ]; then
WG_PEERS=$($SUD docker exec -it "$AMNEZIA_WG_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
log_and_display "AmneziaWG peers: $WG_PEERS"
fi
fi
if $SUD docker ps 2>/dev/null | grep -q amnezia-wireguard; then
WIREGUARD_CONTAINER=$($SUD docker ps 2>/dev/null | grep amnezia-wireguard | awk '{print $1}' | head -1)
if [ -n "$WIREGUARD_CONTAINER" ]; then
WG_PEERS=$($SUD docker exec -it "$WIREGUARD_CONTAINER" wg show 2>/dev/null | grep -c 'peer' || echo "0")
log_and_display "WireGuard peers: $WG_PEERS"
fi
fi
fi
else
log_and_display "skipped.."
fi
# ------------------------------------------------------------------------------
# 10. Additional improvements
# ------------------------------------------------------------------------------
#
# 10.1. CPU and memory load check (Load average, top processes)
#
header "CPU & Memory usage (top)"
# Load average (last 1,5,15 minutes)
LOAD_AVG=$(uptime 2>/dev/null | awk -F'load average:' '{print $2}' || echo "unknown")
log_and_display "Load average: $LOAD_AVG"
log_and_display ""
log_and_display "Top 5 processes by CPU:"
ps aux 2>/dev/null | sort -k3 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting CPU processes"
log_and_display ""
log_and_display "Top 5 processes by MEM:"
ps aux 2>/dev/null | sort -k4 -nr | head -n 6 | awk '{printf "%s %s %s %s %s\n", $1,$2,$3"%",$4"%",$11}' | column -t 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting MEM processes"
# 10.2. System logs check (latest critical messages)
header "Last 10 critical/error messages (journalctl)"
if command -v journalctl >/dev/null 2>&1; then
journalctl -p 3 -n 10 --no-pager 2>/dev/null | tee -a "$LOG_FILE" || log_and_display " Error getting system logs"
else
log_and_display "journalctl not found (non-systemd system?)"
fi
# 10.3. System package versions check (examples)
# Open ports check
header "Network Ports Check"
if command -v netstat >/dev/null 2>&1; then
log_and_display "Listening ports:"
netstat -tlnp 2>/dev/null | grep LISTEN | head -10 | while read line; do
log_and_display " $line"
done
elif command -v ss >/dev/null 2>&1; then
log_and_display "Listening ports:"
ss -tlnp 2>/dev/null | head -10 | while read line; do
log_and_display " $line"
done
else
log_and_display "netstat/ss not found"
fi
# SSH check
header "SSH Service Check"
if command -v systemctl >/dev/null 2>&1; then
ssh_status=$(systemctl is-active ssh 2>/dev/null || systemctl is-active sshd 2>/dev/null || echo "not found")
if [ "$ssh_status" = "active" ]; then
log_and_display "SSH service: $ssh_status"
else
log_and_display "SSH service: $ssh_status"
fi
else
log_and_display "systemctl not found"
fi
# Time check
header "Time Synchronization"
if command -v timedatectl >/dev/null 2>&1; then
timedatectl status 2>/dev/null | grep -E "System clock|NTP service" | while read line; do
log_and_display " $line"
done
else
log_and_display " System time: $(date 2>/dev/null || echo 'unknown')"
fi
# Kernel check
header "Kernel Information"
log_and_display "Kernel version: $(uname -r 2>/dev/null || echo 'unknown')"
log_and_display "Kernel architecture: $(uname -m 2>/dev/null || echo 'unknown')"
if [ -f /proc/cmdline ]; then
log_and_display "Kernel parameters:"
cat /proc/cmdline 2>/dev/null | tr ' ' '\n' | head -5 | while read param; do
log_and_display " $param"
done
fi
# ------------------------------------------------------------------------------
# Completion
# ------------------------------------------------------------------------------
log_and_display ""
header "FINISH"
log_and_display ""
log_and_display "Diagnostics completed. Log saved to: $LOG_FILE"
log_and_display ""
# Variable cleanup
pm="" && opt="" && docker_pkg="" && CUR_USER="" && USER_GOOD="" && USER_GROUP="" && PASSWD_REQUEST="" && CHECK_CONTAINERS="" && SUD="" && docker_service="" && docker_status="" && docker_loading=""
+1 -1
View File
@@ -1,7 +1,7 @@
FROM alpine:3.15
LABEL maintainer="AmneziaVPN"
ARG XRAY_RELEASE="v1.8.6"
ARG XRAY_RELEASE="v25.8.3"
RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz
RUN apk --update upgrade --no-cache
+12 -2
View File
@@ -541,12 +541,12 @@ QString Settings::getGatewayEndpoint()
bool Settings::isDevGatewayEnv()
{
return m_isDevGatewayEnv;
return value("Conf/devGatewayEnv", false).toBool();
}
void Settings::toggleDevGatewayEnv(bool enabled)
{
m_isDevGatewayEnv = enabled;
setValue("Conf/devGatewayEnv", enabled);
}
bool Settings::isHomeAdLabelVisible()
@@ -578,3 +578,13 @@ void Settings::setAllowedDnsServers(const QStringList &servers)
{
setValue("Conf/allowedDnsServers", servers);
}
QStringList Settings::readNewsIds() const
{
return value("News/readIds").toStringList();
}
void Settings::setReadNewsIds(const QStringList &ids)
{
setValue("News/readIds", ids);
}
+4 -2
View File
@@ -174,7 +174,7 @@ public:
QLocale getAppLanguage()
{
QString localeStr = m_settings.value("Conf/appLanguage").toString();
QString localeStr = m_settings.value("Conf/appLanguage", QLocale::system().name()).toString();
return QLocale(localeStr);
};
void setAppLanguage(QLocale locale)
@@ -236,6 +236,9 @@ public:
QStringList allowedDnsServers() const;
void setAllowedDnsServers(const QStringList &servers);
QStringList readNewsIds() const;
void setReadNewsIds(const QStringList &ids);
signals:
void saveLogsChanged(bool enabled);
void screenshotsEnabledChanged(bool enabled);
@@ -251,7 +254,6 @@ private:
mutable SecureQSettings m_settings;
QString m_gatewayEndpoint;
bool m_isDevGatewayEnv = false;
};
#endif // SETTINGS_H
File diff suppressed because it is too large Load Diff
@@ -43,6 +43,11 @@ namespace
constexpr char authData[] = "auth_data";
constexpr char config[] = "config";
constexpr char subscription[] = "subscription";
constexpr char endDate[] = "end_date";
constexpr char isConnectEvent[] = "is_connect_event";
}
struct ProtocolData
@@ -59,6 +64,7 @@ namespace
{
QString osVersion;
QString appVersion;
QString appLanguage;
QString installationUuid;
@@ -78,6 +84,9 @@ namespace
if (!appVersion.isEmpty()) {
obj[configKey::appVersion] = appVersion;
}
if (!appLanguage.isEmpty()) {
obj[apiDefs::key::appLanguage] = appLanguage;
}
if (!installationUuid.isEmpty()) {
obj[configKey::uuid] = installationUuid;
}
@@ -163,7 +172,7 @@ namespace
auto clientProtocolConfig =
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
//TODO looks like this block can be removed after v1 configs EOL
// TODO looks like this block can be removed after v1 configs EOL
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
@@ -217,12 +226,28 @@ namespace
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
apiConfig.insert(apiDefs::key::supportedProtocols,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
apiConfig.insert(apiDefs::key::serviceInfo,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::serviceInfo).toObject());
}
serverConfig[configKey::apiConfig] = apiConfig;
return ErrorCode::NoError;
}
bool isSubscriptionExpired(const QJsonObject &apiConfig)
{
auto subscription = apiConfig.value(configKey::subscription).toObject();
if (subscription.isEmpty()) {
return false;
}
auto subscriptionEndDate = subscription.value(configKey::endDate).toString();
if (apiUtils::isSubscriptionExpired(subscriptionEndDate)) {
return true;
}
return false;
}
}
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
@@ -232,6 +257,23 @@ ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &s
{
}
bool ApiConfigsController::exportVpnKey(const QString &fileName)
{
if (fileName.isEmpty()) {
emit errorOccurred(ErrorCode::PermissionsError);
return false;
}
prepareVpnKeyExport();
if (m_vpnKey.isEmpty()) {
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
return false;
}
SystemController::saveFile(fileName, m_vpnKey);
return true;
}
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
{
if (fileName.isEmpty()) {
@@ -242,8 +284,14 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode,
@@ -277,8 +325,14 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode,
@@ -303,6 +357,13 @@ void ApiConfigsController::prepareVpnKeyExport()
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
if (vpnKey.isEmpty()) {
vpnKey = apiUtils::getPremiumV2VpnKey(serverConfigObject);
apiConfigObject.insert(apiDefs::key::vpnKey, vpnKey);
serverConfigObject.insert(configKey::apiConfig, apiConfigObject);
m_serversModel->editServer(serverConfigObject, m_serversModel->getProcessedServerIndex());
}
m_vpnKey = vpnKey;
vpnKey.replace("vpn://", "");
@@ -322,6 +383,7 @@ bool ApiConfigsController::fillAvailableServices()
{
QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType();
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
@@ -345,6 +407,7 @@ bool ApiConfigsController::importServiceFromGateway()
{
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
m_apiServicesModel->getCountryCode(),
"",
@@ -396,8 +459,14 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfig)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
apiConfig.value(configKey::userCountryCode).toString(),
newCountryCode,
@@ -410,6 +479,10 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
if (newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig) {
apiPayload.insert(configKey::isConnectEvent, true);
}
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
@@ -429,6 +502,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
newServerConfig.insert(configKey::apiConfig, newApiConfig);
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
newServerConfig.insert(config_key::crc, serverConfig.value(config_key::crc));
if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) {
newServerConfig.insert(config_key::name, serverConfig.value(config_key::name));
@@ -492,7 +566,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
}
}
bool ApiConfigsController::deactivateDevice()
bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
{
auto serverIndex = m_serversModel->getProcessedServerIndex();
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
@@ -502,8 +576,18 @@ bool ApiConfigsController::deactivateDevice()
return true;
}
if (isSubscriptionExpired(apiConfigObject)) {
if (isRemoveEvent) {
return true;
} else {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
apiConfigObject.value(configKey::userCountryCode).toString(),
apiConfigObject.value(configKey::serverCountryCode).toString(),
@@ -536,8 +620,14 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
return true;
}
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
uuid,
apiConfigObject.value(configKey::userCountryCode).toString(),
serverCountryCode,
@@ -21,7 +21,7 @@ public:
public slots:
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
bool revokeNativeConfig(const QString &serverCountryCode);
// bool exportVpnKey(const QString &fileName);
bool exportVpnKey(const QString &fileName);
void prepareVpnKeyExport();
void copyVpnKeyToClipboard();
@@ -30,7 +30,7 @@ public slots:
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig = false);
bool updateServiceFromTelegram(const int serverIndex);
bool deactivateDevice();
bool deactivateDevice(const bool isRemoveEvent);
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
bool isConfigValid();
@@ -0,0 +1,68 @@
#include "apiNewsController.h"
#include "core/api/apiUtils.h"
#include <QJsonDocument>
#include <QJsonObject>
namespace
{
namespace configKey
{
constexpr char userCountryCode[] = "user_country_code";
constexpr char serviceType[] = "service_type";
}
}
ApiNewsController::ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
const QSharedPointer<ServersModel> &serversModel, QObject *parent)
: QObject(parent), m_newsModel(newsModel), m_settings(settings), m_serversModel(serversModel)
{
}
void ApiNewsController::fetchNews()
{
if (m_serversModel.isNull()) {
qWarning() << "ServersModel is null, skip fetchNews";
return;
}
const auto stacks = m_serversModel->gatewayStacks();
if (stacks.isEmpty()) {
qDebug() << "No Gateway stacks, skip fetchNews";
return;
}
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject payload;
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
const QJsonObject stacksJson = stacks.toJson();
if (stacksJson.contains(configKey::userCountryCode)) {
payload.insert(configKey::userCountryCode, stacksJson.value(configKey::userCountryCode));
}
if (stacksJson.contains(configKey::serviceType)) {
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
}
auto future = gatewayController.postAsync(QString("%1v1/news"), payload);
future.then(this, [this](QPair<ErrorCode, QByteArray> result) {
auto [errorCode, responseBody] = result;
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return;
}
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
QJsonArray newsArray;
if (doc.isArray()) {
newsArray = doc.array();
} else if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.value("news").isArray()) {
newsArray = obj.value("news").toArray();
}
}
m_newsModel->updateModel(newsArray);
emit fetchNewsFinished();
});
}
@@ -0,0 +1,34 @@
#ifndef APINEWSCONTROLLER_H
#define APINEWSCONTROLLER_H
#include <QJsonArray>
#include <QObject>
#include <QSharedPointer>
#include <memory>
#include "core/api/apiDefs.h"
#include "core/controllers/gatewayController.h"
#include "settings.h"
#include "ui/models/newsModel.h"
#include "ui/models/servers_model.h"
class ApiNewsController : public QObject
{
Q_OBJECT
public:
explicit ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
const QSharedPointer<ServersModel> &serversModel, QObject *parent = nullptr);
Q_INVOKABLE void fetchNews();
signals:
void errorOccurred(ErrorCode errorCode);
void fetchNewsFinished();
private:
QSharedPointer<NewsModel> m_newsModel;
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServersModel> m_serversModel;
};
#endif // APINEWSCONTROLLER_H
@@ -62,6 +62,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
apiPayload[configKey::authData] = authData;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody;
@@ -1,6 +1,6 @@
#include "connectionController.h"
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -32,8 +32,9 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
void ConnectionController::openConnection()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
{
emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
return;
}
+2 -1
View File
@@ -297,10 +297,11 @@ void ExportController::revokeConfig(const int row, const DockerContainer contain
{
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
ErrorCode errorCode =
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorCode);
}
emit revokeConfigCompleted();
}
void ExportController::renameClient(const int row, const QString &clientName, const DockerContainer container, ServerCredentials credentials)
+1
View File
@@ -42,6 +42,7 @@ public slots:
signals:
void generateConfig(int type);
void revokeConfigCompleted();
void exportErrorOccurred(const QString &errorMessage);
void exportErrorOccurred(ErrorCode errorCode);
+11 -5
View File
@@ -4,6 +4,12 @@
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include "logger.h"
namespace {
Logger logger("FocusController");
}
FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
: QObject { parent },
m_engine { engine },
@@ -85,7 +91,7 @@ void FocusController::dropRootObject(QObject *object)
dropListView();
setFocusOnDefaultItem();
} else {
qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
logger.warning() << "TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
}
}
@@ -101,7 +107,7 @@ void FocusController::reload(Direction direction)
QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top());
if (!rootObject) {
qCritical() << "No ROOT OBJECT found!";
logger.error() << "No ROOT OBJECT found!";
resetRootObject();
dropListView();
return;
@@ -113,7 +119,7 @@ void FocusController::reload(Direction direction)
direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore);
if (m_focusChain.empty()) {
qWarning() << "Focus chain is empty!";
logger.warning() << "Focus chain is empty!";
resetRootObject();
dropListView();
return;
@@ -131,7 +137,7 @@ void FocusController::nextItem(Direction direction)
}
if (m_focusChain.empty()) {
qWarning() << "There are no items to navigate";
logger.warning() << "There are no items to navigate";
setFocusOnDefaultItem();
return;
}
@@ -149,7 +155,7 @@ void FocusController::nextItem(Direction direction)
const auto focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(focusedItemIndex));
if (focusedItem == nullptr) {
qWarning() << "Failed to get item to focus on. Setting focus on default";
logger.warning() << "Failed to get item to focus on. Setting focus on default";
setFocusOnDefaultItem();
return;
}
+3 -3
View File
@@ -19,7 +19,7 @@
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <CoreFoundation/CoreFoundation.h>
#endif
@@ -274,7 +274,7 @@ void ImportController::processNativeWireGuardConfig()
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject();
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(2, 5));
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
QString junkPacketMinSize = QString::number(10);
QString junkPacketMaxSize = QString::number(50);
clientProtocolConfig[config_key::junkPacketCount] = junkPacketCount;
@@ -595,7 +595,7 @@ void ImportController::startDecodingQr()
m_totalQrCodeChunksCount = 0;
m_receivedQrCodeChunksCount = 0;
#if defined Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
m_isQrCodeProcessed = true;
#endif
#if defined Q_OS_ANDROID
+98 -3
View File
@@ -73,7 +73,7 @@ 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(2, 5));
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
QString junkPacketMinSize = QString::number(10);
QString junkPacketMaxSize = QString::number(50);
@@ -403,9 +403,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
const auto &protocols = ContainerProps::protocolsForContainer(container);
for (const auto &protocol : protocols) {
QJsonObject containerConfig;
if (protocol == mainProto) {
// for Multiprotocols (OpenVPN over SS, OpenVPN over Cloak)
bool shouldProcessProtocol = false;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
shouldProcessProtocol = true;
} else {
shouldProcessProtocol = (protocol == mainProto);
}
if (shouldProcessProtocol) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
@@ -550,14 +560,97 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
qDebug() << siteName;
containerConfig.insert(config_key::site, siteName);
} else if (protocol == Proto::OpenVpn) {
QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
protocols::openvpn::serverConfigPath, errorCode);
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) {
continue;
} else {
QStringList parts = trimmedLine.split(" ");
if (parts.count() >= 2) {
QString key = parts[0];
QString value = parts.mid(1).join(" ");
serverConfigMap.insert(key, value);
}
}
}
QString serverValue = serverConfigMap.value("server");
if (!serverValue.isEmpty()) {
QStringList serverParts = serverValue.split(" ");
if (serverParts.count() >= 1) {
containerConfig[config_key::subnet_address] = serverParts[0];
}
}
bool ncpDisable = serverConfig.contains("ncp-disable");
containerConfig[config_key::ncp_disable] = ncpDisable;
bool tlsAuth = serverConfig.contains("tls-auth");
containerConfig[config_key::tls_auth] = tlsAuth;
bool blockOutsideDns = serverConfig.contains("block-outside-dns");
containerConfig[config_key::block_outside_dns] = blockOutsideDns;
QString cipher = serverConfigMap.value("cipher");
if (!cipher.isEmpty()) {
containerConfig[config_key::cipher] = cipher;
}
QString hash = serverConfigMap.value("auth");
if (!hash.isEmpty()) {
containerConfig[config_key::hash] = hash;
}
} else if (protocol == Proto::Cloak) {
QString cloakConfig = serverController->getTextFileFromContainer(container, credentials,
"/opt/amnezia/cloak/ck-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(cloakConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject cloakConfigObj = doc.object();
QString site = cloakConfigObj.value("RedirAddr").toString();
if (!site.isEmpty()) {
containerConfig[config_key::site] = site;
}
} else {
qDebug() << "Failed to parse main loop Cloak JSON config";
}
} else if (protocol == Proto::ShadowSocks) {
QString shadowsocksConfig = serverController->getTextFileFromContainer(container, credentials,
"/opt/amnezia/shadowsocks/ss-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject ssConfigObj = doc.object();
QString cipher = ssConfigObj.value("method").toString();
if (!cipher.isEmpty()) {
containerConfig[config_key::cipher] = cipher;
}
} else {
qDebug() << "Failed to parse main loop Shadowsocks JSON config";
}
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
if (shouldProcessProtocol) {
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
}
installedContainers.insert(container, config);
}
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
if (torOrDnsRegMatch.hasMatch()) {
@@ -721,6 +814,8 @@ void InstallController::clearCachedProfile(QSharedPointer<ServerController> serv
m_clientManagementModel->revokeClient(containerConfig, container, serverCredentials, serverIndex, serverController);
emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerProps::containerHumanNames().value(container)));
QJsonObject updatedConfig = m_settings->containerConfig(serverIndex, container);
emit profileCleared(updatedConfig);
}
QRegularExpression InstallController::ipAddressPortRegExp()

Some files were not shown because too many files have changed in this diff Show More